Willow Pattern/Schnelllader

Aus C64-Wiki
Zur Navigation springenZur Suche springen

<< zurück zu Willow Pattern


Willow Pattern/Schnelllader: Die folgenden Abschnitte stellen den disassemblierten Kassetten-Schnelllader des Spiels Willow Pattern dar. Sie sind gegliedert in einzelne Codeblöcke, die gemäß ihrer Funktion während und nach dem Laden gruppiert sind.

Autostart-Routine[Bearbeiten | Quelltext bearbeiten]

Die folgende, kurze Routine stellt den ersten Teil einer langen Sequenz von Dateien dar, die zusammengenommen die Kassettenversion des Spiels "Willow Pattern" bilden. Die Ladeadresse $02A7 (dezimal 679) ist so gewählt, dass das Maschinenspracheprogramm zwar in einen üblicherweise ungenutzten Bereich des Hauptspeichers geladen wird, die nachfolgenden Datenbytes aber den Sprungvektor für die Eingabe einer BASIC-Zeile überschreiben, auf das Programm selbst umbiegen und dieses dadurch nach dem Laden automatisch starten.

Das Programm stellt aber keinen Schnelllader dar, sondern verändert nur einige Bildschirmeinstellungen und lädt dann mit Hilfe der normalen Kernal-Routinen — und mit der üblichen, langsamen Geschwindigkeit — ein 1 kByte langes, namenloses Programm nach, das zwei etwa gleich lange und gleich leistungsfähige Schnelllader enthält. Anschließend werden drei Datenblöcke mit Hilfe des ersten Schnellladers von Kassette eingelesen, bevor die Kontrolle an die zweite Laderoutine übergeben wird.

ORG $02A7

P_AA: LDA #<AA01  ; Stop-Vektor auf AA01 richten (STOP-Taste abschalten)
      STA $0328   ; Low-Byte
      LDA #>AA01
      STA $0329   ; High-Byte
      LDA #$0E    ; Farbcode für "hellblau"
      STA $D021   ; Bildschirmhintergrund setzen
      LDA #$40
      JSR $FF90   ; Status setzen
      LDA #$01    ; Logische Dateinummer=1
      TAX         ; Gerätenummer=1 (Datassette)
      TAY         ; Sekundäradresse=1
      JSR $FFBA   ; Fileparameter setzen
      LDA #$00    ; Länge des Dateinamens=0
      JSR $FFBD   ; Filenamenparameter setzen
      JSR $FFD5   ; LOAD
      LDA #$93    ; ASCII-Code für "Bildschirm löschen"
      JSR $FFD2   ; BSOUT Ausgabe eines Zeichens
      LDX #$00    ; Farbcode für "schwarz"
      STX $D021   ; Bildschirmhintergrund setzen
      LDA #$01    ; Farbcode für "weiß"
AA00: STA $D900,X ; Farb-RAM für einen Teil des Bildschirms füllen
      INX         ; Schreibindex erhöhen
      BNE AA00    ; Rücksprung falls noch nicht 256 Bytes gefüllt
      LDX #$C4    ; Bildschirmzeiger auf $05C4 richten
      LDY #$05
      JSR P_BA    ; Programmteil laden
      LDA #$02    ; Dateinummer (und Synchronisationszeichen)
      LDX #$00    ; Bildschirmzeiger auf $BE00 richten
      LDY #$BE
      JSR P_BA    ; Programmteil laden
      LDA #$03    ; Dateinummer (und Synchronisationszeichen)
      LDX #$00    ; Bildschirmzeiger auf $BE00 richten
      LDY #$BE
      JSR P_BA    ; Programmteil laden
      JMP P_CA
AA01: RTS

      DB $00,$00,$00,$00,$00,$00,$00 ; Füllbytes

      DW $E38B    ; Vektor für BASIC Warmstart
      DW P_AA     ; Vektor für Eingabe einer Zeile

Erster Schnelllader[Bearbeiten | Quelltext bearbeiten]

Der erste Schnelllader lädt eine unbeschränkte Anzahl von 256 Byte langen Datenblöcken in beliebige — nicht notwendigerweise direkt aufeinanderfolgende — Pages des C64-Hauptspeichers. Jedem Datenblock muss dabei ein Header vorausgehen, der nacheinander die Blocknummer (1 Byte) sowie die Zieladresse des Blocks, die Endadresse des ganzen Programmteils und sowie dessen Einsprungadresse (jeweils 2 Byte im Little Endian-Format) enthält. Ist die Einsprungadresse auf $0000 gesetzt, so wird der Programmteil nach dem Laden nicht angesprungen. Diese Arbeitsweise weist einige Ähnlichkeiten zum Schnelllader Novaload auf.

Die Unterscheidung von 0- und 1-Bits geschieht in Routine P_BH mit Hilfe des Timers A der CIA 2. Dieser zählt für jedes Bit, beginnend bei einem Startwert von 560, mit dem Systemtakt der CPU abwärts. Ist der Zähler beim Eintreffen des nächsten Signals vom Band bereits abgelaufen, so handelt es sich um ein 1-Bit (Abstand zum vorigen Impuls 720 Takte), ansonsten um ein 0-Bit (Abstand zum vorigen Signal 384 Takte). Die Interruptroutine P_AG fasst jeweils 8 Bit zu einem Byte zusammen. Interessanterweise wird dieses nicht direkt in den Hauptspeicher übertragen, sondern landet zunächst in einem 32 Byte langen Ringpuffer, wo es von Routine P_BL abgeholt werden kann. Da die nachzuladenden Daten zu 41,3% aus 0-Bits und zu 58,7% aus 1-Bits bestehen, dauert das Einlesen eines Byte vom Band durchschnittlich 4650 Systemtakte (fast 5 ms).

