; ============================================================================
; LOAD ($C000-$C3FF) - Electronic Arts fast loader / bootstrap, C64 side
; ============================================================================
; Part of directory file "load" (PRG, $9800-$C3FF). Entry point $C145 is called once from $02B8.
; Installs drive code via a DOS "B-E" (block execute) of track 1 sector 17, then pulls the game from
; the disk through a 2-bit serial protocol on CIA2 port A ($DD00); the drive decrypts each sector before
; sending it, so every sector except those on track 18 is stored XOR-encrypted on disk.
; $C004/$C034 form the public API the boot sequence uses; the game keeps a private copy at $0804/$085C
; because this page is later overwritten by the text-engine overlay.

        .setcpu "6502"
        .include "c64.inc"
        .include "kernal.inc"
        .include "zeropage.inc"

; ---- references to code/data outside this file ----
D_01FE                   := $01FE
checksumXorConstant      := $0400
D_09C3                   := $09C3
drawDroneHeadingPanel    := $6F00
D_8C00                   := $8C00
titleColorRam            := $9800
titleScreenRam           := $9C00
titleBitmapAAEA          := $AAEA
titleBitmapBAA2          := $BAA2
titleBitmapBAA3          := $BAA3
L_C411                   := $C411
strAttemptingRepairMid   := $CBED
strAttemptingRepairEnd   := $CBF2
D_FD1A                   := $FD1A
D_FED2                   := $FED2
nmiVector                := $FFFA
nmiVectorHi              := $FFFB
irqVector                := $FFFE
irqVectorHi              := $FFFF

; Contents
; --------
;    $C004  bootLoadSectors              Read A consecutive sectors from track Y starting at sector X into the address
;                                        set by bootSetDest, one page per sector.
;    $C034  bootSetDest                  Set the address the next transfer goes to, by patching the two operand bytes
;                                        bootDestLo and bootDestHi that bootReadSectorBody copies into the zero-page pointer.
;    $C03B  bootExchangeByte             Full-duplex byte exchange with the drive over CIA2 port A, one bit per
;                                        handshake, LSB first.
;    $C08A  bootReadOneSector            Wrapper round the protocol body: remember track and sector, stop CIA1 timer A
;                                        so nothing can interrupt the cycle-counted burst, run the transfer and start the timer again.
;    $C09D  bootReadSectorBody           The protocol body of one sector read.
;    $C0EF  bootReceiveSectorData        The timing-critical half of the loader, identical to receiveSectorData ($0950)
;                                        in the game.
;    $C145  bootMain                     The whole boot sequence.
;    $C218  bootDone                     An RTS that is really a jump.
;    $C229  rtiVector                    The entire interrupt handler while the loader owns the machine.
;    $C22A  bootDisableIrqs              Mask and acknowledge every interrupt source in the machine: both CIA interrupt
;                                        control registers and the VIC interrupt mask.
;    $C23F  bootShowTitlePicture         Puts the title picture on the screen, and on the way arms both halves of the
;                                        trick ending.
;    $C2AB  bootDriveInitialize          OPEN 15,8,15,'I0', wait four frames and CLOSE 15, so the drive re-reads the BAM
;                                        and forgets any state left over from the LOAD of the file 'load'.
;    $C2CA  bootWaitFrames               Wait Y video frames by watching bit 7 of the raster register go high and then
;                                        low again once per frame.
;    $C2DA  bootModemInit                Opens the KERNAL's RS-232 device at 300 baud, sends a carriage return and then
;                                        the Hayes string 'ATE0S0=0' so that a modem sitting on the user port stops echoing and will not answer the phone
;                                        during the load.
;    $C32C  bootBlockExecute             Installs the fast loader in the 1541.
;    $C341  bootSendDosCommand           Sends one 0-terminated string to the drive as an OPEN on the given channel:
;                                        LISTEN the current device, SECOND with the secondary address, CIOUT every byte up to the terminator, UNLSN.
;    $C3C4  leftoverCheckDiskId          Unreachable, and truncated by the end of the file: the original of the game's
;                                        checkDiskId ($0FB1), left behind from a build in which the loader lived at $CBxx and read the BAM to $BA00.

        .org $C000


; bootEntry - Public entry of the boot loader: JMP bootMain ($C145).  Emitted as .byte in the listing
; but it is a 3-byte JMP, put at the very start of the file so the caller does not have to know where
; bootMain lives.
; In:  none (the KERNAL has just loaded the file 'load' to $9800-$C3FF)
; Out: never comes back - see bootMain, whose RTS is redirected to $0461
; Called from: eaLoadGame $02D2 (boot/eaBootFile), the only caller in the whole boot chain.
bootEntry:
        .byte   $4C                     ; C000  JMP bootMain - the loader's one public entry
        .byte   $45,$C1                 ; C001  E.
bootLoaderStatus:
        .byte   $00                     ; C003  bootLoaderStatus: the last non-zero DOS error code seen by bootLoadSectors; written, never read back

; ----------------------------------------------------------------------
; bootLoadSectors - Read A consecutive sectors from track Y starting at sector X into the address set
; by bootSetDest, one page per sector.  On a drive error it saves the status byte in bootLoaderStatus,
; turns the border red and asks for the same sector again forever; a good sector turns the border
; black.  It never steps to another track: every caller re-arms the track itself.
; In:  A = sector count, X = first sector, Y = track, bootDestLo/bootDestHi = destination page
; Out: C = 0, X = sector after the last one read, Y = track, ldCount = 0, bootDestHi advanced by A
;     pages, I flag restored by the PHP/PLP pair, border colour left black
; Called from: bootMain $C19D (tracks 22-27), $C1D2 (track 33), $C1DB (track 32), $C1EB (track 28),
;     $C1FB (track 29).  The game keeps its own identical copy at $0804.
; ----------------------------------------------------------------------
bootLoadSectors:
        php                             ; C004  keep the caller's I flag - the transfer itself must run with interrupts off
        sei                             ; C005  no IRQ may land between two handshake edges
        sta     ldCount                 ; C006  A = how many sectors in a row to pull

; ----------------------------------------------------------------------
; Sector loop.  One call per sector; a bad status byte is not an error to report but simply a reason
; to ask again, which is why a damaged disk shows a red border and hangs here.
; ----------------------------------------------------------------------
bootReadSectorLoop:
        jsr     bootReadOneSector       ; C008  read one sector (Y = track, X = sector) into the page bootSetDest chose
        bcc     bootSectorOk            ; C00B  C = 0: the drive returned status 0, the 256 bytes are in memory
        sta     bootLoaderStatus        ; C00D  C = 1: A holds the DOS error code ($02 header not found, $03 no sync, $05 checksum, $08 write protect)
        lda     #$02                    ; C010  colour 2 = red
        sta     VIC_BORDER              ; C012  red border is the only sign of a read error the player ever gets
        ldy     ldTrack                 ; C015  same track again
        ldx     ldSector                ; C017  and the same sector
        jmp     bootReadSectorLoop      ; C019  retry forever - the loader has no give-up path


; ----------------------------------------------------------------------
; The sector arrived intact: black border, then step on one sector and one destination page.
; ----------------------------------------------------------------------
bootSectorOk:
        lda     #$00                    ; C01C  colour 0 = black
        sta     VIC_BORDER              ; C01E  border black again - this sector was good
        inc     ldSector                ; C021  next sector of this track
        inc     bootDestHi              ; C023  next destination page: one sector is exactly one page
        ldx     ldSector                ; C026  X = sector for the next call
        ldy     ldTrack                 ; C028  Y = track, unchanged throughout
        dec     ldCount                 ; C02A  one sector fewer to go
        bne     bootReadSectorLoop      ; C02C  keep pulling until the count runs out
        plp                             ; C02E  give the caller its I flag back
        clc                             ; C02F  C = 0: every sector was read
sub_C030:
        rts                             ; C030  back to bootMain: every sector of the run is in memory


