Spellbound/Schnelllader
<< zurück zu Finders Keepers oder Spellbound
Finders Keepers/Schnelllader und Spellbound/Schnelllader: Die folgenden Abschnitte stellen den disassemblierten Kassetten-Schnelllader der Spiele Finders Keepers und Spellbound dar. Sie sind gegliedert in einzelne Codeblöcke, die gemäß ihrer Funktion während des Ladens gruppiert sind.
Initialisierung des Schnellladers[Bearbeiten | Quelltext bearbeiten]
Die folgenden Routinen werden beim Laden des Spiele Finders Keepers und Spellbound von Kassette ab Adresse $029F in den Speicher des C64 übertragen. Der Ladevorgang überschreibt hierbei die Kopie des Interruptvektors an Adresse $029F/$02A0 mit der Startadresse der Laderoutine. Diese Kopie wird nach dem Ende des Ladevorgangs als Interruptvektor an Adresse $0314/$0315 zurückgeschrieben, so dass der darauffolgende Timer-Interrupt den Schnelllader aktiviert. Den gleichen trickreichen Autostart verwendet auch der Kassetten-Schnelllader CyberLoad.
ORG $029F
DW P_AA ; Speicher für IRQ-Vektor während Bandbetrieb
DB $00 ; CIA 2 NMI-Flag
DB $11 ; CIA 1 Timer A
DB $90 ; CIA 1 Interruptflag
DB $11 ; CIA 1 Flag für Timer A
DB $00 ; Bildschirmzeile
DB $01 ; Flag für PAL- (1) oder NTSC-Version (0)
DB $00
; Initialisierung
P_AA: LDA #$20 ; Füllbyte A=' '
LDX #$08 ; High-Byte der Endadresse für Füllen
LDY #$00 ; Index für Füllen initialisieren
AA00: STA ($AC),Y ; Auf Schnelllader folgende Bytes mit Leerzeichen überschreiben
JSR $FCDB ; Adresszeiger erhöhen
CPX *$AD ; Schon Adresse $0800 erreicht?
BNE AA00 ; Rücksprung falls noch nicht erreicht
LDX #$D0 ; High-Byte der Endadresse für Füllen
AA01: LDA $FCD1 ; Füllbyte A='8'
STA ($AC),Y ; Restlichen Speicher bis I/O-Bereich überschreiben
INY ; Schreibindex erhöhen
BNE AA01 ; Rücksprung falls noch kein Überlauf
INC *$AD ; High-Byte des Schreibzeigers erhöhen
CPX *$AD ; Schon Adresse $D000 erreicht?
BNE AA01 ; Rücksprung falls noch nicht erreicht
LDA #$80 ; Flag für Direkt-Modus setzen
STA *$9D
LDA #$00 ; $00 an den Anfang des BASIC-Speichers schreiben
STA $0800
STA *$C6 ; Tastaturpuffer löschen
JMP P_AD ; Sprung zur Laderoutine
; Füllbytes ohne Funktion
DB $00,$AD,$01,$DD,$29,$01,$85,$A7,$AD,$06,$DD,$E9,$1C,$6D,$99,$02
DB $8D,$06,$DD,$AD,$07,$DD,$6D,$9A,$02,$8D,$07,$DD,$A9,$11,$8D,$0F
DB $DD,$AD,$A1,$02,$8D,$0D,$DD,$D0,$DF,$4C,$D4
; Betriebssystem-Vektoren, teilweise umgebogen
DW $E38B ; Vektor für BASIC-Warmstart
DW $A483 ; Vektor für Eingabe einer Zeile
DW $A57C ; Vektor für Umwandlung in Interpretercode
DW P_AA ; Vektor für Umwandlung in Klartext (LIST), umgebogen auf Schnelllader
DW $A7E4 ; Vektor für BASIC-Befehlsadresse holen
DW $AE86 ; Vektor für Ausdruck auswerten
DB $00 ; Akku für SYS-Befehl
DB $00 ; X-Reg für SYS-Befehl
DB $00 ; Y-Reg für SYS-Befehl
DB $00 ; Status-Register für SYS-Befehl
DB $4C ; JMP-Befehl für USR-Funktion
DW $B248 ; USR-Vektor
DB $00
DW P_AA ; IRQ-Vektor, umgebogen auf Schnelllader
DW $C5FF ; BRK-Vektor, umgebogen auf ???
DW $FEC1 ; NMI-Vektor, umgebogen auf "RTI"
DW $F34A ; OPEN-Vektor
DW $F291 ; CLOSE-Vektor
DW $F20E ; CHKIN-Vektor
DW $F250 ; CKOUT-Vektor
DW $F333 ; CLRCH-Vektor
DW $F157 ; INPUT-Vektor
DW $F1CA ; OUTPUT-Vektor
DW $F6EA ; STOP-Vektor, umgebogen auf "LDY #$58 : RTS"
DW $F13E ; GET-Vektor
DW $F32F ; CLALL-Vektor
DW $FE66 ; Warmstart-Vektor
DW P_AD ; LOAD-Vektor, umgebogen auf Schnelllader
DW $F5ED ; SAVE-Vektor
Schnelllade-Routinen[Bearbeiten | Quelltext bearbeiten]
Die nachfolgenden Routinen stellen eine minimale Implementierung eines Datassetten-Schnellladers dar: Routine P_AB liest ein Bit von Band (falls der auf den Startwert 438 initialisierte Timer A von CIA 2 schon abgelaufen ist, handelt es sich um ein 1-Bit, sonst ein 0-Bit), Routine P_AC aggregiert Bits zu Bytes (wobei jedem Byte ein 1-Bit vorausgeht, das die Datenmenge aufbläht, aber nicht überprüft wird), und Routine P_AD fasst schließlich Bytes anhand von eingestreuten Headern mit Adressinformationen zu Blocks zusammen.
; Bit von Band lesen
P_AB: LDA #$10 ; Auf Bit vom Band warten
AB00: BIT $DC0D
BEQ AB00
LDA #$11 ; CIA 2 Timer A neu starten
STA $DD0E
LDA $DD0D ; Unterlaufbit Timer A holen
LSR A ; und ins CF
BIT $DC0D ; Interruptregister CIA 1 löschen
ROL *$FC ; Unterlaufbit von rechts in gelesenes Byte schieben
RTS
; Byte vom Band lesen und nach A
P_AC: LDX #$09 ; Bitzähler initialisieren (jedes Byte startet mit 1-Bit)
AC00: JSR P_AB ; Bit von Band lesen
INC $D020 ; Bildschirm-Rahmenfarbe weiterzählen
DEX ; Bitzähler erniedrigen
BNE AC00 ; Rücksprung falls noch nicht 9 Bit gelesen
LDA *$FC ; Empfangenes Byte in A laden
RTS
; Füllbytes ohne Funktion
DB $EA,$EA,$A9,$81,$8D,$0D,$DC,$58,$A9,$00,$20,$71,$A8,$4C,$EA,$A7
DB $DC,$58,$A9,$00,$20,$71,$A8,$4C,$EA,$A7,$00,$00,$00,$00,$00,$8D
; Hauptschleife
P_AD: LDA #$B6 ; Startwert CIA 2 Timer 1=438
STA $DD04 ; Low-Byte
LDA #$01
STA $DD05 ; High-Byte
LDY #$7F
STY $DD0D ; Alle Interrupts von CIA 2 verbieten
STY $DC0D ; Alle Interrupts von CIA 1 verbieten
LDA #$07 ; Motor an
STA *$01
AD00: STY *$FC ; Von Band gelesenes Byte mit $7F initialisieren
AD01: JSR P_AB ; Bit von Band lesen
BNE AD01 ; Rücksprung falls noch nicht 8 aufeinanderfolgende Nullbits
JSR P_AC ; Byte vom Band lesen
BNE AD00 ; Rücksprung falls nicht Nullbyte gelesen
AD02: JSR P_AC ; Byte vom Band lesen
BEQ AD02 ; Nullbytes überlesen
CMP #$16 ; Synchonisationszeichen $16?
BNE AD00 ; Rücksprung falls nicht Synchronisationszeichen
AD03: LDY #$03 ; 4 Byte langen Header lesen
AD04: JSR P_AC ; Byte vom Band lesen
STA $00AC,Y ; Header mit Start- und Endadresse nach $00AC..$00AF schreiben
DEY ; Schreibindex vermindern
BPL AD04 ; Rücksprung falls noch nicht 4 Bytes übertragen
AD05: JSR P_AC ; Byte vom Band lesen
STA ($AC,X) ; und an Zieladresse im Speicher schreiben (X=0)
JSR $FCDB ; Adresszeiger erhöhen
JSR $FCD1 ; Endadresse schon erreicht?
BCC AD05 ; Rücksprung falls noch nicht erreicht
AD06: BCS AD03 ; Unbedingter Sprung, nächsten Header lesen
Erweiterungen des Schnellladers durch Selbstmodifikation[Bearbeiten | Quelltext bearbeiten]
Nach dem Laden eines vollständigen Programmblocks modifiziert sich der Schnelllader, indem er einen kurzen Datenblock in den von ihm selbst belegten Speicherbereich lädt. Hierbei wird der "BCS"-Befehl bei AD06 durch den Opcode für "LDA#" ersetzt, so dass anstelle eines Rücksprungs nach AD03 der nachfolgende Code ausgeführt wird. Dieser stellt gleich zu Beginn den ursprünglichen Rücksprung mittels "BCS" wieder her.
Vorbereitungen für das Ausführen eines BASIC-Programms[Bearbeiten | Quelltext bearbeiten]
Diese Erweiterung der Laderoutine setzt eine Reihe von Zeropage-Speicherzellen sowie den Sprungvektor für die Tastatur-Decodierung auf ihre Standardwerte zurück und schafft damit die Voraussetzungen für die Ausführung eines BASIC-Programms. Obwohl es ausreichen würde, diese Routine einmalig vor dem Start der BASIC-Steuerungsroutine aufzurufen, wird sie während des Ladevorgangs insgesamt 78 Mal von Band gelesen und ausgeführt.
LDA #$B0 ; Opcode für "BCS"
STA AD06 ; Rücksprung in Hauptschleife wiederherstellen
LDA #$07 ; Alle ROMs einblenden
STA *$01
LDA #$4C ; JMP für Funktionen
STA *$54 ; auf Standardwert $4C setzen
LDA #$91 ; Vektor Fest nach Fließkomma
LDY #$B3
STA *$05 ; auf Standardwert $B391 setzen
STY *$06
LDA #$AA ; Vektor Fließkomma nach Fest
LDY #$81
STA *$03 ; auf Standardwert $B1AA setzen
STY *$04
LDX #$1C ; CHRGET-Routine aus ROM in RAM umkopieren
AD07: LDA $E3A2,X
STA *$73,X
DEX
BPL AD07
LDA #$48 ; Zeiger auf Tastatur-Decodierung
LDY #$EB
STA $028F ; auf Standardwert $EB48 setzen
STY $0290
JMP P_AD ; Zurück zum Schnelllader
Ausführen eines BASIC-Programms[Bearbeiten | Quelltext bearbeiten]
Dieser Codeabschnitt setzt einmalig den Zeiger für das Ende des BASIC-Programms und den Beginn der Variablen auf das Ende der BASIC-Steuerungsroutine und startet diese dann, indem er die Implementierung des BASIC-Befehls RUN an Adresse $A871 im ROM aufruft. Sowohl der nachfolgende Sprung zur Interpreterschleife wie auch die alternative Routine für den Programm-Modus ab AD07 werden nie ausgeführt.
LDA #$B0 ; Opcode für "BCS"
STA AD06 ; Rücksprung in Hauptschleife wiederherstellen
LDA #$81 ; CIA1 Interrupt bei Unterlauf von Timer A
STA $DC0D
CLI ; Interrupts zulassen
JSR $FCCA ; Recordermotor stoppen
LDA *$9D ; Direkt- oder Programm-Modus?
BPL AD07 ; Sprung wenn Programm-Modus (ist nie der Fall)
LDA #$52 ; Ende des BASIC-Programms, Low-Byte
STA *$2D ; setzen
LDA #$09 ; Ende des BASIC-Programms, High-Byte
STA *$2E ; setzen
LDA #$00 ; Überflüssig
JSR $A871 ; BASIC-Befehl RUN
JMP $A7EA ; Sprung zur Interpreterschleife (wird nie erreicht)
; Sonderbehandlung Programm-Modus
; Wird nie erreicht, würde mit "SYNTAX ERROR IN 10" scheitern
AD07: JSR $A68E ; Programmzeiger auf BASIC-Start
JSR $A67E ; Einsprung in CLR-Befehl
JMP $A7EA ; Sprung zur Interpreterschleife
Abschluss des LOAD-Befehls[Bearbeiten | Quelltext bearbeiten]
Das folgende Codestück wird nach jedem Laden eines vollständigen Programmblocks von Kassette gelesen und ausgeführt, insgesamt also 9 Mal. Es gaukelt dem LOAD-Befehl in der BASIC-Steuerungsroutine einen ordnungsgemäßen Abschluss der Ladeoperation mittels Kernal-Routinen vor.
LDA #$B0 ; Opcode für "BCS"
STA AD06 ; Rücksprung in Hauptschleife wiederherstellen
LDA #$81 ; CIA1 Interrupt bei Unterlauf von Timer A
STA $DC0D
CLI ; Interrupts zulassen
JSR $FCCA ; Recordermotor stoppen
BIT $E453 ; ?
LDX #$40 ; Low-Byte der Endadresse+1 (hier $FF40, alternativ $CFE8, $DBE8, $D030, $6000, $CC00, $D030, $D000, $F000)
LDY #$FF ; High-Byte der Endadresse+1
LDA #$40 ; Statusvariable ST auf "EOF" setzen
STA *$90
CLC ; Zeichen für "Kein Fehler"
RTS ; Rücksprung nach $E178