PRG $C000

P_BA: STA *$A3    ; Synchronisationszeichen
      STX *$A4    ; Bildschirmzeiger low
      STY *$A5    ; Bildschirmzeiger high
      SEI         ; Interrupts abschalten, bevor Interruptvektor geändert wird
      LDA #$C1    ; Interruptvektor auf Befehl "RTI" richten
      STA $0318   ; Low-Byte
      LDA #$FE
      STA $0319   ; High-Byte
      LDA *$01    ; Prozessorport lesen
      AND #$DF    ; Datassetten-Motor dauerhaft einschalten
      STA *$01    ; und zurückschreiben
      LDA #$32    ; Startwert für Timer A von CIA 2 auf $0232 (560)
      STA $DD04   ; Low-Byte
      LDA #$02
      STA $DD05   ; High-Byte
      LDA #$19    ; Timer A mit Startwert laden und im "One Shot"-Modus starten
      STA $DD0E
      LDA #$7F    ; Alle Interrupts von CIA 1 abschalten
      STA $DC0D
      STA $D020   ; Bildschirmrand hellgrau
      LDA #$90    ; Einzige Interruptquelle für CIA ist Impuls von Datassette
      STA $DC0D
      LDY #$00    ; Blockzähler
      STY *$B1    ; initialisieren
      JSR P_BC    ; Meldung "SEARCHING " ausgeben
BA00: JSR P_BI    ; Mit Datassette synchronisieren
      JSR P_BJ    ; 7 Byte langen Blockheader an $A9..$AF einlesen
      LDA *$A9    ; Blocknummer aus Blockheader holen
      CMP *$B1    ; und mit aktuellem Blockzähler vergleichen
      BEQ BA01    ; Sprung falls gleich
      JSR P_BE    ; sonst Fehlermeldung "BLOCK? " ausgeben
      BEQ BA00    ; Unbedingter Sprung, neu synchronisieren und nächster Block
BA01: JSR P_BD    ; Meldung "SEARCHING " durch Meldung "LOADING " ersetzen
      JSR P_BK    ; Nächsten Programmblock einlesen
      BEQ BA02    ; Sprung falls Block samt Prüfsumme korrekt gelesen
      JSR P_BE    ; sonst Fehlermeldung "BLOCK? " ausgeben
      BEQ BA00    ; Unbedingter Sprung, neu synchronisieren und nächster Block
BA02: INC *$B1    ; Blockzähler erhöhen
      LDA $D020   ; Bildschirm-Rahmenfarbe holen
      EOR #$02    ; wechseln
      STA $D020   ; und zurückschreiben
      DEC *$AD    ; Endadresse für nachfolgenden Vergleich erniedrigen
      LDA *$AB    ; Zieladresse des aktuellen Blocks holen
      CMP *$AD    ; und mit Endadresse-1 vergleichen
      BNE BA00    ; Sprung, falls ungleich (also nicht letzter Block)
      JSR P_BM    ; Bildschirm und Interrupts wieder auf Standardwerte setzen
      LDA *$AE    ; Soll soeben geladener Programmteil ausgeführt werden?
      ORA *$AF
      BEQ BB02    ; Sprung falls nicht ausführen (Einsprungadresse=$0000)
      JMP ($00AE) ; sonst ausführen

; Inhalt von <A> als Hexadezimalzahl an ($A4),Y darstellen
P_BB: PHA         ; Wert von A retten
      LSR A       ; High Nibble isolieren
      LSR A
      LSR A
      LSR A
      JSR BB00    ; und ausgeben
      PLA         ; Wert von A zurückholen
      INY         ; Schreibindex für Bildschirm erhöhen
      AND #$0F    ; Low Nibble isolieren
BB00: ORA #$30    ; Nibble in Bereich '0'...'9' bringen
      CMP #$3A    ; Größer als '9'?
      BCC BB01    ; Sprung falls nicht
      SBC #$39    ; sonst in Bereich 'A'...'F' bringen
BB01: STA ($A4),Y ; und ausgeben
BB02: RTS

; Meldung "SEARCHING " auf Bildschirm ausgeben
P_BC: LDX #T000-T000  ; Zeiger auf Bildschirmmeldung "SEARCHING "
      LDY #$28    ; Schreibindex für Bildschirm (zweite Zeile)
      JMP BE00    ; Bildschirmmeldung ausgeben

; Meldung "SEARCHING " durch Meldung "LOADING " ersetzen
P_BD: LDY #$28    ; Schreibindex für Bildschirm (zweite Zeile)
      LDA #$20    ; ' '
