1074 lines
70 KiB
ArmAsm
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
|