; bootLoadSectorsFail - Dead 3-byte error exit PLP / SEC / RTS, emitted as .byte because nothing
; reaches it.  It is the counterpart of writeSectorsFail ($0840) in the game's copy of the loader,
; where a failed write does report back; here a read error simply retries.
; In:  none
; Out: C = 1 (unreachable)
; Called from: nowhere.
bootLoadSectorsFail:
        .byte   $28                     ; C031  PLP / SEC / RTS - the give-up exit the retry loop above never takes
        .byte   $38,$60                 ; C032  8`

; ----------------------------------------------------------------------
; bootSetDest - Set the address the next transfer goes to, by patching the two operand bytes
; bootDestLo and bootDestHi that bootReadSectorBody copies into the zero-page pointer.  Only the page
; ever advances, so the low byte is always 0 in practice.
; In:  X = destination low byte, Y = destination high byte
; Out: bootDestLo, bootDestHi
; Called from: bootMain $C194 ($0800), $C1C9 ($6F00), $C1E2 ($9300), $C1F2 ($FA00).
; ----------------------------------------------------------------------
bootSetDest:
        stx     bootDestLo              ; C034  low byte -> the operand the LDA at $C0D2 reads
bootSetDestHi:
        sty     bootDestHi              ; C037  high byte -> the page byte bootLoadSectors increments after every sector
        rts                             ; C03A  back to bootMain


; ----------------------------------------------------------------------
; bootExchangeByte - Full-duplex byte exchange with the drive over CIA2 port A, one bit per handshake,
; LSB first.  Our bit goes out on DATA OUT with CLK OUT as the strobe while the drive's bit comes in
; on DATA IN with CLK IN as its acknowledgement; the drive runs the mirror image of this in
; exchangeByteWithHost ($034B).  Used for the three command bytes of a read and, with a dummy byte, to
; collect the status afterwards.  Slow but self-timing: neither side has to count cycles.
; In:  A = byte to send
; Out: A = byte received (also in bootRecvShiftReg), Z reflects it, C = 0, X = 0, bootSendShiftReg = 0
; Called from: bootReadSectorBody $C09F (command $80), $C0A4 (track), $C0A9 (sector), $C0E8 (dummy
;     byte for the status).
; CIA2 port A ($DD00) as this file uses it:  bits 0-1 VIC bank select (%01 = bank 2, $8000-$BFFF), bit
; 2 RS-232 TXD, bit 3 serial ATN OUT, bit 4 serial CLK OUT, bit 5 serial DATA OUT, bit 6 serial CLK
; IN, bit 7 serial DATA IN.  The two outputs are inverted - writing a 1 pulls that line low - while
; the two inputs read the line as it is, 1 = high = released.  At the drive both directions read
; inverted, which is why 'set DATA OUT' below arrives there as a 1 bit.
; ----------------------------------------------------------------------
bootExchangeByte:
        sta     bootSendShiftReg        ; C03B  the byte to send goes into the transmit shift register
        ldx     #$08                    ; C03E  eight bits, one full handshake each

; ----------------------------------------------------------------------
; One bit per pass, in four phases: take the drive's bit, pull CLK to say we have it, put our own bit
; on DATA and release CLK, then wait for the drive's CLK acknowledgement and toggle DATA back.
; ----------------------------------------------------------------------
bootExchangeBit:
        bit     CIA2_PRA                ; C040  V = bit 6 = CLK IN
        bvc     bootExchangeBit         ; C043  wait for the drive to release CLK ($0357): its bit is now sitting on DATA
        lda     CIA2_PRA                ; C045  read the port as it stands
        and     #$DF                    ; C048  clear bit 5 = DATA OUT, so we stop driving DATA and can see what the drive drives
        sta     CIA2_PRA                ; C04A  release the line
L_C04D:
        lda     CIA2_PRA                ; C04D  sample the port with the drive's bit on DATA IN
        cmp     #$80                    ; C050  C = bit 7 (DATA IN): set means the DATA line is high
        ror     bootRecvShiftReg        ; C052  roll it into the top of the receive register; after eight rolls the first bit sits in bit 0 - LSB first
        ora     #$10                    ; C055  set bit 4 = CLK OUT, which pulls the CLK line low
        sta     CIA2_PRA                ; C057  CLK low means 'bit taken'; the drive spins on exactly this at $035A
        and     #$BF                    ; C05A  clear bit 6 in the copy: CLK IN reads 0 now that we hold CLK low, so this is the port we expect
bootWaitDriveAck:
        cmp     CIA2_PRA                ; C05C  compare the expected port state against the live one
        beq     bootWaitDriveAck        ; C05F  spin until something moves - the drive toggles its DATA OUT at $0366 to acknowledge

; ----------------------------------------------------------------------
; Our own bit now.  The drive samples DATA while we hold CLK low and reads a low line as a 1, so a 1
; bit means pulling DATA down.
; ----------------------------------------------------------------------
        lsr     bootSendShiftReg        ; C061  shift the next outgoing bit out of the transmit register, LSB first
        lda     #$00                    ; C064  bit = 0: leave DATA OUT clear, the line stays high and the drive samples a 0
        bcc     bootDriveDataBit        ; C066  outgoing bit was 0? then $00 is the value to merge in
        lda     #$20                    ; C068  bit = 1: set bit 5 = DATA OUT, pulling DATA low - a low line reads as 1 at the drive ($0375)
bootDriveDataBit:
        ora     CIA2_PRA                ; C06A  merge the data bit into the live port state, CLK OUT still held low
        sta     CIA2_PRA                ; C06D  put our bit on the wire
        and     #$EF                    ; C070  clear bit 4 = CLK OUT
        sta     CIA2_PRA                ; C072  releasing CLK says the bit is valid; the drive waits for that at $036B
bootWaitClkAck:
        bit     CIA2_PRA                ; C075  V = CLK IN once more
        bvs     bootWaitClkAck          ; C078  wait for the drive to pull CLK low ($037F) - it has taken our bit
bootToggleDataAck:
        eor     #$20                    ; C07A  toggle our DATA OUT bit...
        sta     CIA2_PRA                ; C07C  ...which is the acknowledgement the drive spins on at $0382
L_C07F:
        dex                             ; C07F  one bit done
        bne     bootExchangeBit         ; C080  round again for all eight
        lda     bootRecvShiftReg        ; C082  A = the byte the drive sent back; Z is set if it was 0 (status ok)
        clc                             ; C085  C = 0: only bootReadSectorBody turns a non-zero status into an error flag
        rts                             ; C086  back to bootReadSectorBody with the drive's answer in A

bootSendShiftReg:
        .byte   $00                     ; C087  bootSendShiftReg: the byte being sent, shifted right one bit per handshake
bootRecvShiftReg:
        .byte   $BE                     ; C088  bootRecvShiftReg: the byte being assembled, rolled in from the top ($BE is leftover); $C089 is unused
        .byte   $00                     ; C089  .

; ----------------------------------------------------------------------
; bootReadOneSector - Wrapper round the protocol body: remember track and sector, stop CIA1 timer A so
; nothing can interrupt the cycle-counted burst, run the transfer and start the timer again.  The
; carry from bootReadSectorBody survives the two LDX/STX pairs and is what the caller tests.
; In:  Y = track, X = sector, bootDestLo/bootDestHi = destination
; Out: C = 1 and A = DOS status byte on error, C = 0 on success; ldTrack/ldSector set; I = 1; CIA1_CRA
;     = 1
; Called from: bootLoadSectors $C008, once per sector.
; ----------------------------------------------------------------------
bootReadOneSector:
        sei                             ; C08A  the transfer is timed by eye against the raster - nothing may interrupt it
        sty     ldTrack                 ; C08B  keep the track for the retry path in bootLoadSectors
        stx     ldSector                ; C08D  and the sector
        ldx     #$00                    ; C08F  CRA = 0
        stx     CIA1_CRA                ; C091  stop CIA1 timer A: its interrupt is what normally fires 60 times a second
        jsr     bootReadSectorBody      ; C094  run the protocol; comes back with C = 1 and A = status on any DOS error
sub_C097:
        ldx     #$01                    ; C097  CRA = 1: timer A running again, continuous mode
sub_C099:
        stx     CIA1_CRA                ; C099  LDX/STX touch no flags, so the error carry reaches the caller intact
        rts                             ; C09C  back to bootLoadSectors, error carry intact


; ----------------------------------------------------------------------
; bootReadSectorBody - The protocol body of one sector read.  Sends the three command bytes
; $80/track/sector, prepares the machine for the burst (sprites off, VIC bank bits latched, DATA held
; low, raster threshold patched into the receive loop), waits for the drive to announce the sector,
; receives 256 bytes and finally exchanges a dummy byte to collect the status.  The drive decrypts the
; sector before sending it, so what arrives is plaintext.
; In:  ldTrack, ldSector, bootDestLo/bootDestHi, VIC_CTRL1 (its y-scroll decides where the bad lines
;     are)
; Out: C = 0 and Z = 1 on success, C = 1 and A = DOS status on error; 256 bytes at the destination;
;     bootSavedSprites, ldCia2Bits, ldDestPtr and the SBC operand at $C0FE updated
; Called from: bootReadOneSector $C094.
; ----------------------------------------------------------------------
bootReadSectorBody:
        lda     #$80                    ; C09D  $80 = read a sector; the drive takes anything but $C0 (reset) and $60 (write) as a read
        jsr     bootExchangeByte        ; C09F  send it - the drive hands back the $01 it loaded at $0569, which nobody looks at
        lda     ldTrack                 ; C0A2  second command byte
        jsr     bootExchangeByte        ; C0A4  the track ($032D at the drive)
        lda     ldSector                ; C0A7  third command byte
        jsr     bootExchangeByte        ; C0A9  the sector ($0332); the drive now hunts for that header and reads it

; ----------------------------------------------------------------------
; The drive needs a few milliseconds to find the sector - use them to get the C64 into a state where
; 256 bytes can be taken at a fixed number of cycles each.
; ----------------------------------------------------------------------
        lda     VIC_SPR_ENA             ; C0AC  $D015, the sprite enable mask
        sta     bootSavedSprites        ; C0AF  save it: sprite DMA has to be off while the bytes come in
        lda     CIA2_PRA                ; C0B2  the port again
        and     #$03                    ; C0B5  bits 0-1 only = the VIC bank select, the only bits that must be preserved
        sta     ldCia2Bits              ; C0B7  the receive loop rewrites these on every strobe and EORs them back out of each sample
D_C0B9:
        ora     #$20                    ; C0B9  plus bit 5 = DATA OUT: hold DATA low, which tells the drive 'not ready for a byte'
D_C0BB:
        sta     CIA2_PRA                ; C0BB  the same write clears bits 2-4, so TXD, ATN and CLK OUT are all released
        lda     VIC_CTRL1               ; C0BE  $D011
        and     #$07                    ; C0C1  bits 0-2 = y-scroll, which is what decides which raster lines are bad lines
        adc     #$2F                    ; C0C3  + $2F; $2F mod 8 = 7, so the threshold is congruent to yscroll - 1.  C is 0 here (the CLC ending bootExchangeByte), so this really is a plain add
        sta     L_C0FD+1                ; C0C5  patch the SBC operand at $C0FE - the receive loop now knows which line to keep clear of

; ----------------------------------------------------------------------
; Handshake: the drive pulls CLK low when the sector is in its buffer, decrypted and ready to go.
; ----------------------------------------------------------------------
bootWaitSectorReady:
        bit     CIA2_PRA                ; C0C8  V = CLK IN
L_C0CB:
        bvs     bootWaitSectorReady     ; C0CB  wait for CLK to go low - the drive's 'sector ready' at $044D
        lda     #$00                    ; C0CD  no sprites at all
        sta     VIC_SPR_ENA             ; C0CF  sprite DMA would steal cycles and slide every sample point
        lda     bootDestLo              ; C0D2  the destination bootSetDest patched in...
        sta     ldDestPtr               ; C0D5  ...into the zero-page pointer the receive loop stores through
sub_C0D7:
        lda     bootDestHi              ; C0D7  high byte, bumped one page per sector by bootLoadSectors
        sta     ldDestPtrHi             ; C0DA  ldDestPtr now points at this sector's page
        jsr     bootReceiveSectorData   ; C0DC  take the 256 bytes, two bits at a time
        jmp     bootReadRestoreSprites  ; C0DF  jump to the next instruction: the fossil of a longer error path


; ----------------------------------------------------------------------
; Put the machine back and collect the drive's verdict.
; ----------------------------------------------------------------------
bootReadRestoreSprites:
        lda     bootSavedSprites        ; C0E2  the sprite mask from before the burst
        sta     VIC_SPR_ENA             ; C0E5  restore it (nothing is enabled during boot, but the game's copy of this code needs it)
sub_C0E8:
        jsr     bootExchangeByte        ; C0E8  exchange a dummy byte - whatever is in A - to receive the status the drive sends at $0391
        beq     bootReadSectorExit      ; C0EB  status 0 = no error: return with the C = 0 that bootExchangeByte left
        sec                             ; C0ED  any DOS error code: C = 1 and bootLoadSectors will ask for the sector again
bootReadSectorExit:
        rts                             ; C0EE  back to bootReadOneSector


; ----------------------------------------------------------------------
; bootReceiveSectorData - The timing-critical half of the loader, identical to receiveSectorData
; ($0950) in the game.  For each of 256 bytes it releases DATA to ask for one, then reads CIA2 port A
; four times at fixed cycle offsets; each read carries two data bits on CLK IN and DATA IN, LSB pair
; first, and the four pairs are folded together with LSR LSR / EOR.  The drive answers with four port
; writes ($046E, $0475, $047D, $0484) and never handshakes inside a byte, so the NOPs and the 'bit
; loaderTimingPad' here are load-bearing padding, not filler.  Before each byte it also refuses to
; start on the raster line just before a bad line, because the VIC would steal 40-43 cycles in the
; middle of the byte.
; In:  ldCia2Bits (VIC bank bits), ldDestPtr/ldDestPtrHi, the raster threshold patched into $C0FE
; Out: 256 bytes at (ldDestPtr), Y = 0, CIA2 port A left with DATA OUT set (DATA held low)
; Called from: bootReadSectorBody $C0DC.
; ----------------------------------------------------------------------
bootReceiveSectorData:
        bit     CIA2_PRA                ; C0EF  V = CLK IN
        bvc     bootReceiveSectorData   ; C0F2  wait for the drive to release CLK ($0466): it is in its per-byte loop now
        ldy     #$00                    ; C0F4  256 bytes, indexed 0..255

; ----------------------------------------------------------------------
; One pass per byte.  First find a raster line the byte will fit on.
; ----------------------------------------------------------------------
bootReceiveByteLoop:
        sec                             ; C0F6  C = 1 so the SBC below subtracts the threshold exactly
        ldx     ldCia2Bits              ; C0F7  X = the VIC bank bits alone: CLK OUT, DATA OUT, ATN and TXD all released
        nop                             ; C0F9  padding
bootWaitSafeRaster:
        lda     VIC_RASTER              ; C0FA  $D012 = the low eight bits of the current raster line
L_C0FD:
        sbc     #$32                    ; C0FD  operand patched at $C0C5 to yscroll + $2F
L_C0FF:
        bcc     bootRequestByte         ; C0FF  line is above the display window - no bad line can happen here, go ahead
        and     #$07                    ; C101  distance from a bad line, modulo 8
        beq     bootWaitSafeRaster      ; C103  0 means this is the line right before a bad line: wait, the byte would not fit in it

; ----------------------------------------------------------------------
; The byte itself.  Releasing DATA is the request; the drive answers with four port writes and the
; four reads below are spaced to land on them.  Each sample has the data pair in bits 7-6 and the VIC
; bank bits in 1-0; two LSRs per step drop the bank bits and slide the pairs into place, and the EOR
; of ldCia2Bits at $C123 cancels in advance the bank bits that ride in on the last sample.
; ----------------------------------------------------------------------
bootRequestByte:
        stx     CIA2_PRA                ; C105  release DATA = 'send the next byte'; the drive's $0469 loop is waiting for exactly this
        txa                             ; C108  the same port value...
        ora     #$20                    ; C109  ...plus DATA OUT...
        tax                             ; C10B  ...kept in X ready for the 'byte taken' write at $C128
        nop                             ; C10C  padding: the four reads below have to line up with the drive's four writes
        nop                             ; C10D  padding
        bit     loaderTimingPad         ; C10E  BIT $80 - three cycles of delay, nothing is tested
        lda     CIA2_PRA                ; C110  sample 1 (drive write at $046E): data bits 1,0 in bits 7,6
        lsr     a                       ; C113  slide the pair down...
        lsr     a                       ; C114  ...to bits 5,4; the bank bits in 1,0 fall off the end
        nop                             ; C115  padding
        eor     CIA2_PRA                ; C116  sample 2 ($0475): data bits 3,2 arrive in bits 7,6
        lsr     a                       ; C119  slide both pairs down...
        lsr     a                       ; C11A  ...so pairs 2 and 1 sit in bits 5-4 and 3-2
        nop                             ; C11B  padding
        nop                             ; C11C  padding
        nop                             ; C11D  padding
        eor     CIA2_PRA                ; C11E  sample 3 ($047D): data bits 5,4
sub_C121:
        lsr     a                       ; C121  slide the three pairs down...
        lsr     a                       ; C122  ...into bits 5-0
        eor     ldCia2Bits              ; C123  pre-cancel the bank bits that the last sample will bring in on bits 1-0
        eor     CIA2_PRA                ; C125  sample 4 ($0484): data bits 7,6 - A is now the finished byte
        stx     CIA2_PRA                ; C128  pull DATA low again: 'byte taken', the drive may start the next one
sub_C12B:
        nop                             ; C12B  padding
        sta     (ldDestPtr),y           ; C12C  store it through the pointer bootReadSectorBody set up
        iny                             ; C12E  next byte
        bne     bootReceiveByteLoop     ; C12F  all 256 of them
        rts                             ; C131  back to bootReadSectorBody: 256 bytes delivered


; strBlockExecuteCopy: text, 13 bytes. Second, unused copy of the 0-terminated DOS command 'B-E 2 0 1
; 17' (the labels sub_C133/sub_C137 come from runtime-overlay callers, not from this text)
strBlockExecuteCopy:
        .byte   "B"                     ; C132  unused duplicate of the DOS command at $C35F; the live copy is the one bootSendDosCommand reads
sub_C133:
        .byte   $2D,$45,$20,$32         ; C133  -E 2
sub_C137:
        .byte   $20,$30,$20,$31,$20,$31,$37,$00; C137   0 1 17.

; strBufferChannelCopy: text, 2 bytes. Unused copy of the 0-terminated buffer-channel filename '#'
strBufferChannelCopy:
        .byte   "#",$00                 ; C13F  unused duplicate of the buffer-channel filename at $C36C
bootDestLo:
        .byte   $00                     ; C141  bootDestLo: destination low byte, read by the LDA at $C0D2
bootDestHi:
        .byte   $00                     ; C142  bootDestHi: destination page, incremented after every good sector

; bootUnusedC143: unknown, 1 bytes. Zero byte between bootDestHi and bootSavedSprites, unused
bootUnusedC143:
        .byte   $00                     ; C143  unused
bootSavedSprites:
        .byte   $00                     ; C144  bootSavedSprites: $D015 parked here while a sector comes in

; ----------------------------------------------------------------------
; bootMain - The whole boot sequence.  Blanks the screen, starts SID voice 3 as a noise source, shows
; the title picture, arms the two self-modifying patches that rebuild the stack, hushes any modem,
; initialises the drive and block-executes the drive-side fast loader, then throws the KERNAL out of
; the address space and pulls the entire game off the disk while the picture is up. Load map: tracks
; 22-27 s0-17 -> $0800-$73FF (the main program); $6F00-$72FF copied down to $0400-$07FF; track 33
; s0-16 -> $6F00 and track 32 s8-15 -> $8000 (the comcen-screen overlay); track 28 s0-12 -> $9300;
; track 29 s11-15 -> $FA00, then moved up $1B8 bytes so it ends at $FFD1.
; In:  the game disk in device 8, the title picture at $9800/$9C00/$A000 as loaded with the file
;     'load'
; Out: the game's memory image ($0400-$87FF, $9300-$9FFF, $FBB8-$FFD1), randomSeed at $09C3, $01 =
;     $35, $00 = $2F, $FFFA/$FFFE = rtiVector, CIA and VIC interrupts masked, the title picture on
;     screen
; Called from: bootEntry $C000 (so, eaLoadGame $02D2).  It never returns there: see bootDone.
; Blank the screen and start the noise oscillator, whose output is sampled at the very end as the
; game's random seed.
; ----------------------------------------------------------------------
bootMain:
        cld                             ; C145  binary arithmetic from here on
        ldy     #$00                    ; C146  0 into every register below
        sty     VIC_SPR_ENA             ; C148  $D015 = 0: no sprites
        sty     VIC_BORDER              ; C14B  border black
        sty     VIC_BG0                 ; C14E  background black
        sty     VIC_CTRL1               ; C151  $D011 = 0 clears DEN: the display is blanked while the picture is copied into place
        lda     #$80                    ; C154  bit 7 = noise waveform, gate off
        sta     SID_V3_CTRL             ; C156  voice 3 free-runs as a noise generator, so $D41B reads as a random byte
D_C159:
        jsr     bootShowTitlePicture    ; C159  copy the picture in and switch the VIC to it; this call also arms both stack patches

; ----------------------------------------------------------------------
; $FF serves three purposes: both halves of the SID frequency, and - two DEXes later - the stack
; pointer the patched instruction at $C168 installs.
; ----------------------------------------------------------------------
        ldx     #$FF                    ; C15C  X = $FF
        stx     SID_V3_FREQ_HI          ; C15E  voice 3 frequency high byte
L_C161:
        stx     SID_V3_FREQ_LO          ; C161  and low byte: $FFFF, the fastest the noise generator will run
        dex                             ; C164  X = $FE
        dex                             ; C165  X = $FD
        ldy     #$00                    ; C166  Y = 0 for the copy loop further down
bootPatchedTxs:
        sta     titleBitmapAAEA,y       ; C168  patched from STA $AAEA,Y ($99) to TXS ($9A) by bootShowTitlePicture ($C242).  S = $FD, which throws away eaLoadGame's return address and leaves $01FD/$01FE free for the fake one; the two orphaned operand bytes $EA $AA run harmlessly as NOP / TAX

; ----------------------------------------------------------------------
; Talk to the outside world while the KERNAL is still there: modem, drive, and the DOS command that
; installs the fast loader in the 1541.
; ----------------------------------------------------------------------
L_C16B:
        jsr     bootModemInit           ; C16B  hush any attached modem: echo off, auto-answer off
        jsr     bootDriveInitialize     ; C16E  OPEN 15,8,15,'I0' - initialise the drive
        jsr     bootBlockExecute        ; C171  B-E track 1 sector 17: the drive loads that sector into a buffer and runs it, which pulls the fast loader into $0300-$05FF in the drive and jumps to it

; ----------------------------------------------------------------------
; From here on there is no KERNAL and no BASIC: the loader owns the machine, and everything below
; $FFFA is fair game as a destination.
; ----------------------------------------------------------------------
L_C174:
        jsr     bootDisableIrqs         ; C174  mask every interrupt source before the ROMs go away
        lda     #$35                    ; C177  $35
        sta     CPU_PORT                ; C179  $01 = $35: I/O still visible, BASIC and KERNAL ROM banked out so RAM under them can be filled
        lda     #$2F                    ; C17B  $2F
sub_C17D:
        sta     CPU_DDR                 ; C17D  $00 = $2F: the usual processor-port direction bits
        lda     #$29                    ; C17F  low byte of rtiVector
        sta     nmiVector               ; C181  $FFFA is RAM now, so the NMI vector has to be planted by hand
        sta     irqVector               ; C184  and $FFFE, the IRQ/BRK vector
        lda     #$C2                    ; C187  high byte of $C229
        sta     nmiVectorHi             ; C189  NMI vector complete
        sta     irqVectorHi             ; C18C  IRQ vector complete: any interrupt from here just RTIs
L_C18F:
        cli                             ; C18F  interrupts may run again - they do nothing

; ----------------------------------------------------------------------
; The main program: tracks 22 to 27, all 18 sectors of each, straight into $0800 upwards.  The
; destination page carries on by itself from one call to the next.
; ----------------------------------------------------------------------
        ldx     #$00                    ; C190  destination low byte
        ldy     #$08                    ; C192  destination page $08
        jsr     bootSetDest             ; C194  first byte of the game lands at $0800
bootLoadTrackLoop:
        ldy     #$16                    ; C197  track 22 ($16); this operand is what the INC at $C1A0 walks forward
        ldx     #$00                    ; C199  from sector 0
        lda     #$12                    ; C19B  18 sectors - a whole track
        jsr     bootLoadSectors         ; C19D  18 more pages of the game
        inc     bootLoadTrackLoop+1     ; C1A0  self-modifying: aim the LDY above at the next track
        lda     bootLoadTrackLoop+1     ; C1A3  read it back
        cmp     #$1C                    ; C1A6  past track 27 ($1B)?
        bcc     bootLoadTrackLoop       ; C1A8  six tracks, 108 sectors: $0800-$73FF

; ----------------------------------------------------------------------
; Build the fake return address.  bootShowTitlePicture has already put $04 at $01FE; the patched
; instruction below pushes $60 - the RTS opcode, read out of the code at bootDone - to $01FD.  The RTS
; at $C218 therefore pops $0460, adds one and continues at $0461, the memory-checksum routine that
; arrives with the $0400 block a few instructions later.
; ----------------------------------------------------------------------
        lda     bootDone                ; C1AA  read the byte $60 out of bootDone's own RTS instruction
        ldy     #$00                    ; C1AD  Y = 0 for the copy loop
        ldx     #$04                    ; C1AF  four pages to copy
bootPatchedPha:
        eor     #$8A                    ; C1B1  patched from EOR #$8A ($49) to PHA ($48) by bootShowTitlePicture ($C283): pushes that $60 to $01FD.  The orphaned operand byte $8A then runs as TXA

; ----------------------------------------------------------------------
; Rescue the tail of track 27.  Sectors 13-16 of that track landed at $6F00-$72FF, but they belong at
; $0400-$07FF: the manual-check answer table, the checksum routine this loader returns into, and the
; status message strings.  $6F00 is then free to receive an overlay.
; ----------------------------------------------------------------------
bootCopyTo0400Loop:
        lda     drawDroneHeadingPanel,y ; C1B3  $6F00 = where track 27 sectors 13-16 ended up (the label belongs to the later overlay at $6F00)  (also mapGenerator6F00:laneColumnTable)
bootCopyTo0400Store:
        sta     checksumXorConstant,y   ; C1B6  $0400 upwards  (also trainerPlaybook0200:play3Col12)
        iny                             ; C1B9  next byte
        bne     bootCopyTo0400Loop      ; C1BA  a page at a time
        inc     bootCopyTo0400Loop+2    ; C1BC  self-modifying source page
L_C1BF:
        inc     bootCopyTo0400Store+2   ; C1BF  and destination page
        dex                             ; C1C2  four pages
        bne     bootCopyTo0400Loop      ; C1C3  $6F00-$72FF -> $0400-$07FF

; ----------------------------------------------------------------------
; Overlay variant B into $6F00-$87FF: the comcen missile and drone screens.  The game swaps the map
; generator (tracks 31 + 32 s0-7) in over the top of it later on.
; ----------------------------------------------------------------------
        ldx     #$00                    ; C1C5  destination low byte
        ldy     #$6F                    ; C1C7  destination page $6F
        jsr     bootSetDest             ; C1C9  next transfer starts at $6F00
        ldy     #$21                    ; C1CC  track 33
        lda     #$11                    ; C1CE  17 sectors
        ldx     #$00                    ; C1D0  from sector 0
        jsr     bootLoadSectors         ; C1D2  -> $6F00-$7FFF
        ldy     #$20                    ; C1D5  track 32
        ldx     #$08                    ; C1D7  from sector 8
        lda     #$08                    ; C1D9  8 sectors - the second half of that track
        jsr     bootLoadSectors         ; C1DB  -> $8000-$87FF, the tail of the same overlay

; ----------------------------------------------------------------------
; Tables and graphics.
; ----------------------------------------------------------------------
        ldx     #$00                    ; C1DE  destination low byte
        ldy     #$93                    ; C1E0  destination page $93
L_C1E2:
        jsr     bootSetDest             ; C1E2  next transfer starts at $9300
        ldy     #$1C                    ; C1E5  track 28
        ldx     #$00                    ; C1E7  from sector 0
        lda     #$0D                    ; C1E9  13 sectors
        jsr     bootLoadSectors         ; C1EB  -> $9300-$9FFF

; ----------------------------------------------------------------------
; The resident high code, loaded low and then shuffled up.  It has to end at $FFD1, just below the
; hardware vectors, but it cannot be loaded there directly because the last sector would run over
; $FFFA and destroy the vectors that were planted at $C181.
; ----------------------------------------------------------------------
        ldx     #$00                    ; C1EE  destination low byte
sub_C1F0:
        ldy     #$FA                    ; C1F0  destination page $FA - RAM under the KERNAL, reachable because $01 = $35
        jsr     bootSetDest             ; C1F2  next transfer starts at $FA00
        ldy     #$1D                    ; C1F5  track 29
        ldx     #$0B                    ; C1F7  from sector 11
        lda     #$05                    ; C1F9  5 sectors
        jsr     bootLoadSectors         ; C1FB  -> $FA00-$FEFF; X comes back as $10, which the move loop below relies on

; ----------------------------------------------------------------------
; Move the block up by $1B8 bytes, top page first so nothing overwrites itself.
; ----------------------------------------------------------------------
        ldy     #$00                    ; C1FE  Y = 0
bootMoveHighLoop:
        lda     D_FD1A,y                ; C200  top source page; $FD1A + $1B8 = $FED2
bootMoveHighStore:
        sta     D_FED2,y                ; C203  the same byte $1B8 higher up
        iny                             ; C206  next byte
        bne     bootMoveHighLoop        ; C207  a page at a time
        dec     bootMoveHighLoop+2      ; C209  self-modifying: walk both pointers downwards...
        dec     bootMoveHighStore+2     ; C20C  ...so the copy never catches its own tail
        dex                             ; C20F  X is still the $10 bootLoadSectors left behind: 16 pages
        bne     bootMoveHighLoop        ; C210  $FA00-$FEFF ends up at $FBB8-$FFD1; the pages below $FA00 drag garbage into $EFD2-$FBB7, which the game overwrites long before it looks there

; ----------------------------------------------------------------------
; One last random byte, then the RTS that is not a return.
; ----------------------------------------------------------------------
        lda     SID_OSC3_RANDOM         ; C212  $D41B = voice 3's oscillator output, free-running noise since $C156
        sta     D_09C3                  ; C215  seed the game's random number generator with it

; ----------------------------------------------------------------------
; bootDone - An RTS that is really a jump.  The stack was rebuilt while the loader ran: $01FE = $04
; (stored by bootShowTitlePicture at $C26C) and $01FD = $60 (pushed by the patched PHA at $C1B1, the
; value being this very RTS opcode read back as data at $C1AA).  So the RTS pops $0460, adds one and
; carries on at $0461 - the memory checksum routine in the $0400 block that was copied down from $6F00
; - which ends with JMP $0800.  eaLoadGame's own JMP $0800 at $02D5 is never reached.
; In:  S = $FC, $01FD/$01FE = $60/$04
; Out: execution continues at $0461 with S = $FE
; Called from: falls in from bootMain.
; ----------------------------------------------------------------------
bootDone:
        rts                             ; C218  pops $0460 and continues at $0461, not at $02D5


; strGameChar: text, 9 bytes. 0-terminated 'GAMECHAR' - leftover from the shared EA loader, not
; referenced
strGameChar:
        .byte   "GAMECH"                ; C219  leftover strings from the shared EA loader ('GAMECHAR', 'HMEM'); unused here
L_C21F:
        .byte   $41,$52,$00             ; C21F  AR.

; strHmem: text, 7 bytes. $00, 'HMEM', $00, $00 - leftover from the shared EA loader, not referenced
strHmem:
        .byte   $00,"HMEM"              ; C222
L_C227:
        .byte   $00,$00                 ; C227  ..

; ----------------------------------------------------------------------
; rtiVector - The entire interrupt handler while the loader owns the machine.  $FFFA and $FFFE both
; point here from $C181-$C18E, so a stray NMI (RESTORE key) or IRQ costs a few cycles and nothing
; else.
; In:  none
; Out: none
; Called from: the NMI and IRQ vectors $FFFA/$FFFE, set by bootMain.
; ----------------------------------------------------------------------
rtiVector:
        rti                             ; C229  the whole interrupt service routine


; ----------------------------------------------------------------------
; bootDisableIrqs - Mask and acknowledge every interrupt source in the machine: both CIA interrupt
; control registers and the VIC interrupt mask.  Leaves the I flag set; the callers CLI again once the
; vectors are safe.
; In:  none
; Out: CIA1_ICR and CIA2_ICR masked and their latches cleared, VIC_IRQ_MASK = 0, I = 1
; Called from: bootMain $C174 (before the ROMs are banked out) and bootShowTitlePicture $C23F.
; ----------------------------------------------------------------------
bootDisableIrqs:
        sei                             ; C22A  no interrupts while the interrupt hardware is being turned off
D_C22B:
        lda     #$7F                    ; C22B  bit 7 clear = clear the mask bits named in bits 0-4, that is, all five sources
        sta     CIA1_ICR                ; C22D  CIA1: no more timer A jiffy IRQ, no keyboard or timer B IRQ
        sta     CIA2_ICR                ; C230  CIA2: no more NMI from its timers, its FLAG line or the RS-232 code
        lda     CIA1_ICR                ; C233  reading an ICR clears whatever flag has already latched in it
        lda     CIA2_ICR                ; C236  and releases a pending CIA2 NMI so it cannot fire the moment I is cleared
        lda     #$00                    ; C239  0
D_C23B:
        sta     VIC_IRQ_MASK            ; C23B  $D01A = 0: no raster and no sprite-collision interrupt either
        rts                             ; C23E  the machine can no longer interrupt itself


; ----------------------------------------------------------------------
; bootShowTitlePicture - Puts the title picture on the screen, and on the way arms both halves of the
; trick ending.  The picture came in with the file 'load': colour nibbles at $9800, video matrix at
; $9C00 and the bitmap already in place at $A000.  Colour RAM has to be copied to $D800 because it is
; not part of a VIC bank, and the video matrix has to move to $8C00 because the VIC is switched to
; bank 2 ($8000-$BFFF), where it can see the bitmap at $A000.
; In:  title data at $9800 (colour), $9C00 (video matrix), $A000 (bitmap)
; Out: multicolour bitmap mode showing the picture; $D800-$DBFF and $8C00-$8FFF filled; $01FE = $04;
;     the code bytes at $C168 and $C1B1 patched; the four copy loops' operands left one page past
;     their start; I = 0
; Called from: bootMain $C159.
; ----------------------------------------------------------------------
bootShowTitlePicture:
        jsr     bootDisableIrqs         ; C23F  nothing may look at the screen while the VIC is being reprogrammed

; ----------------------------------------------------------------------
; Patch one of two: the instruction at $C168 becomes TXS, so bootMain can move the stack pointer
; without the byte TXS ever appearing in the loaded file.
; ----------------------------------------------------------------------
        inc     bootPatchedTxs          ; C242  $99 (STA abs,Y) + 1 = $9A (TXS)

; ----------------------------------------------------------------------
; Silence everything the VIC could do on its own: sprites parked and disabled, interrupt and collision
; registers cleared.
; ----------------------------------------------------------------------
        lda     #$00                    ; C245  0 into all of them
        ldy     #$10                    ; C247  $D000-$D010: eight sprite X/Y pairs plus the X most-significant-bit register
bootClearSpriteRegLoop:
        sta     VIC_SPR0_X,y            ; C249  park every sprite at 0,0
        dey                             ; C24C  backwards to $D000
        bpl     bootClearSpriteRegLoop  ; C24D  BPL, so index 0 is done too
        sta     VIC_SPR_ENA             ; C24F  $D015 = 0: no sprite enabled
        sta     VIC_SPR_EXP_Y           ; C252  $D017 = 0: no vertical expansion
        ldy     #$05                    ; C255  $D01A-$D01F
bootClearVicRegLoop:
        sta     VIC_IRQ_MASK,y          ; C257  IRQ mask, sprite priority, sprite multicolour, X expansion and both collision registers
        dey                             ; C25A  backwards
        bpl     bootClearVicRegLoop     ; C25B  including index 0

; ----------------------------------------------------------------------
; Hand the VIC bank 2, $8000-$BFFF, which is where the bitmap already sits.
; ----------------------------------------------------------------------
        lda     CIA2_DDRA               ; C25D  $DD02
        ora     #$03                    ; C260  bits 0-1 must be outputs or the bank select does nothing
        sta     CIA2_DDRA               ; C262  write the direction bits back
        lda     #$05                    ; C265  %00000101: bank bits %01 = bank 2 ($8000-$BFFF), bit 2 = RS-232 TXD idle high
        sta     CIA2_PRA                ; C267  ATN, CLK OUT and DATA OUT all released - the serial bus is idle

; ----------------------------------------------------------------------
; Patch two of three: $04, the high byte of the fake return address, goes straight into the stack
; page.  X = 4 then doubles as the page count of the colour copy.
; ----------------------------------------------------------------------
        ldx     #$04                    ; C26A  $04 = page of the fake return address $0460, and four pages to copy
        stx     D_01FE                  ; C26C  $01FE: the high byte the RTS at $C218 will pop
        ldy     #$00                    ; C26F  Y = 0
bootCopyColourLoop:
        lda     titleColorRam,y         ; C271  the picture's colour nibbles, loaded to $9800 with the rest of the file
bootCopyColourStore:
        sta     COLOR_RAM,y             ; C274  $D800: colour RAM lives outside the VIC bank, so it has to be copied here
        iny                             ; C277  next byte
        bne     bootCopyColourLoop      ; C278  a page at a time
        inc     bootCopyColourLoop+2    ; C27A  self-modifying source page
        inc     bootCopyColourStore+2   ; C27D  and destination page
L_C280:
        dex                             ; C280  four pages
        bne     bootCopyColourLoop      ; C281  $9800-$9BFF -> $D800-$DBFF; only the low nibble of each byte matters

; ----------------------------------------------------------------------
; Patch three: $C1B1 becomes PHA, the instruction that pushes the low byte of the fake return address.
; Then the video matrix, which must live inside the VIC bank.
; ----------------------------------------------------------------------
        dec     bootPatchedPha          ; C283  $49 (EOR #) - 1 = $48 (PHA)
        ldx     #$04                    ; C286  four more pages
bootCopyScreenLoop:
        lda     titleScreenRam,y        ; C288  the picture's video matrix (Y came back to 0 from the loop above)
bootCopyScreenStore:
        sta     D_8C00,y                ; C28B  $8C00 = the screen RAM the VIC reads at offset $0C00 of bank 2
        iny                             ; C28E  next byte
        bne     bootCopyScreenLoop      ; C28F  a page at a time
        inc     bootCopyScreenLoop+2    ; C291  self-modifying source page
        inc     bootCopyScreenStore+2   ; C294  and destination page
        dex                             ; C297  four pages
        bne     bootCopyScreenLoop      ; C298  $9C00-$9FFF -> $8C00-$8FFF

; ----------------------------------------------------------------------
; Turn the picture on.
; ----------------------------------------------------------------------
        lda     #$D8                    ; C29A  %11011000: multicolour on, 40 columns, x-scroll 0
        sta     VIC_CTRL2               ; C29C  $D016
        lda     #$38                    ; C29F  %0011 -> video matrix at bank + $0C00, %100 -> bitmap at bank + $2000
        sta     VIC_MEM_PTR             ; C2A1  $D018: with bank 2 that means screen $8C00 and bitmap $A000
        lda     #$3B                    ; C2A4  %00111011: bitmap mode, display enabled, 25 rows, y-scroll 3
        sta     VIC_CTRL1               ; C2A6  $D011 - the title picture appears
        cli                             ; C2A9  interrupts back on: the KERNAL calls that follow expect its IRQ to be running
L_C2AA:
        rts                             ; C2AA  back to bootMain with the picture up and both stack patches armed


; ----------------------------------------------------------------------
; bootDriveInitialize - OPEN 15,8,15,'I0', wait four frames and CLOSE 15, so the drive re-reads the
; BAM and forgets any state left over from the LOAD of the file 'load'.  Uses the KERNAL, which is
; still banked in at this point in the boot.
; In:  strInitDrive at $C2D8
; Out: drive initialised, logical file 15 closed again; A/X/Y clobbered
; Called from: bootMain $C16E.
; ----------------------------------------------------------------------
bootDriveInitialize:
        lda     #$02                    ; C2AB  filename length 2
        ldy     #$C2                    ; C2AD  name pointer high byte
        ldx     #$D8                    ; C2AF  and low byte: $C2D8 = 'I0'
        jsr     KERNAL_SETNAM           ; C2B1  SETNAM
        lda     #$0F                    ; C2B4  logical file 15
        tay                             ; C2B6  secondary address 15 = the drive's command channel
        ldx     #$08                    ; C2B7  device 8
        jsr     KERNAL_SETLFS           ; C2B9  SETLFS
        jsr     KERNAL_OPEN             ; C2BC  OPEN 15,8,15,'I0' - the drive re-reads the BAM and resets itself
        ldy     #$04                    ; C2BF  four frames
        jsr     bootWaitFrames          ; C2C1  give the drive time to finish before the channel is closed
        lda     #$0F                    ; C2C4  logical file 15
        jsr     KERNAL_CLOSE            ; C2C6  CLOSE - bootBlockExecute opens the command channel again by hand
        rts                             ; C2C9  back to bootMain


; ----------------------------------------------------------------------
; bootWaitFrames - Wait Y video frames by watching bit 7 of the raster register go high and then low
; again once per frame.  A pure delay: it needs no interrupt, which matters because the callers run
; with the CIAs in an odd state.
; In:  Y = number of frames (0 would mean 256)
; Out: Y = 0
; Called from: bootDriveInitialize $C2C1 (4 frames) and bootModemInit $C302 (20) and $C314 (60).
; ----------------------------------------------------------------------
bootWaitFrames:
        bit     VIC_RASTER              ; C2CA  N = bit 7 of $D012, set on raster lines $80-$FF
        bpl     bootWaitFrames          ; C2CD  wait for the beam to reach the lower half of the screen
bootWaitRasterWrap:
        bit     VIC_RASTER              ; C2CF  watch the same bit
        bmi     bootWaitRasterWrap      ; C2D2  and wait for it to wrap past $FF: one frame has gone by
        dey                             ; C2D4  one frame fewer
        bne     bootWaitFrames          ; C2D5  round again
        rts                             ; C2D7  back to the caller, Y = 0


; strInitDrive: text, 2 bytes. 'I0' drive initialise command (length passed explicitly to SETNAM)
strInitDrive:
        .byte   "I0"                    ; C2D8  'I' = initialise, '0' = drive 0 of what might be a dual unit; the length is passed to SETNAM, so this string needs no terminator

; ----------------------------------------------------------------------
; bootModemInit - Opens the KERNAL's RS-232 device at 300 baud, sends a carriage return and then the
; Hayes string 'ATE0S0=0' so that a modem sitting on the user port stops echoing and will not answer
; the phone during the load.  Harmless when no modem is connected.  MEMTOP is lowered to $9700 first,
; because OPENing device 2 makes the KERNAL carve its two 256-byte RS-232 buffers out of the top of
; memory - which would otherwise land inside the file that has just been loaded to $9800-$C3FF.
; In:  rs232Params at $C320, strHayesInit at $C322
; Out: KERNAL MEMTOP = $9700, logical file 2 opened and closed again, output channel back to the
;     screen
; Called from: bootMain $C16B.
; ----------------------------------------------------------------------
bootModemInit:
        clc                             ; C2DA  C = 0 selects 'set' rather than 'read' in the MEMTOP call
        ldy     #$97                    ; C2DB  high byte
        ldx     #$00                    ; C2DD  low byte: top of memory = $9700
        jsr     KERNAL_MEMTOP           ; C2DF  so OPEN puts the RS-232 buffers at $9500-$96FF, clear of the loaded file at $9800
        lda     #$02                    ; C2E2  two 'filename' bytes = the RS-232 control and command registers
        ldy     #$C3                    ; C2E4  pointer high byte
        ldx     #$20                    ; C2E6  and low byte: rs232Params
        jsr     KERNAL_SETNAM           ; C2E8  SETNAM
        lda     #$02                    ; C2EB  logical file 2
        tax                             ; C2ED  device 2 = the KERNAL's RS-232 pseudo device
        ldy     #$00                    ; C2EE  secondary address 0
        jsr     KERNAL_SETLFS           ; C2F0  SETLFS
        jsr     KERNAL_OPEN             ; C2F3  OPEN 2,2,0,CHR$(6)+CHR$(0): 300 baud, 8N1, 3-line handshake
        ldx     #$02                    ; C2F6  logical file 2
        jsr     KERNAL_CHKOUT           ; C2F8  everything printed from here goes to the modem
        lda     #$0D                    ; C2FB  carriage return
L_C2FD:
        jsr     KERNAL_CHROUT           ; C2FD  flush any half-typed command sitting in the modem's line buffer
        ldy     #$14                    ; C300  20 frames
        jsr     bootWaitFrames          ; C302  one character at 300 baud takes about two frames; give it room

; ----------------------------------------------------------------------
; Then the command itself, byte by byte until the 0 terminator.
; ----------------------------------------------------------------------
D_C305:
        ldy     #$00                    ; C305  start of the string
bootHayesSendLoop:
        lda     strHayesInit,y          ; C307  'ATE0S0=0' and a carriage return
        beq     L_C312                  ; C30A  0 ends the string
        jsr     KERNAL_CHROUT           ; C30C  out it goes
        iny                             ; C30F  next character
        bne     bootHayesSendLoop       ; C310  always taken - the terminator is what ends the loop
L_C312:
        ldy     #$3C                    ; C312  60 frames, about a second
        jsr     bootWaitFrames          ; C314  the whole string must be clocked out before the channel is closed
        jsr     KERNAL_CLRCHN           ; C317  output back to the screen
        lda     #$02                    ; C31A  logical file 2
L_C31C:
        jsr     KERNAL_CLOSE            ; C31C  CLOSE - the game installs its own software UART at $E000 later
        rts                             ; C31F  back to bootMain


; rs232Params: byteTable, 2 bytes. RS-232 control/command bytes for OPEN 2,2,0: $06 = 300 baud, 8 data
; bits, 1 stop bit; $00 = 3-line handshake, full duplex, no parity
rs232Params:
        .byte   $06,$00                 ; C320  ..  control byte $06 = 300 baud, 8 data bits, 1 stop bit; command byte $00 = 3-line handshake, full duplex, no parity

; strHayesInit: text, 10 bytes. 0-terminated 'ATE0S0=0' + CR sent to a modem (echo off, auto-answer
; off)
strHayesInit:
        .byte   "ATE0"                  ; C322  AT = attention, E0 = stop echoing what is sent
L_C326:
        .byte   $53                     ; C326  S0=0 = register 0 zero rings, that is, never auto-answer; then CR and the 0 terminator
        .byte   $30,$3D,$30,$0D,$00     ; C327  0=0..

; ----------------------------------------------------------------------
; bootBlockExecute - Installs the fast loader in the 1541.  It opens channel 2 on the drive with the
; filename '#', which makes the DOS hand that channel a free 256-byte buffer, and then sends 'B-E 2 0
; 1 17' on the command channel: block-execute channel 2, drive 0, track 1, sector 17.  The drive reads
; that sector into the buffer and jumps into it; the sector is the bootstrap that reads track 1
; sectors 18-20 to $0300-$05FF and jumps there (disassembly/drive/driveFastLoader.s).  From that
; moment the drive answers the two-bit protocol instead of the normal serial one.
; In:  strBufferChannel and strBlockExecute at $C35F, device number in zp $BA
; Out: the drive is running the fast loader; CPU_PORT restored; I = 1
; Called from: bootMain $C171.
; ----------------------------------------------------------------------
bootBlockExecute:
        sei                             ; C32C  the KERNAL's serial routines are bit-banged and cannot be interrupted
        lda     CPU_PORT                ; C32D  the current memory configuration
        sta     bootSavedCpuPort        ; C32F  keep it - bootSendDosCommand puts it back
        ora     #$03                    ; C332  bits 0-1 in = BASIC and KERNAL ROM visible, which the IEC routines need (they already are: $01 is still $37 here, so this is belt and braces)
        sta     CPU_PORT                ; C334  write it back
        lda     #$F2                    ; C336  secondary address $F0 + channel 2 = OPEN channel 2
        ldy     #$0D                    ; C338  offset $0D into strBlockExecute is strBufferChannel, the '#'
D_C33A:
        jsr     bootSendDosCommand      ; C33A  OPEN 2,8,2,'#': give channel 2 one of the drive's buffers

; ----------------------------------------------------------------------
; Second command, sent by falling straight into the same routine: the block-execute itself.
; ----------------------------------------------------------------------
D_C33D:
        lda     #$FF                    ; C33D  secondary address $F0 + channel 15 = OPEN on the command channel
        ldy     #$00                    ; C33F  offset 0 = 'B-E 2 0 1 17'; falls through into bootSendDosCommand

; ----------------------------------------------------------------------
; bootSendDosCommand - Sends one 0-terminated string to the drive as an OPEN on the given channel:
; LISTEN the current device, SECOND with the secondary address, CIOUT every byte up to the terminator,
; UNLSN.  Finishes by restoring the memory configuration bootBlockExecute saved.
; In:  A = secondary address byte ($F0 + channel), Y = offset into strBlockExecute, zp $BA = device
;     number, bootSavedCpuPort
; Out: the command has been sent and acted on; CPU_PORT restored; A/X/Y clobbered
; Called from: bootBlockExecute $C33A by JSR, and again by falling through from $C33F.
; ----------------------------------------------------------------------
bootSendDosCommand:
        pha                             ; C341  keep the secondary address across the LISTEN
        lda     magnifyEdgeMask         ; C342  zp $BA is the KERNAL's current device number, 8 here; the game's name for that byte is unrelated
        jsr     KERNAL_LISTEN           ; C344  make the drive listen
        pla                             ; C347  the secondary address back
        jsr     KERNAL_SECOND           ; C348  $F0 + channel means OPEN, so what follows is the filename or command text
bootDosCommandLoop:
        lda     strBlockExecute,y       ; C34B  one byte of the string
        beq     bootDosCommandEnd       ; C34E  0 ends it
        jsr     KERNAL_CIOUT            ; C350  send it on the serial bus
        iny                             ; C353  next byte
        bne     bootDosCommandLoop      ; C354  always taken
bootDosCommandEnd:
        jsr     KERNAL_UNLSN            ; C356  UNLISTEN closes the OPEN, which is when the drive actually acts on the command
        lda     bootSavedCpuPort        ; C359  the memory configuration from before
        sta     CPU_PORT                ; C35C  put it back ($37 at this point, so the ROMs stay in for the next command)
        rts                             ; C35E  back to bootBlockExecute, or to bootMain when this was the fall-through call


; strBlockExecute: text, 13 bytes. 0-terminated DOS command 'B-E 2 0 1 17' (block-execute channel 2,
; drive 0, track 1, sector 17) sent to channel 15
strBlockExecute:
        .byte   "B-E 2 0 "              ; C35F  block-execute: channel 2, drive 0, track 1, sector 17
sub_C367:
        .byte   $31                     ; C367  sector 17 is the drive bootstrap that pulls sectors 18-20 into $0300-$05FF and jumps there
        .byte   $20,$31,$37,$00         ; C368   17.

; strBufferChannel: text, 2 bytes. 0-terminated '#' used as the filename when opening buffer channel 2
; (reached as strBlockExecute+$0D)
strBufferChannel:
        .byte   "#",$00                 ; C36C  filename '#' with no number = give this channel any free buffer

; bootPaddingC36E: unknown, 3 bytes. Three zero bytes, padding
bootPaddingC36E:
        .byte   $00                     ; C36E  padding
        .byte   $00,$00                 ; C36F  ..
bootSavedCpuPort:
        .byte   $00                     ; C371  bootSavedCpuPort: the $01 value bootBlockExecute found, $37 with both ROMs in

; bootStrayC372: unknown, 1 bytes. Single byte $13, probably the tail of an instruction of the
; leftover code that preceded it in the original loader
bootStrayC372:
        .byte   $13                     ; C372  stray byte, probably the tail of an instruction of the leftover code that used to sit here

; leftoverLookupC373 - Unreachable leftover from the shared EA loader (listed as .byte): SEC; SBC
; #$AC; if A < $10 then X = A, $BE6D = $BE68 = $BCC8,X; RTS. Targets do not exist in this build.
; In:  A
; Out: $BE6D, $BE68 (dead)
; (confidence: low)
leftoverLookupC373:
        .byte   $38                     ; C373  unreachable leftovers of the shared EA loader below: they reference $BE65-$BE6D and $BCC8, which do not exist in this build
        .byte   $E9,$AC,$90,$0E,$C9,$10,$B0,$0A; C374  ........
        .byte   $AA,$BD                 ; C37C  ..
L_C37E:
        .byte   $C8                     ; C37E  .
sub_C37F:
        .byte   $BC,$8D,$6D,$BE         ; C37F  ..m.
L_C383:
        .byte   $8D                     ; C383  .
L_C384:
        .byte   $68,$BE,$60             ; C384  h.`