BD00: STA ($A4),Y ; Meldung "SEARCHING " in zweiter Zeile löschen
      INY         ; Schreibindex erhöhen
      CPY #$35    ; Schon ganze Meldung gelöscht?
      BNE BD00    ; Rücksprung falls noch nicht
      LDX #T001-T000  ; Zeiger auf Bildschirmmeldung "LOADING "
      LDY #$02    ; Schreibindex für Bildschirm
      JMP BE00    ; Bildschirmmeldung ausgeben

; Fehlermeldung " BLOCK? " und ggf. "REWIND TO " ausgeben
P_BE: LDX #T002-T000 ; Zeiger auf Bildschirmmeldung " BLOCK? "
      LDY #$02    ; Schreibindex für Bildschirm
      JSR BE00    ; Bildschirmmeldung ausgeben
      LDA *$B1    ; Nummer des gesuchten Blocks holen
      LDY #$32    ; Schreibindex für Bildschirm
      JSR P_BB    ; und als Hexadezimalzahl ausgeben
      LDA *$A9    ; Blocknummer aus Blockheader holen
      CMP *$B1    ; und mit aktuellem Blockzähler vergleichen
      BCC P_BC    ; Sprung falls kleiner
      LDX #T003-T000 ; Zeiger auf Bildschirmmeldung "REWIND TO "
      LDY #$28    ; Schreibindex für Bildschirm
; Bildschirmmeldung über Zeiger $A4/$A5 ausgeben
BE00: LDA T000,X  ; Meldung byteweise lesen
      STA ($A4),Y ; und auf Bildschirm schreiben
      INY         ; Schreibindex erhöhen
      INX         ; Leseindex erhöhen
      CMP #$20    ; Leerzeichen (Endekennzeichen)?
      BNE BE00    ; Rücksprung wenn nicht Leerzeichen
      RTS

; Mit Datassette synchronisieren
P_BF: SEI         ; Interrupts abschalten (unnötig in Interruptroutine)
      JSR P_BH    ; Ein Bit von Band einlesen
      LDA *$BD    ; Gelesenes Byte holen
      CMP *$A3    ; und mit Synchronisations-Kennzeichen vergleichen
      BNE BG00    ; Rückkehr aus Interrupt falls nicht passend
      LDA #$01    ; Speicher für gelesene Bits
      STA *$BD    ; als Zähler für 8 Bit initialisieren
      LDA #<P_BG  ; Interruptvektor auf Routine P_BG richten
      STA $0314   ; Low-Byte
      LDA #>P_BG
      STA $0315   ; High-Byte
      JMP BG00    ; Alle Register zurückholen, dann Rücksprung aus Interrupt

; Interruptroutine für Band lesen, Byte in Ringpuffer schreiben
P_BG: SEI         ; Interrupts abschalten (unnötig in Interruptroutine)
      JSR P_BH    ; Ein Bit von Band einlesen und nach $BD
      BCC BG00    ; Sprung falls noch nicht 8 Bit empfangen
      LDY *$72    ; Schreibzeiger für Ringpuffer holen
      LDA *$BD    ; Von Band gelesenes Byte holen
      STA $033C,Y ; und in 32 Byte-Ringpuffer schreiben
      LDA #$01    ; Kennzeichen für "Weitere 8 Bit einlesen"
      STA *$BD    ; in Puffer für empfangene Bits schreiben
      INY         ; Schreibzeiger erhöhen
      TYA         ; nach A übertragen
      AND #$1F    ; auf Wertebereich 0..31 begrenzen
      STA *$72    ; und zurückschreiben
BG00: JMP $FEBC   ; Alle Register zurückholen, dann Rücksprung aus Interrupt

; Bit von Band lesen, liefert CS wenn 8. Bit (vollständiges Byte empfangen)
P_BH: LDA #$19    ; Timer A von CIA 2 mit Startwert laden
      STA $DD0E   ; und im "One Shot"-Betrieb starten
      LDA $DC0D   ; Wartende Interrupts CIA 1 löschen
      LDA $DD0D   ; Wartende Interrupts CIA 2 löschen
      LSR A       ; Unterlauf Timer A von CIA 1?
      ROL *$BD    ; Unterlaufflag als empfangenes Bit merken
      RTS

; Mit Datasette synchronisieren
P_BI: SEI         ; Interrupts abschalten, bevor Interruptvektor geändert wird
      LDA #$00
      STA *$72    ; Schreibzeiger auf 32 Byte-Ringpuffer zurücksetzen
      STA *$71    ; Lesezeiger auf 32 Byte-Ringpuffer zurücksetzen
      STA *$BD
      LDA #<P_BF  ; Interruptvektor auf P_BF (bitweise Synchronisation) richten
      STA $0314   ; Low-Byte
      LDA #>P_BF
      STA $0315   ; High-Byte
      CLI         ; Interrupts wieder zulassen
BI00: JSR P_BL    ; Byte von Band aus 32 Byte-Ringpuffer nach A holen
      CMP *$A3    ; Ist Byte immer noch gleich Synchronisationskennzeichen?
      BEQ BI00    ; Weiter warten falls ja
      EOR #$FF    ; Gelesenes Byte invertieren
      CMP *$A3    ; Ist invertiertes Byte gleich Synchronisationskennzeichen?
      BNE P_BI    ; Neuer Synchronisationsversuch falls nicht
      JSR P_BL    ; Byte von Band aus 32 Byte-Ringpuffer nach A holen
      CMP *$A3    ; Mit ursprünglichem Synchronisationskennzeichen vergleichen
      BNE P_BI    ; Neuer Synchronisationsversuch falls nicht
      RTS

