268 lines
20 KiB
ArmAsm
268 lines
20 KiB
ArmAsm
; ============================================================================
|
|
; 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)
|