; leftoverSetC387 - Unreachable leftover: TAY; $BE6D = $FF; $4D = A; RTS.
; In:  A
; Out: $BE6D, $4D (dead)
; (confidence: low)
leftoverSetC387:
        .byte   $A8,$A9,$FF,$8D         ; C387  ....
L_C38B:
        .byte   $6D,$BE,$84,$4D,$60     ; C38B  m..M`

; leftoverSetC390 - Unreachable leftover: $4D = $FF; $BE6C = $FF; falls into leftoverSetC397.
; In:  none
; Out: $4D, $BE6C, $BE6D (dead)
; (confidence: low)
leftoverSetC390:
        .byte   $A9,$FF,$85,$4D,$8D,$6C ; C390  ...M.l
L_C396:
        .byte   $BE                     ; C396  .

; leftoverSetC397 - Unreachable leftover: $BE6D = $FF; RTS.
; In:  none
; Out: $BE6D (dead)
; (confidence: low)
leftoverSetC397:
        .byte   $A9,$FF,$8D,$6D,$BE,$60 ; C397  ...m.`

; leftoverCheckC39D - Unreachable leftover: returns A = 1 if $51 != 0 or ($48 | $57) == 0; otherwise
; if $48 == 0 sets $51 = $0A, $BE65 = 0, $57 = 0, else $57 = $48; RTS.
; In:  $48, $51, $57
; Out: A, $51, $57, $BE65 (dead)
; (confidence: low)
leftoverCheckC39D:
        .byte   $A5,$51                 ; C39D  .Q