; 7 Byte-Blockheader in Puffer an $A9...$AF einlesen
P_BJ: LDY #$00    ; Schreibindex initialisieren
BJ00: JSR P_BL    ; Byte von Band aus 32 Byte-Ringpuffer nach A holen
      STA $00A9,Y ; und in Puffer an $A9...$AF schreiben
      INY         ; Schreibindex erhöhen
      CPY #$07    ; Schon 7 Bytes eingelesen?
      BNE BJ00    ; Rücksprung falls noch nicht
      LDA *$A9    ; Blocknummer aus Blockheader holen
      LDY #$0A    ; Schreibindex für Ausgabe auf Bildschirm
      JMP P_BB    ; und als Hexadezimalzahl an ($A4),Y darstellen

; Einen 256 Byte-Block von Band lesen und an ($AA) in Speicher schreiben
P_BK: LDY #$00
      STY *$B0    ; Prüfsumme initialisieren
BK00: JSR P_BL    ; Byte aus 32 Bit-Ringpuffer nach A holen
      STA ($AA),Y ; und an Zieladresse im Speicher schreiben
      EOR *$B0    ; und in Prüfsumme einarbeiten
      STA *$B0
      INY         ; Schreibindex erhöhen
      BNE BK00    ; Rücksprung falls noch nicht 256 Bytes gelesen
      JSR P_BL    ; Byte (Prüfsumme) aus 32 Bit-Ringpuffer nach A holen
      CMP *$B0    ; und mit errechneter Prüfsumme vergleichen
      RTS

; Byte aus 32 Byte-Ringpuffer an $033C nach A holen
P_BL: LDX *$71    ; Lesezeiger holen
      CPX *$72    ; und mit Schreibzeiger vergleichen
      BEQ P_BL    ; Rücksprung wenn gleich (kein Byte im Puffer)
      LDA $033C,X ; Byte aus Puffer holen
      PHA         ; und retten
      INX         ; Lesezeiger weiterbewegen
      TXA         ; und nach A übertragen
      AND #$1F    ; auf Wertebereich 0..31 beschränken
      STA *$71    ; und zurückschreiben
      PLA         ; Byte aus Puffer zurückholen
      RTS

; Zurückschalten auf Normalbetrieb
P_BM: LDA $D011   ; Bildschirm wieder einschalten
      ORA #$10
      STA $D011
      LDA *$01    ; Prozessorport lesen
      ORA #$20    ; Datassetten-Motor ausschalten
      STA *$C0
      STA *$01    ; und zurückschreiben
      LDA #$7F    ; Alle Interrupts von CIA 1 abschalten
      STA $DC0D
      LDA #$81    ; Unterlauf Timer A als einzige Interruptquelle von CIA 1
      STA $DC0D
      SEI         ; Interrupts abschalten, bevor Interruptvektor geändert wird
      LDA #$31    ; Interruptvektor auf normale Interruptroutine richten
      STA $0314   ; Low-Byte von $EA31
      LDA #$EA
      STA $0315   ; High-Byte von $EA31
      CLI         ; Interrupts wieder zulassen
      RTS

T000: DB $13,$05,$01,$12,$03,$08,$09,$0E,$07,$20 ; "SEARCHING "
T001: DB $0C,$0F,$01,$04,$09,$0E,$07,$20         ; "LOADING "
T002: DB $60,$02,$0C,$0F,$03,$0B,$3F,$20         ; " BLOCK? "
T003: DB $12,$05,$17,$09,$0E,$04,$60,$14,$0F,$20 ; "REWIND TO "

Zweiter Schnelllader[Bearbeiten | Quelltext bearbeiten]

Der zweite Schnelllader stellt offensichtliche eine geringfügig modifizierte Kopie des ersten dar — zahlreiche Routinen stimmen überein, und selbst die Bildschirmmeldungen sind, obwohl identisch, ein zweites Mal vorhanden. Auch die Codierung von 0- und 1-Bits ist unverändert, allerdings hat sich die Struktur des Headers geändert, der den eigentlichen Nutzdaten vorausgeht: Diese auf 4 Byte verkürzte Datenstruktur enthält nun das Synchronisationsbyte für den nachfolgenden Block und die laufende Nummer des Blocks (jeweils 1 Byte) sowie dessen Zieladresse im Speicher.

Noch auffallender ist aber eine neu eingeführte Datenstruktur im Anschluss an die Nutzdaten: Diese enthält nicht nur — wie beim ersten Schnelllader — eine Prüfsumme, sondern zudem zwischen 2 und 32 Bytes "Ballast": Daten ohne Bedeutung, die nur die Analyse der Bandimpulse erschweren sollen und von der Laderoutine überlesen und ignoriert werden. Damit dies funktioniert, gibt jeweils das erste überflüssige Byte an, wie viele weitere Bytes überlesen werden sollen.

Neuerdings an jeden Datenblock angehängt wird zudem eine Einsprungadresse, an die nach dem Laden des jeweiligen Datenblocks — und nicht erst nach dem Laden des vollständigen Programmteils — verzweigt wird. In den meisten Fällen zeigt diese Adresse wieder in den Code des Schnellladers (Label CA00).

