; ============================================================================ ; C128 auto-boot sector (track 1 sector 0, loaded by the C128 KERNAL to $0B00) ; ============================================================================ ; Header: "CBM", load address 0, bank 0, 0 extra blocks, boot message "AN ELECTRONIC ARTS PRODUCTION". ; The code copies $0B89-$0BFF to $8000 (a CBM80 cartridge image), puts a 1571 into 1541 mode with ; "U0>M0" / "U0>H0" on the command channel, selects MMU bank 15 and calls GO64 ($FF4D). In C64 mode ; the cartridge signature at $8004 autostarts the copied code (see boot/c64CartridgeStub8000.s). .setcpu "6502" .include "c64.inc" .include "kernal.inc" .include "zeropage.inc" ; ---- references to code/data outside this file ---- D_8000 := $8000 D_FF00 := $FF00 L_FF4D := $FF4D ; Contents ; -------- ; $0B27 c128BootEntry Entry point of the C128 auto-boot sector, called by the KERNAL boot code ; (BOOT_CALL, $FF53 -> $F890) with a JSRFAR in bank 15 after it has loaded this sector to $0B00 and printed "BOOTING AN ; ELECTRONIC ARTS PRODUCTION...". ; $0B45 c128OpenCommandChannel Opens the disk DOS command channel: SETNAM with a zero-length name, SETLFS ; logical file 15 / device 8 / secondary address 15, then JMP KERNAL_OPEN so that OPEN's RTS returns straight to this ; routine's caller. ; $0B55 c128SendDriveCommand Sends one of this sector's three DOS command strings on the already open ; command channel. ; $0B68 sendDriveCommandLoop The transmit loop of c128SendDriveCommand, not a routine of its own: fetch the ; next character through ($FB),Y, stop at the $00 terminator, otherwise CIOUT it to the listening command channel and ; advance. ; $0B72 sendDriveCommandDone Tail of c128SendDriveCommand: JMP KERNAL_CLRCHN ($FFCC). .org $0B00 ; c128BootHeader - the fixed header the C128 KERNAL expects at the front of an auto-boot sector. At ; reset the KERNAL reads track 1 sector 0 of every drive into $0B00 and looks at: ; +0..+2 "CBM" signature - without it the sector is ignored and the C128 goes to BASIC ; +3/+4 load address for the extra blocks that follow ($0000 = none) ; +5 RAM bank those blocks are loaded into (0) ; +6 number of extra blocks to load (0 - this one sector holds everything) ; +7.. zero-terminated boot message, then a zero-terminated filename, then the boot code. ; The KERNAL copies the four bytes $0B03-$0B06 to $AC-$AF before acting on them. Everything in this ; file is dead once the game loads: the fast loader fills $0800-$6EFF, so at run time $0B00-$0BFF ; belongs to the main program (loadCommTailBuild1 at $0B4F, ownerNameString, the settings block) and ; has nothing to do with the code here. c128BootHeader: .byte $43,$42,$4D,$00,$00,$00,$00; 0B00 CBM.... "CBM" + load address $0000 + bank 0 + 0 extra blocks: the whole boot program fits in this sector ; c128BootMessage - 29 characters plus the $00 terminator at $0B24; the KERNAL prints "BOOTING ", this ; text and "...". The KERNAL then builds the filename it believes follows the message in place and ; overwrites $0B23/$0B24 with the drive prefix "0:", so a RAM dump taken after booting reads "AN ; ELECTRONIC ARTS PRODUCTIO0:" rather than the text stored on disk. c128BootMessage: .byte "AN ELECTRONIC ARTS PRODUCTION",$00; 0B07 "AN ELECTRONIC ARTS PRODUCTION",$00 - the message printed after "BOOTING " ; c128BootFileName - the filename field, and the sector's one real bug. It should have been $00 ; (empty filename) followed by the first instruction LDY #$00 ($A0 $00). Because the message ; terminator is immediately followed by $A0, the KERNAL reads $A0 as a one-character filename and the ; $00 as its terminator, tries to LOAD "0:"+$A0 (fails, result ignored) and sets the boot entry to the ; byte after it: $0B27. So these two bytes are data that never execute - verified in x128, where a ; breakpoint at $0B25 never fires and $0B27 is reached with A=X=Y=0. It is harmless only because ; JSRFAR takes Y from the register image at $07, which a cold reset leaves at 0 - exactly what the ; missing LDY would have set. c128BootFileName: .byte $A0,$00 ; 0B25 .. $A0,$00 - intended as the empty-filename byte plus LDY #$00; the KERNAL swallows them as a filename, so execution starts at $0B27 ; ---------------------------------------------------------------------- ; c128BootEntry - Entry point of the C128 auto-boot sector, called by the KERNAL boot code (BOOT_CALL, ; $FF53 -> $F890) with a JSRFAR in bank 15 after it has loaded this sector to $0B00 and printed ; "BOOTING AN ELECTRONIC ARTS PRODUCTION...". It copies 256 bytes from $0B89 to $8000-$80FF (the ; CBM80 pseudo-cartridge disassembled in boot/c64CartridgeStub8000.s), opens the drive command channel ; and sends "U0>M0" and "U0>H0" so that a 1571 behaves exactly like a 1541, then selects MMU bank 15 ; and jumps to the KERNAL GO64 entry. ; In: Y = 0, the index of the copy loop. Nothing else. The LDY #$00 meant to set it sits at $0B25 ; and is never executed (the KERNAL parsed it as a filename); Y is 0 only because JSRFAR loads the ; registers from the images at $05-$07, which a cold reset leaves zeroed. ; Out: $8000-$80FF = the pseudo-cartridge image (only $8000-$8076 is meaningful); logical file 15 open ; to device 8 secondary 15 with SETLFS still pointing at it; a 1571 switched to 1541 mode and head ; 0; $FF00 = $00 (bank 15). Never returns - GO64 resets the machine into C64 mode, where the ; KERNAL finds "CBM80" at $8004 and jumps to $8009 (cartColdStart), which loads "0:EA" and ; continues at $02B8. ; Called from: the C128 KERNAL boot code only (JSRFAR). The $0B27 references listed in XREF.txt ; belong to the game's main program, which overwrites this sector long before they run. ; Copy the C64-mode pseudo-cartridge into place: 256 bytes $0B89,Y -> $8000,Y. Only the first 107 ; bytes mean anything; the loop also drags the sector filler at $0BF4-$0BFF and $0C00-$0C88 (the C128 ; RS-232 input buffer) up to $8077-$80FF, where nothing ever reads them. ; ---------------------------------------------------------------------- c128BootEntry: lda c64CartImage,y ; 0B27 read byte Y of the pseudo-cartridge image that starts at $0B89 sta D_8000,y ; 0B2A store it at $8000+Y; in bank 15 the write falls through the BASIC ROM into RAM bank 0, which is what C64 mode will see iny ; 0B2D next byte of the image bne c128BootEntry ; 0B2E loop until Y wraps back to 0, i.e. exactly 256 bytes copied - the CBM80 signature must land at $8004 ; ---------------------------------------------------------------------- ; Put the drive into the state the game's own loader needs: a 1571 must run in 1541 mode, because the ; drive-resident fast loader and sector cipher the game uploads later (track 1 sectors 17-20, run at ; $0300-$05FF in the drive) are 1541 code, and it must read side 0. ; ---------------------------------------------------------------------- jsr c128OpenCommandChannel ; 0B30 open the DOS command channel (logical file 15, device 8, secondary 15) lda #$00 ; 0B33 command index 0 = "U0>M0" jsr c128SendDriveCommand ; 0B35 send it: a 1571 drops into 1541 mode (a plain 1541 answers with a syntax error nobody reads) lda #$01 ; 0B38 command index 1 = "U0>H0" jsr c128SendDriveCommand ; 0B3A send it: select head 0, the only side this disk uses ; ---------------------------------------------------------------------- ; Hand the machine over to C64 mode. $FF00 is the MMU configuration register (mirrored at $D500); ; writing $00 selects the standard "bank 15" configuration - I/O at $D000, BASIC and KERNAL ROM in, ; RAM bank 0 - which is what the KERNAL's C64-mode routine expects to be called in. ; ---------------------------------------------------------------------- lda #$00 ; 0B3D MMU configuration value $00 = bank 15 (I/O + both ROMs + RAM bank 0) sta D_FF00 ; 0B3F write the MMU configuration register so GO64 runs in the standard bank jmp L_FF4D ; 0B42 $FF4D = KERNAL C64-mode (GO64) entry: resets into C64 mode, where the reset code finds the "CBM80" just copied to $8004 and jumps to $8009. Never returns ; ---------------------------------------------------------------------- ; c128OpenCommandChannel - Opens the disk DOS command channel: SETNAM with a zero-length name, SETLFS ; logical file 15 / device 8 / secondary address 15, then JMP KERNAL_OPEN so that OPEN's RTS returns ; straight to this routine's caller. ; In: none. ; Out: logical file 15 open on device 8 secondary 15. The SETLFS parameters also stay current, which ; the C64-mode code depends on: its LOAD at $8065 uses secondary address 15, and any non-zero ; secondary makes the KERNAL load "0:EA" to the address in the file header ($02A8-$030B) instead ; of to the caller's address. A/X/Y clobbered; C = OPEN's error flag, which neither caller tests. ; Called from: c128BootEntry ($0B30) and, after GO64, the copied cartridge code at $8043 ; (boot/c64CartridgeStub8000.s) - that RAM survives because the C64 reset skips RAMTAS when it ; finds a cartridge, and the cartridge's own clear loop stops at $03FF. ; ---------------------------------------------------------------------- c128OpenCommandChannel: lda #$00 ; 0B45 SETNAM length 0: a command channel needs no filename jsr KERNAL_SETNAM ; 0B47 KERNAL SETNAM ($FFBD) lda #$0F ; 0B4A logical file number 15 ldx #$08 ; 0B4C device 8 = the disk drive tay ; 0B4E secondary address 15 as well - channel 15 is the DOS command/status channel jsr KERNAL_SETLFS ; 0B4F KERNAL SETLFS ($FFBA) with A=15, X=8, Y=15 jmp KERNAL_OPEN ; 0B52 KERNAL OPEN ($FFC0) as a tail call - its RTS returns to our caller; the error flag in C is ignored ; ---------------------------------------------------------------------- ; c128SendDriveCommand - Sends one of this sector's three DOS command strings on the already open ; command channel. It doubles the command index to index driveCmdPtrTable (two bytes per entry), ; copies the string pointer to $FB/$FC, makes logical file 15 the output channel with CHKOUT and ; CIOUTs every character up to but not including the $00 terminator; the closing CLRCHN unlistens the ; drive, which is what actually makes the DOS execute the command. ; In: A = command index: 0 = "U0>M0" (1571 -> 1541 mode), 1 = "U0>H0" (head/side 0), 2 = "I" ; (initialise). Channel 15 must already be open (c128OpenCommandChannel). The routine does the ; doubling itself - the survey's "pre-doubled" wording was wrong. ; Out: the command has been sent and executed. $FB/$FC = pointer to the string, X = $0F, Y = string ; length, A = the last character sent; default I/O channels restored by CLRCHN. The DOS error ; code is left unread in the drive's status channel, so a 1541 rejecting "U0>" goes unnoticed by ; design. ; Called from: c128BootEntry ($0B35 with index 0, $0B3A with index 1) and the C64-mode cartridge code ; at $8048 with index 2 (boot/c64CartridgeStub8000.s). ; ---------------------------------------------------------------------- c128SendDriveCommand: asl a ; 0B55 index * 2 - the pointer table holds two bytes per entry tax ; 0B56 use the doubled index as the byte offset into the table lda driveCmdPtrTable,x ; 0B57 low byte of this command's string pointer ldy driveCmdPtrTableHi,x ; 0B5A high byte from the following table byte (always $0B - all three strings live in this sector) sta driveCmdStrPtr ; 0B5D park the pointer in $FB/$FC for the (zp),Y fetch in the loop sty driveCmdStrPtrHi ; 0B5F ...and its high byte in $FC ldx #$0F ; 0B61 logical file 15 = the command channel opened by c128OpenCommandChannel jsr KERNAL_CHKOUT ; 0B63 KERNAL CHKOUT ($FFC9): make it the current output channel (sends LISTEN 8 + secondary 15) ldy #$00 ; 0B66 start at the first character of the command string ; ---------------------------------------------------------------------- ; sendDriveCommandLoop - The transmit loop of c128SendDriveCommand, not a routine of its own: fetch ; the next character through ($FB),Y, stop at the $00 terminator, otherwise CIOUT it to the listening ; command channel and advance. ; In: $FB/$FC = start of the command string, Y = index of the next character, logical file 15 already ; selected as the output channel. ; Out: falls out to sendDriveCommandDone at the terminator with Y = length of the string. ; Called from: fallen into from $0B66; branch target of the BNE at $0B70. ; ---------------------------------------------------------------------- sendDriveCommandLoop: lda (driveCmdStrPtr),y ; 0B68 next character of the DOS command string beq sendDriveCommandDone ; 0B6A $00 terminator reached - the whole command has been sent jsr KERNAL_CIOUT ; 0B6C KERNAL CIOUT ($FFA8): put this character on the serial bus for the listening drive iny ; 0B6F advance to the next character bne sendDriveCommandLoop ; 0B70 always taken: the longest command is five characters, so Y never wraps to 0 ; ---------------------------------------------------------------------- ; sendDriveCommandDone - Tail of c128SendDriveCommand: JMP KERNAL_CLRCHN ($FFCC). Its UNLISTEN is ; what tells the drive the command is complete and makes the DOS act on it; it also restores the ; default input/output channels, and its RTS returns to c128SendDriveCommand's caller. ; In: none. ; Out: serial bus unlistened (command executed), default channels restored. ; Called from: branch target of the BEQ at $0B6A (end of sendDriveCommandLoop). ; ---------------------------------------------------------------------- sendDriveCommandDone: jmp KERNAL_CLRCHN ; 0B72 KERNAL CLRCHN ($FFCC): UNLISTEN makes the drive execute the command; its RTS returns to our caller ; driveCmdPtrTable - three little-endian pointers to the DOS command strings below, one per command ; index (2 bytes per entry). c128SendDriveCommand reads the low byte with LDA driveCmdPtrTable,X and ; the high byte with LDY driveCmdPtrTableHi,X after doubling the index in A, which is why the ; high-byte position carries its own label. driveCmdPtrTable: driveCmdPtrTableHi = driveCmdPtrTable+1 .addr driveCmdSet1541Mode ; 0B75 index 0 -> "U0>M0", index 1 -> "U0>H0", index 2 -> "I" .addr driveCmdSelectHead0 ; 0B77 .addr driveCmdInitialize ; 0B79 ; The three DOS command strings, each terminated with $00. "U0>" is the 1571 user command group; a ; plain 1541 replies with error 31 (syntax error), which nobody reads, so one sector serves both ; drives. driveCmdSet1541Mode: .byte "U0>M" ; 0B7B "U0>M" - first four characters of the 1571 command "U0>M0" (switch to 1541 mode) .byte "0",$00 ; 0B7F "0" and the $00 terminator that complete "U0>M0"; the line break at $0B7F is only a rendering artifact of the main program's ownerNameString, which starts at this address in the other unit ; driveCmdSelectHead0 - "U0>H0": 1571 command that selects head 0, i.e. side 0 of the disk. Every ; track the game reads is on side 0. driveCmdSelectHead0: .byte "U0>H0",$00 ; 0B81 "U0>H0",$00 - 1571: use head 0 ; driveCmdInitialize - "I": plain DOS INITIALIZE (re-read the BAM, clear the error state). The ; C128-mode code above never sends it; only the copied C64-mode code does, at $8046 (LDA #$02 / JSR ; $0B55), just before it LOADs "0:EA". driveCmdInitialize: .byte "I",$00 ; 0B87 "I",$00 - DOS initialise, sent only by the C64-mode code at $8046 ; c64CartImage - bytes $89-$F3 of the sector: the CBM80 pseudo-cartridge that c128BootEntry copies to ; $8000 before calling GO64. It is data here and code there; it is disassembled as $8000-$806A in ; boot/c64CartridgeStub8000.s and must not be read as code in this file. Row by row, address in this ; sector -> address after the copy: ; 0B89 -> 8000 cold-start vector $8009, warm-start vector $8009, $C3 $C2 $CD $38 = "CBM"|$80 + '8' ; 0B91 -> 8008 '0', last byte of "CBM80"; JSR $FF84 IOINIT; LDA #$00 / TAY; STA opcode of the loop ; 0B99 -> 8010 operands of the clear loop at $800F: STA $0002,Y / STA $0200,Y / STA $0300,Y - the ; RAMTAS work the C64 reset skips when a cartridge is present ; 0BA1 -> 8018 INY / BNE $800F (256 passes), then LDX #$00 / LDY #$A0 / CLC = MEMTOP value $A000 ; 0BA9 -> 8020 JSR $FF99 MEMTOP (C=0, so it stores), LDA #$08 / STA $0282 = start-of-BASIC page $08 ; 0BB1 -> 8028 LDA #$04 / STA $0288 = screen page $04, JSR $FF8A RESTOR (default KERNAL vectors) ; 0BB9 -> 8030 JSR $FF81 CINT (screen editor), LDA #$06 = blue / STA $D020 border ; 0BC1 -> 8038 STA $D021 background also blue, LDA $D011 / AND #$EF clears DEN = display blanked ; 0BC9 -> 8040 STA $D011, JSR $0B45 (this sector's open-command-channel code, still in RAM), LDA #2 ; 0BD1 -> 8048 JSR $0B55 sends "I", LDA #$0F / JSR $FFC3 CLOSE lfn 15 ; 0BD9 -> 8050 SETNAM arguments LDA #$04 / LDX #$61 / LDY #$80 (name "0:EA" at $8061) and JSR $FFBD ; 0BE1 -> 8058 end of that JSR, LDX #$F0 / TXS, LDA #$00 = load (not verify), JMP $8065 past name ; 0BE9 -> 8060 the four characters "0:EA" at $8061-$8064 and JSR $FFD5 LOAD - secondary address 15 ; from $0B45 makes it load to the file's own address $02A8-$030B ; 0BF1 -> 8068 JMP $02B8 = eaLoadGame, which LOADs "load" to $9800-$C3FF, calls the boot loader at ; $C000 and finally jumps to the game at $0800. ; The copy loop moves 256 bytes, so $0BF4-$0BFF and $0C00-$0C88 (the C128 RS-232 input buffer) land at ; $8077-$80FF too; nothing reads them, and the game's overlays overwrite $8000-$87FF anyway. c64CartImage: .byte $09,$80,$09,$80,$C3,$C2,$CD,$38; 0B89 .......8 107 bytes of C64-mode header and code, copied to $8000-$806A above; see boot/c64CartridgeStub8000.s .byte $30,$20,$84,$FF,$A9,$00,$A8,$99; 0B91 0 ...... .byte $02,$00,$99,$00,$02,$99,$00,$03; 0B99 ........ .byte $C8,$D0,$F4,$A2,$00,$A0,$A0,$18; 0BA1 ........ .byte $20,$99,$FF,$A9,$08,$8D,$82,$02; 0BA9 ....... .byte $A9,$04,$8D,$88,$02,$20,$8A,$FF; 0BB1 ..... .. .byte $20,$81,$FF,$A9,$06,$8D,$20,$D0; 0BB9 ..... . .byte $8D,$21,$D0,$AD,$11,$D0,$29,$EF; 0BC1 .!....). .byte $8D,$11,$D0,$20,$45,$0B,$A9,$02; 0BC9 ... E... .byte $20,$55,$0B,$A9,$0F,$20,$C3,$FF; 0BD1 U... .. .byte $A9,$04,$A2,$61,$A0,$80,$20,$BD; 0BD9 ...a.. . .byte $FF,$A2,$F0,$9A,$A9,$00,$4C,$65; 0BE1 ......Le .byte $80,$30,$3A,$45,$41,$20,$D5,$FF; 0BE9 .0:EA .. .byte $4C,$B8,$02 ; 0BF1 L.. ; sectorTailFiller - the last 12 bytes of the sector ($F4-$FF): $00 followed by 8E 72 17 / 8E 73 17 / ; 8E 70 17 / 8E 71, i.e. fragments of STX $1772 / STX $1773 / STX $1770 / STX $1771. Dead leftovers ; from the mastering system's buffer (track 1 sector 16 has similar debris - fragments of assembler ; source text). They ride along with the 256-byte copy to $806B-$8076 and are never referenced. sectorTailFiller: .byte $00,$8E,$72,$17,$8E,$73,$17,$8E; 0BF4 ..r..s.. unused mastering leftovers; copied to $806B-$8076 and never used .byte $70,$17,$8E,$71 ; 0BFC p..q tail of the filler ($8073-$8076 after the copy)