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