PRG $C200

P_CA: SEI
      LDA #<CG00  ; BRK-Vektor auf CG00 richten (Register zurückholen und RTI)
      STA $0316   ; Low-Byte
      LDA #>CG00
      STA $0317   ; High-Byte
      LDA #$C1    ; Interruptvektor auf Befehl "RTI" richten
      STA $0318   ; Low-Byte
      LDA #$FE
      STA $0319   ; High-Byte
      LDA *$01    ; Prozessorport lesen
      AND #$DE    ; Datassetten-Motor dauerhaft einschalten, ROMs ausblenden
      STA *$01    ; und zurückschreiben
      LDA #$02    ; Startwert für Timer A von CIA 2 auf $0232 (560)
      STA $DD05   ; High-Byte
      LDA #$32
      STA $DD04   ; Low-Byte
      LDA #$19    ; Timer A mit Startwert laden und im "One Shot"-Modus starten
      STA $DD0E
      LDA #$7F    ; Alle Interrupts von CIA 1 abschalten
      STA $DC0D
      STA $D020   ; Bildschirmrand hellgrau
      LDA #$90    ; Einzige Interruptquelle für CIA ist Impuls von Datassette
      STA $DC0D
      LDY #$00    ; Blockzähler
      STY *$AB    ; initialisieren
      LDA #$0F    ; Synchronisationszeichen für ersten Block
      STA *$A3    ; setzen
      JSR P_CC    ; Meldung "SEARCHING " ausgeben
CA00: LDY *$AA    ; Unnötiger Code, soll nur Verwirrung stiften
      LDA ($A8),Y
      STA *$AC
      INY
      LDA ($A8),Y
      STA *$AD
      JSR P_CI    ; Mit Datasette synchronisieren
      JSR P_CJ    ; 4 Byte-Dateiheader in Puffer an $A4...$A7 einlesen
      LDA *$A5    ; Blocknummer aus Header holen
      CMP *$AB    ; und mit aktuellem Blockzähler vergleichen
      BEQ CA01    ; Sprung falls gleich (gesuchter Block gefunden)
      JSR P_CE    ; sonst Fehlermeldung "BLOCK? " ausgeben
      BEQ CA00    ; Unbedingter Sprung, neu synchronisieren und nächster Block
CA01: JSR P_CD    ; Meldung "SEARCHING " durch Meldung "LOADING " ersetzen
      JSR P_CK    ; Nächsten Programmblock einlesen
      BEQ CA02    ; Sprung falls Block samt Prüfsumme korrekt gelesen
      JSR P_CE    ; sonst Fehlermeldung "BLOCK? " ausgeben
      BEQ CA00    ; Unbedingter Sprung, neu synchronisieren und nächster Block
CA02: LDA *$A4    ; Synchronisationszeichen für nächsten Block
      STA *$A3    ; setzen
      INC *$AB    ; Blockzähler erhöhen
      LDA $D020   ; Bildschirm-Rahmenfarbe holen
      EOR #$02    ; wechseln
      STA $D020   ; und zurückschreiben
      JSR P_CL    ; Byte aus 32 Byte-Ringpuffer an $033C nach A holen
      STA *$AC    ; und als Low-Byte der Einsprungadresse merken
      JSR P_CL    ; Byte aus 32 Byte-Ringpuffer an $033C nach A holen
      STA *$AD    ; und als High-Byte der Einsprungadresse merken
      JMP ($00AC) ; Nächsten Programmteil anspringen ($C242=BA00 oder $BF00=P_DA)

; Inhalt von <A> als Hexadezimalzahl an $BE00,Y darstellen
P_CB: PHA         ; Wert von A retten
      LSR A       ; High Nibble isolieren
      LSR A
      LSR A
      LSR A
      JSR CB00    ; und ausgeben
      PLA         ; Wert von A zurückholen
      INY         ; Schreibindex für Bildschirm erhöhen
      AND #$0F    ; Low Nibble isolieren
CB00: ORA #$30    ; Nibble in Bereich '0'...'9' bringen
      CMP #$3A    ; Größer als '9'?
      BCC CB01    ; Sprung falls nicht
      SBC #$39    ; sonst in Bereich 'A'...'F' bringen
CB01: STA $BE00,Y ; und ausgeben
      RTS

; Meldung "SEARCHING " auf Bildschirm ausgeben
P_CC: LDX #T100-T100 ; Zeiger auf Bildschirmmeldung "SEARCHING "
      LDY #$28    ; Schreibindex für Bildschirm (zweite Zeile)
      JMP CE00    ; Bildschirmmeldung ausgeben

; Meldung "SEARCHING " durch Meldung "LOADING " ersetzen
P_CD: LDY #$28    ; Schreibindex für Bildschirm (zweite Zeile)
      LDA #$20    ; ' '
CD00: STA $BE00,Y ; Meldung "SEARCHING " in zweiter Zeile löschen
      INY         ; Schreibindex erhöhen
      CPY #$35    ; Schon ganze Meldung gelöscht?
      BNE CD00    ; Rücksprung falls noch nicht
      LDX #T101-T100 ; Zeiger auf Bildschirmmeldung "LOADING "
      LDY #$02    ; Schreibindex für Bildschirm
      JMP CE00    ; Bildschirmmeldung ausgeben