sub_C39F:
        .byte   $D0,$06,$A5,$48,$05,$57,$D0,$03; C39F  ...H.W..
        .byte   $A9,$01,$60,$A5         ; C3A7  ..`.
sub_C3AB:
        .byte   $48,$D0,$0B,$A9,$0A,$85,$51,$A9; C3AB  H.....Q.
        .byte   $00,$20,$BC,$C3,$A9,$00,$85,$57; C3B3  . .....W
        .byte   $60                     ; C3BB  `

; leftoverStoreBE65 - Unreachable leftover: STA $BE65; RTS.
; In:  A
; Out: $BE65 (dead)
; (confidence: low)
leftoverStoreBE65:
        .byte   $8D,$65,$BE,$60         ; C3BC  .e.`

; bootDiskIdTable: text, 4 bytes. Two 2-character disk ids 'OZ' (game disk) and 'EA' (data disk) used
; only by the unreachable leftoverCheckDiskId; the live copy is diskIdTable at $0FAD
bootDiskIdTable:
        .byte   "OZ"                    ; C3C0  'OZ' is the id of the SPORT OF WAR game disk, 'EA' that of a data disk; the live copy of this table is diskIdTable at $0FAD
L_C3C2:
        .byte   $45,$41                 ; C3C2  EA

; ----------------------------------------------------------------------
; leftoverCheckDiskId - Unreachable, and truncated by the end of the file: the original of the game's
; checkDiskId ($0FB1), left behind from a build in which the loader lived at $CBxx and read the BAM to
; $BA00.  It compares the two disk id characters of the BAM against bootDiskIdTable ('OZ' = the game
; disk, 'EA' = a data disk) and would return the matching index in zp $18.  The two JSRs now land in
; the middle of message strings of the runtime overlay, and both exits branch to $C411, past the end
; of the file.
; In:  bootDiskIdTable, a BAM image at $BA00 (in that build)
; Out: zp $18 = matched index, or a branch to nowhere (dead code)
; Called from: nowhere.
; (confidence: medium)
; ----------------------------------------------------------------------
leftoverCheckDiskId:
        lda     a:oldLoadDestLo         ; C3C4  zp $04/$05 were that build's destination pointer
        pha                             ; C3C7  save the low half
        lda     a:oldLoadDestHi         ; C3C8  and the high half
        pha                             ; C3CB  on the stack
        ldx     #$00                    ; C3CC  destination low byte
        ldy     #$BA                    ; C3CE  destination page $BA
        jsr     strAttemptingRepairMid  ; C3D0  in that build $CBED was setDest; here it is the middle of a message string
        lda     #$01                    ; C3D3  one sector
        ldy     #$12                    ; C3D5  track 18
        ldx     #$00                    ; C3D7  sector 0 = the BAM
        jsr     strAttemptingRepairEnd  ; C3D9  and $CBF2 was that build's bootLoadSectors
        lda     #$02                    ; C3DC  start at table entry 1, the 'EA' pair
        sta     scratch18               ; C3DE  zp $18 = byte offset into bootDiskIdTable
leftoverDiskIdLoop:
        ldx     scratch18               ; C3E0  offset of the pair being tested
        inx                             ; C3E2  point at its second character
        lda     titleBitmapBAA3         ; C3E3  second disk id byte of the BAM
        cmp     bootDiskIdTable,x       ; C3E6  compare
        bne     leftoverDiskIdNext      ; C3E9  no match: try the other id
        dex                             ; C3EB  back to the first character
        lda     titleBitmapBAA2         ; C3EC  first disk id byte
        cmp     bootDiskIdTable,x       ; C3EF  compare
D_C3F2:
        beq     leftoverDiskIdMatch     ; C3F2  both characters matched
leftoverDiskIdNext:
        dec     scratch18               ; C3F4  step down two bytes...
        dec     scratch18               ; C3F6  ...to the previous entry, 'OZ'
        bpl     leftoverDiskIdLoop      ; C3F8  only two entries in the table
        bmi     L_C411                  ; C3FA  neither id matched - branches past the end of this file
leftoverDiskIdMatch:
        lsr     scratch18               ; C3FC  offset / 2 = the index: 0 = 'OZ' game disk, 1 = 'EA' data disk
        bne     L_C411                  ; C3FE  'EA' would go somewhere outside this file; the listing stops here mid-routine
