modemwars/disassembly/boot/fastLoaderC000.s
2026-08-23 02:09:40 -05:00

1074 lines
70 KiB
ArmAsm

; ============================================================================
; 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