; Fehlermeldung " BLOCK? " und ggf. "REWIND TO " ausgeben
P_CE: LDX #T102-T100 ; Zeiger auf Bildschirmmeldung " BLOCK? "
      LDY #$02    ; Schreibindex für Bildschirm
      JSR CE00    ; Bildschirmmeldung ausgeben
      LDA *$AB    ; Nummer des gesuchten Blocks holen
      LDY #$32    ; Schreibindex für Bildschirm
      JSR P_CB    ; und als Hexadezimalzahl ausgeben
      LDA *$A5    ; Blocknummer aus Blockheader holen
      CMP *$AB    ; mit Nummer des gesuchten Blocks vergleichen
      BCC P_CC    ; Weitersuchen falls kleiner
      LDX #T003-T000 ; Zeiger auf Bildschirmmeldung "REWIND TO "
      LDY #$28    ; Schreibindex für Bildschirm
CE00: LDA T100,X  ; Meldung byteweise lesen
      STA $BE00,Y ; und auf Bildschirm schreiben
      INY         ; Schreibindex erhöhen
      INX         ; Leseindex erhöhen
      CMP #$20    ; Leerzeichen (Endekennzeichen)?
      BNE CE00    ; Rücksprung wenn nicht Leerzeichen
      RTS

; Mit Datassette synchronisieren
P_CF: SEI         ; Interrupts abschalten (unnötig in Interruptroutine)
      JSR P_CH    ; Ein Bit von Band einlesen
      LDA *$BD    ; Gelesenes Byte holen
      CMP *$A3    ; und mit Synchronisations-Kennzeichen vergleichen
      BNE CG00    ; Rückkehr aus Interrupt falls nicht passend
      LDA #$01    ; Speicher für gelesene Bits
      STA *$BD    ; als Zähler für 8 Bit initialisieren
      LDA #<P_CG  ; Interruptvektor auf Routine P_BG richten
      STA $0314   ; Low-Byte
      LDA #>P_CG
      STA $0315   ; High-Byte
      JMP CG00    ; Alle Register zurückholen, dann Rücksprung aus Interrupt

; Interruptroutine für Band lesen, Byte in Ringpuffer schreiben
P_CG: SEI         ; Interrupts abschalten (unnötig in Interruptroutine)
      JSR P_CH    ; Ein Bit von Band einlesen und nach $BD
      BCC CG00    ; Sprung falls noch nicht 8 Bit empfangen
      LDY *$72    ; Schreibzeiger für Ringpuffer holen
      LDA *$BD    ; Von Band gelesenes Byte holen
      STA $033C,Y ; und in 32 Byte-Ringpuffer schreiben
      LDA #$01    ; Kennzeichen für "Weitere 8 Bit einlesen"
      STA *$BD    ; in Puffer für empfangene Bits schreiben
      INY         ; Schreibzeiger erhöhen
      TYA         ; nach A übertragen
      AND #$1F    ; auf Wertebereich 0..31 begrenzen
      STA *$72    ; und zurückschreiben
CG00: JMP $FEBC   ; Alle Register zurückholen, dann Rücksprung aus Interrupt

; Bit von Band lesen, liefert CS wenn 8. Bit (vollständiges Byte empfangen)
P_CH: LDA #$19    ; Timer A von CIA 2 mit Startwert laden
      STA $DD0E   ; und im "One Shot"-Betrieb starten
      LDA $DC0D   ; Wartende Interrupts CIA 1 löschen
      LDA $DD0D   ; Wartende Interrupts CIA 2 löschen
      LSR A       ; Unterlauf Timer A von CIA 1?
      ROL *$BD    ; Unterlaufflag als empfangenes Bit merken
      RTS

; Mit Datasette synchronisieren
P_CI: SEI         ; Interrupts abschalten, bevor Interruptvektor geändert wird
      LDA #$00
      STA *$72    ; Schreibzeiger auf 32 Byte-Ringpuffer zurücksetzen
      STA *$71    ; Lesezeiger auf 32 Byte-Ringpuffer zurücksetzen
      STA *$BD    ; Speicherzelle für Bits vom Band initialisieren
      LDA #<P_CF  ; Interruptvektor auf P_CF (bitweise Synchronisation) richten
      STA $0314   ; Low-Byte
      LDA #>P_CF
      STA $0315   ; High-Byte
      CLI         ; Interrupts wieder zulassen
CI00: JSR P_CL    ; Byte von Band aus 32 Byte-Ringpuffer nach A holen
      CMP *$A3    ; Ist Byte immer noch gleich Synchronisationskennzeichen?
      BEQ CI00    ; Weiter warten falls ja
      EOR #$FF    ; Gelesenes Byte invertieren
      CMP *$A3    ; Ist invertiertes Byte gleich Synchronisationskennzeichen?
      BNE P_CI    ; Neuer Synchronisationsversuch falls nicht
      JSR P_CL    ; Byte von Band aus 32 Byte-Ringpuffer nach A holen
      CMP *$A3    ; Mit ursprünglichem Synchronisationskennzeichen vergleichen
      BNE P_CI    ; Neuer Synchronisationsversuch falls nicht
      RTS

