Spellbound/Schnelllader

Aus C64-Wiki
Zur Navigation springenZur Suche springen

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