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