; 4 Byte-Dateiheader in Puffer an $A4...$A7 einlesen
P_CJ: LDY #$00    ; Schreibindex initialisieren
      STY *$A8    ; Prüfsumme initialisieren
CJ00: JSR P_CL    ; Byte von Band aus 32 Byte-Ringpuffer nach A holen
      STA $00A4,Y ; und in Puffer an $A4...$A7 schreiben
      INY         ; Schreibindex erhöhen
      CPY #$04    ; Schon 4 Bytes eingelesen?
      BNE CJ00    ; Rücksprung falls noch nicht
      PLA         ; Low-Byte der Rücksprungadresse-1 zurückholen
      STA *$AC    ; und merken
      PLA         ; High-Byte der Rücksprungadresse-1 zurückholen
      STA *$A9    ; und merken (für Entschlüsseln an DA01/DA02)
      PHA         ; und wieder auf Stack
      LDA *$AC    ; Low-Byte der Rücksprungadresse-1
      PHA         ; auch wieder auf Stack
      LDA *$A5    ; Blocknummer aus Header holen
      LDY #$0A    ; Schreibindex für Ausgabe auf Bildschirm
      JMP P_CB    ; als Hexadezimalzahl an $BE00,Y darstellen

; Einen 64/256 Byte-Block von Band lesen und an ($A6) in Speicher schreiben
P_CK: LDY #$00
      STY *$AA    ; Prüfsumme initialisieren
      STY *$AE    ; Zahl der zu lesenden Bytes auf 256 setzen
      LDA *$A5    ; Nummer des aktuellen Blocks holen
      BNE CK00    ; Sprung falls nicht Block 0
      LDA #$40    ; sonst Zahl der zu lesenden Bytes auf 64 setzen
      STA *$AE    ;
CK00: JSR P_CL    ; Byte aus 32 Byte-Ringpuffer an $033C nach A holen
      STA ($A6),Y ; und an Zieladresse im Speicher schreiben
      EOR *$AA    ; und in Prüfsumme einarbeiten
      STA *$AA
      INY         ; Schreibindex erhöhen
      CPY *$AE    ; und mit Anzahl zu lesender Bytes vergleichen
      BNE CK00
      JSR P_CL    ; Byte aus 32 Byte-Ringpuffer an $033C nach A holen
      TAY         ; und als Zahl zu überlesender Füllbytes nach Y
CK01: JSR P_CL    ; Byte aus 32 Byte-Ringpuffer an $033C nach A holen
      DEY         ; Schon alle Füllbytes überlesen?
      BNE CK01    ; Sprung falls nicht
      JSR P_CL    ; Prüfsumme aus 32 Byte-Ringpuffer an $033C nach A holen
      CMP *$AA    ; und mit errechneter Prüfsumme vergleichen
      RTS

; Byte aus 32 Byte-Ringpuffer an $033C nach A holen
P_CL: LDX *$71    ; Lesezeiger holen
      CPX *$72    ; und mit Schreibzeiger vergleichen
      BEQ P_CL    ; Rücksprung wenn gleich (kein Byte im Puffer)
      LDA $033C,X ; Byte aus Puffer holen
      PHA         ; und retten
      INX         ; Lesezeiger weiterbewegen
      TXA         ; und nach A übertragen
      AND #$1F    ; auf Wertebereich 0..31 beschränken
      STA *$71    ; und zurückschreiben
      PLA         ; Byte aus Puffer zurückholen
      RTS

T100: DB $13,$05,$01,$12,$03,$08,$09,$0E,$07,$20 ; "SEARCHING "
T101: DB $0C,$0F,$01,$04,$09,$0E,$07,$20         ; "LOADING "
T102: DB $60,$02,$0C,$0F,$03,$0B,$3F,$20         ; " BLOCK? "
T103: DB $12,$05,$17,$09,$0E,$04,$60,$14,$0F,$20 ; "REWIND TO "

Abschließende Decodierung und Start des Spiels[Bearbeiten | Quelltext bearbeiten]

Diese letzte, kurze Routine findet sich im letzten Datenblock auf Band und wird unmittelbar nach dem Laden angesprungen. Sie ist im Adressbereich $BF20–$BFFF zunächst noch verschlüsselt und decodiert sich unmittelbar vor der Ausführung durch XOR-Verknüpfung mit dem Programmcode des Schnellladers — im folgenden Listing ist die Routine in bereits decodierter Form wiedergegeben. Anschließend entschlüsselt sie auch noch große Teile des soeben geladenen Programmcodes, indem sie ihn mehrfach mit Codeausschnitten des zweiten Schnellladers XOR-verknüpft.

An Komplexität nur noch schwerlich zu übertreffen dürfte die Methode sein, wie die Routine schließlich das Spiel startet: Zwischen den Labels DA01 und DA02 biegt sie den BREAK-Vektor auf den Codabschnitt an P_DB um, und überschreibt sich anschließend bei DA04 selbst mit Nullbytes — also dem Opcode für den BRK-Befehl. Sobald hierbei der Anfang der Schleife überschrieben wird, leitet die erste Ausführung einer BRK-Instruktion den Programmfluss nach P_DB um — von wo aus ein indirekter Sprung über den soeben von Band nachgeladenen Adresszeiger an $79/$7A schließlich zum Programmanfang an Adresse $0847 verzweigt.

PRG $BF00

; 8 zusätzliche Bytes nachladen und mit ihrer Hilfe Programmcode entschlüsseln
P_DA: LDY #$00    ; Schreibindex initialisieren
DA00: JSR P_CL    ; Byte aus 32 Byte-Ringpuffer an $033C nach A holen
      STA $0073,Y ; und an Adresse $0073...$007A schreiben
      INY         ; Schreibindex erhöhen
      CPY #$08    ; Schon 8 Byte von Band gelesen
      BNE DA00    ; Rücksprung falls noch nicht 8 Byte
; Inhalt von $0073...$007A: $00,$02,$00,$40,$00,$BD,$47,$08
; Code an $BF20..$BFFF durch EOR-Verknüpfung mit Schnelllader-Code entschlüsseln
      LDY #$20
DA01: LDA $BF00,Y
      EOR ($A8),Y ; Entspricht "EOR $C200,Y"
      STA $BF00,Y
      INY
      BNE DA01
; Ab hier schon entschlüsselter Code
      SEI         ; Interrupts abschalten, bevor Interruptvektor geändert wird
      LDA #$2F    ; Datenrichtungsregister des Prozessorports auf Standardwert
      STA *$00
      LDA #$7F    ; Alle Interrupts von CIA 1 abschalten
      STA $DC0D
      LDA #<P_DB  ; BREAK-Vektor auf P_DB richten
      STA $0316   ; Low-Byte
      LDA #>P_DB
      STA $0317   ; High-Byte
      LDA #$C1    ; Interruptvektor auf Befehl "RTI" richten
      STA $0318   ; Low-Byte
      LDA #$FE
      STA $0319   ; High-Byte
; Code im Bereich $4000...$BCFF entschüsseln
DA02: LDA ($75),Y ; Verschlüsseltes Byte lesen
      EOR ($A8),Y ; Entspricht "EOR $C200,Y"
      INC *$A9
      EOR ($A8),Y ; Entspricht "EOR $C300,Y"
      DEC *$A9
      STY *$7B    ; Schreibindex merken
      PHA         ; A auf Stack retten
      TYA         ; Y auf Wertebereich 0..63 beschränken
      AND #$3F
      TAY
      PLA         ; A zurückholen
      EOR ($73),Y ; Entspricht "EOR $C200,Y" mit Y im Bereich 0..63
      LDY *$7B    ; Schreibindex zurückholen
      STA ($75),Y ; Entschlüsseltes Byte zurückschreiben
      INY         ; Schreibindex erhöhen
      BNE DA02    ; Rücksprung falls noch nicht 256 Bytes entschlüsselt
      INC *$76    ; High-Byte des Lesezeigers erhöhen
      LDA *$76    ; und nach A holen
      CMP *$78    ; Ende des Programmcode erreicht?
      BNE DA02    ; Sprung falls Ende noch nicht erreicht
      LDA $D011   ; Bildschirm wieder einschalten
      ORA #$10
      STA $D011
      LDA *$01    ; Datassettenmotor anhalten
      ORA #$20
      STA *$C0    ; Flag für Bandmotor merken
      STA *$01
      LDA #$81    ; Unterlauf Timer A als einzige Interruptquelle von CIA 1
      STA $DC0D
      LDA *$01    ; Prozessorport lesen
      AND #$FD    ; alle ROMs ausblenden
      STA *$01    ; und zurückschreiben
      LDY #$00    ; Für Umkopieren von 8 kByte Programmcode
      STY *$73    ; Low-Byte des Schreibzeigers auf 0 setzen
      STY *$75    ; Low-Byte des Lesezeigers auf 0 setzen
      LDA #$20    ; Schreibzeiger auf $2000 richten
      STA *$74
      LDA #$E0    ; Lesezeiger auf $E000 richten
      STA *$76
DA03: LDA ($75),Y ; Speicherbereich $E000...$FFFF nach $2000...$3FFF umkopieren
      STA ($73),Y
      INY         ; Schreibindes erhöhen
      BNE DA03    ; Rücksprung falls noch nicht 256 Bytes
      INC *$74    ; High-Byte des Schreibzeigers erhöhen
      INC *$76    ; High-Byte des Lesezeigers erhöhen
      BNE DA03    ; Rücksprung falls noch nicht ganzer Speicherbereich kopiert
      LDA *$01    ; Prozessorport lesen
      ORA #$02    ; ROMs wieder in Adressraum einblenden
      STA *$01    ; und zurückschreiben
; Aktuell ausgeführten Code mit $00 überschreiben, löst BRK (Sprung nach P_DB) aus
      LDY #$00    ; Schreibindex
      TYA         ; A=$00
DA04: STA $BF00,Y ; Speicher mit $00 überschreiben
      INY         ; Schreibindex erhöhen
      BNE DA04    ; Weiter kopieren (löst BRK aus, sobald Y=DA04+$01)


PRG $BFF3

P_DB: PLA         ; Nicht mehr benötigte Rücksprungadressen von Stack löschen
      PLA
      PLA
      PLA
      PLA
      PLA
      JSR $FF8A   ; I/O initialisieren
      CLI         ; Interrupts wieder zulassen
      JMP ($0079) ; Start des Spiels durch Sprung nach $0847