Spiky Harold/Schnelllader

Aus C64-Wiki
Zur Navigation springenZur Suche springen

<< zurück zu Spiky Harold


Spiky Harold/Schnelllader: Die folgenden Abschnitte stellen den disassemblierten Kassetten-Schnelllader des Spiels Spiky Harold 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 "Spiky Harold" 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 512 Byte langes, namenloses Programm nach, das den ersten von zwei etwa gleich langen und gleich leistungsfähigen Schnellladern enthält. Anschließend werden drei Datenblöcke mit Hilfe des ersten Schnellladers von Kassette eingelesen, bevor die Kontrolle zunächst an eine Decodierroutine und dann an die zweite Laderoutine übergeben wird.

ORG $02A7

; Autostart-Routine, ersten Schnelllader nachladen
P_AA: LDA #$0E    ; Hintergrundfarbe hellblau
      STA $D021
      LDA #$40    ; Status setzen
      JSR $FF90
      LDA #$01    ; Logische Filenummer=$01
      TAX         ; Gerätenummer=$01
      TAY         ; Sekundäradresse=$01
      JSR $FFBA   ; Fileparameter setzen
      LDA #$00    ; Filenamenlänge=$00
      JSR $FFBD   ; Filenamenparameter setzen
      JSR $FFD5   ; LOAD
      LDA #$93    ; ASCII-Code für "Bildschirm löschen"
      JSR $FFD2   ; Ausgabe eines Zeichens
      LDX #$00    ; Hintergrundfarbe schwarz
      STX $D021
      LDA #$01    ; Zeichenfarbe weiß
AA00: STA $D900,X ; Zweite Page des Farb-RAM füllen
      INX
      BNE AA00
      JMP P_BA    ; Zum nachgeladenen Schnellader springen

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

      DW $E38B    ; Vektor für BASIC-Warmstart
      DW P_AA     ; Vektor für Eingabe einer Zeile, umgebogen auf Laderoutine

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 das Synchronisationsbyte für den nachfolgenden Block und die laufende Nummer des Blocks (jeweils 1 Byte) sowie die Zieladresse des Speicher (2 Byte im Little Endian-Format) enthält.

Auffallend ist zudem eine Datenstruktur im Anschluss an die Nutzdaten: Diese enthält nicht nur 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. Ferner folgt noch eine Programmadresse, die nach dem Laden des Blocks angesprungen werden soll, diese zeigt jeweils auf das Label BA00 (Adresse $C741) und damit auf den Schnelllader selbst.

Die Unterscheidung von 0- und 1-Bits geschieht in Routine P_BF mit Hilfe des Timers A der CIA 2. Dieser zählt für jedes Bit, beginnend bei einem Startwert von 562, 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). Interessanterweise werden die daraus zusammengesetzten Bytes nicht direkt in den Hauptspeicher übertragen, sondern landen zunächst in einem 32 Byte langen Ringpuffer, wo sie von Routine P_BJ abgeholt werden können. Da die nachzuladenden Daten zu 50,1% aus 0-Bits und zu 49,9% aus 1-Bits bestehen, dauert das Einlesen eines Byte vom Band in dieser Phase durchschnittlich 4413 Systemtakte (fast 4,5 ms).

PRG $C700

; Erster Schnelllader (lädt verschlüsselten Programmblock $0C00-$C1FF)
P_BA: SEI
      LDA #<BE00  ; IRQ-Vektor zeigt auf P_BE
      STA $0316   ; Low-Byte
      LDA #>BE00
      STA $0317   ; High-Byte
      LDA #$C1    ; NMI-Vektor zeigt auf "RTI"
      STA $0318   ; Low-Byte
      LDA #$FE
      STA $0319   ; High-Byte
      LDA *$01    ; BASIC-ROM ausblenden
      AND #$DE
      STA *$01
      LDA #$02    ; Startwert für CIA 2 Timer A auf $0232 (562)
      STA $DD05   ; High-Byte
      LDA #$32
      STA $DD04   ; Low-Byte
      LDA #$19    ; CIA 2 Timer A laden und im "one shot"-Modus starten
      STA $DD0E
      LDA #$7F    ; Alle Interrupts von CIA 1 abschalten
      STA $DC0D
      LDA #$91    ; CIA 1 Interrupt bei Unterlauf Timer A und Impuls an Pin FLAG aktivieren
      STA $DC0D
      LDY #$00
      STY *$AF    ; Kein Interrupt von CIA 1 Timer A aufgetreten
      STY *$AB    ; Als nächstes Block $00 laden
      LDA #$0F    ; Erstes Synchronisationszeichen
      STA *$A3    ; setzen
      JSR P_BC    ; Meldung "SEARCHING" ausgeben
BA00: LDY *$AA    ; Überflüssige Initialisierungen
      LDA ($A8),Y
      STA *$AC
      INY
      LDA ($A8),Y
      STA *$AD
      JSR P_BG    ; Byteweise Synchronisation
      JSR P_BH    ; Header von Band lesen
      LDA *$A5    ; Gelesene Blocknummer
      CMP *$AB    ; mit gesuchter Blocknummer vergleichen
      BEQ BA01    ; Sprung falls gleich
      JSR BC02    ; sonst "BLOCK?" ausgeben
      BEQ BA00    ; Unbedingter Sprung
BA01: JSR BC00    ; Meldung "LOADING" ausgeben
      JSR P_BI    ; Einen Datenblock einlesen
      BEQ BA02    ; Sprung, falls Prüsumme korrekt
      JSR BC02    ; sonst "BLOCK?" ausgeben
      BEQ BA00    ; Unbedingter Sprung
BA02: LDA *$A4
      STA *$A3
      INC *$AB    ; Blocknummer erhöhen
      INC $D020   ; Rahmenfarbe erhöhen
      JSR P_BJ    ; Ein Datenbyte von Band aus Ringpuffer holen
      STA *$AC    ; und als Low-Byte der Sprungadresse merken
      JSR P_BJ    ; Ein Datenbyte von Band aus Ringpuffer holen
      STA *$AD    ; und als High-Byte der Sprungadresse merken
      JMP ($00AC) ; Über den gerade gelesenen Sprungvektor springen

; A als Hexadezimalzahl auf Bildschirm ausgeben
P_BB: PHA         ; A auf Stack retten
      LSR A       ; High-Nibble in Bit 0..3
      LSR A
      LSR A
      LSR A
      JSR BB00    ; und als Hexadezimalziffer ausgeben
      PLA         ; A von Stack zurückholen
      INY         ; Schreibindex erhöhen
      AND #$0F    ; Low-Nibble in Bit 0..3
BB00: ORA #$30    ; "0"
      CMP #$3A    ; War Nibble im Bereich $0A...$0F?
      BCC BB01    ; Sprung falls nicht
      SBC #$39    ; sonst durch Zeichen "A"..."F" ersetzen
BB01: STA $05C4,Y ; Zeichen auf Bildschirm ausgeben
      RTS

; Bildschirmmeldungen ausgeben
;  Einsprung für "SEARCHING "
P_BC: LDX #T000-T000 ; "SEARCHING "
      LDY #$28       ; Schreibindex für Bildschirmspeicher
      JMP BC03       ; Meldung ausgeben
;  Einsprung für "LOADING"
BC00: LDY #$28       ; Schreibindex für Löschen eines Bildschirmbereichs
      LDA #$20       ; Leerzeichen
BC01: STA $05C4,Y    ; Bildschirmbereich löschen
      INY            ; Schreibindex erhöhen
      CPY #$35       ; Schon ganzer Bereich gelöscht?
      BNE BC01       ; Rücksprung falls noch nicht
      LDX #T001-T000 ; "LOADING "
      LDY #$02       ; Schreibindex für Bildschirmspeicher
      JMP BC03       ; Meldung ausgeben
; Einsprung für "BLOCK? "
BC02: LDX #T002-T000 ; "BLOCK? "
      LDY #$02       ; Schreibindex für Bildschirmspeicher
      JSR BC03       ; Meldung ausgeben
      LDA *$AB       ; Gesuchte Blocknummer nach A laden
      LDY #$32       ; Schreibindex für Bildschirmspeicher
      JSR P_BB       ; Blocknummer als Hexadezimalzahl auf Bildschirm ausgeben
      LDA *$A5       ; Gelesene Blocknummernach A holen
      CMP *$AB       ; und mit gesuchter Blocknummer vergleichen
      BCC P_BC       ; Gesuchte Blocknummer ist größer, dann "SEARCHING " ausgeben
      LDX #T003-T000 ; sonst "REWIND TO " ausgeben
      LDY #$28       ; Schreibindex für Bildschirmspeicher
; Meldung auf Bildschirm ausgeben
BC03: LDA T000,X     ; Zeichen aus Hauptspeicher lesen
      STA $05C4,Y    ; und auf Bildschirm ausgeben
      INY            ; Schreibindex erhöhen
      INX            ; Leseindex erhöhen
      CMP #$20       ; Wortende erreicht?
      BNE BC03       ; Weiter umkopieren, falls noch nicht
      RTS

; Bitweise Synchronisation: Bit von Band lesen und auf Synchronisationszeichen prüfen
P_BD: SEI
      JSR P_BF    ; Ein Bit von Band lesen
      LDA *$BD    ; Letzte 8 gelesene Bits holen
      CMP *$A3    ; und mit gesuchtem Synchronisationszeichen vergleichen
      BNE BE00    ; Rückkehr aus Interrupt, falls nicht gefunden
      LDA #$01    ; Schieberegister für ganzes Datenbyte initialisieren
      STA *$BD
      LDA #<P_BE  ; Interruptvektor auf P_BE richten
      STA $0314   ; Low-Byte
      LDA #>P_BE
      STA $0315   ; High-Byte
      JMP BE00    ; Rückkehr aus Interrupt

; Ein Bit von Band lesen; ggf. vollständiges Byte in Ringpuffer umkopieren
P_BE: SEI         ; Interrupts deaktivieren (nicht nötig in Interruptroutine)
      JSR P_BF    ; Ein Bit von Band lesen und nach $BD
      BCC BE00    ; Sprung falls noch nicht 8 Bit gelesen
      LDY *$72    ; Schreibindex nach Y holen
      LDA *$BD    ; Gelesenes Byte nach A holen
      STA $033C,Y ; Gelesenes Byte in Ringpuffer schreiben
      LDA #$01    ; Schieberegister für gelesene Bits wieder initialisieren
      STA *$BD
      INY         ; Schreibindex für Ringpuffer weiterbewegen
      TYA         ; auf Wertebereich $00...$1F begrenzen
      AND #$1F
      STA *$72    ; und wieder merken
BE00: JMP $FEBC   ; Rückkehr aus Interrupt

; Ein Bit von Band lesen und von rechts in Adresse $BD schieben
P_BF: LDA $DC0D   ; Interruptregister von CIA 1 nach A holen
      PHA         ; und auf Stack retten
      AND #$01    ; Flag für "Unterlauf CIA 1 Timer A" isolieren
      ORA *$AF    ; und an Adresse $AF merken
      STA *$AF
      PLA         ; Interruptregister von CIA 1 zurückholen
      CLC         ; "Byte noch nicht vollständig empfangen" vormerken
      AND #$10    ; Impuls von CIA 1 Pin FLAG empfangen?
      BEQ BF00    ; Sprung falls kein Impuls vom Band
      LDA #$19    ; CIA 2 Timer A neu laden und im "one shot"-Modus starten
      STA $DD0E
      LDA $DD0D   ; Interruptregister von CIA 2 nach A holen
      LSR A       ; CIA 2 Timer A abgelaufen? ja=1-Bit, nein=0-Bit empfangen
      ROL *$BD    ; Empfangenes Bit von rechts in Adresse $BD schieben
BF00: RTS         ; CC: Byte noch nicht vollständig, CS: Byte vollständig

; Byteweise Synchronisation: Bytes von Band lesen und auf Synchronisationszeichen prüfen
P_BG: SEI         ; Interrupts verbieten, da Interruptvektor geändert wird
      LDA #$00
      STA *$72    ; Schreibindex für Ringpuffer zurücksetzen
      STA *$71    ; Leseindex für Ringpuffer initialisieren
      STA *$BD    ; Puffer für eingelesene Bits von Band initialisieren
      LDA #<P_BD  ; IRQ-Vektor zeigt auf P_BD
      STA $0314   ; Low-Byte
      LDA #>P_BD
      STA $0315   ; High-Byte
      CLI         ; Interrupts wieder zulassen
BG00: JSR P_BJ    ; Ein Datenbyte von Band aus Ringpuffer holen
      CMP *$A3    ; und mit Synchronisationszeichen vergleichen
      BEQ BG00    ; überlesen falls gleich
      EOR #$FF    ; sonst gelesenes Datenbyte invertieren
      CMP *$A3    ; und mit Synchronisationszeichen vergleichen
      BNE P_BG    ; Neustart der bitweisen Synchronisation, falls ungleich
      JSR P_BJ    ; Ein Datenbyte von Band aus Ringpuffer holen
      CMP *$A3    ; und mit Synchronisationszeichen vergleichen
      BNE P_BG    ; Neustart der bitweisen Synchronisation, falls ungleich
      RTS

; 4 Byte-Header von Band lesen und nach $A4...$A7
P_BH: LDY #$00
      STY *$A8    ; Low-Byte des Adresszeigers für Entschlüsselung initialisieren
BH00: JSR P_BJ    ; Headerbyte von Band aus Ringpuffer holen
      STA $00A4,Y ; und an $A4...$A7 merken
      INY         ; Schreibindex erhöhen
      CPY #$04    ; Schon 4 Byte von Band gelesen?
      BNE BH00    ; Rücksprung falls noch nicht
      PLA         ; Rücksprungadresse (Low-Byte) vom Stack holen
      STA *$AC    ; und an $AC merken
      PLA         ; Rücksprungadresse (High-Byte) vom Stack holen
      STA *$A9    ; und an $A9 merken
      PHA         ; Rücksprungadresse (High-Byte) zurück auf Stack
      LDA *$AC    ; Rücksprungadresse (Low-Byte)
      PHA         ; zurück auf Stack
      LDA *$A5    ; Blocksnummer nach A holen
      LDY #$0A    ; Schreibzeiger für Bildschirmausgabe (hinter "LOADING")
      JMP P_BB    ; Blocknummer als Hexadezimalzahl ausgeben

; Einen Datenblock von Band einlesen
P_BI: LDY #$00
      STY *$AA    ; Prüfsumme initialisieren
      STY *$AE    ; Blockgröße (Endwert für Schreibindex) auf $00 initialisieren
      LDA *$A5    ; Blockzähler lesen
      BNE BI00    ; Sprung falls nicht erster Block
      LDA #$40    ; Sonst Blockgröße (Endwert für Schreibindex) auf $40 initialisieren
      STA *$AE
BI00: JSR P_BJ    ; Ein Datenbyte von Band aus Ringpuffer holen
      STA ($A6),Y ; in Hauptspeicher schreiben
      EOR *$AA    ; und in Prüfsumme einarbeiten
      STA *$AA
      INY         ; Schreibindex erhöhen
      CPY *$AE    ; Blockende schon erreicht?
      BNE BI00    ; Rücksprung falls noch nicht
      JSR P_BJ    ; Ein Datenbyte von Band aus Ringpuffer holen
      TAY         ; und als Anzahl zu überlesender Dummy-Bytes merken
BI01: JSR P_BJ    ; Ein Datenbyte von Band aus Ringpuffer holen
      DEY         ; Schon alle Dummy-Bytes überlesen?
      BNE BI01    ; Rücksprung falls noch nicht
      JSR P_BJ    ; Prüfsumme von Band aus Ringpuffer holen
      CMP *$AA    ; und mit berechneter Prüfsumme vergleichen
      RTS

; Ein Datenbyte aus Ringpuffer nach A holen
P_BJ: STY *$B0    ; Y zwischenspeichern
BJ00: LDA $0312   ; High-Byte des USR-Vektor nach A holen
      CMP #$B2    ; Noch auf Defaultwert ($B248, "?ILLEGAL QUANTITY ERROR")?
      BEQ BJ01    ; Sprung falls ja
      LDA *$AF    ; Timerinterrupt statt Interrupt durch Bandsignal?
      BEQ BJ01    ; Sprung falls nicht
      LDA #$00    ; sonst Flag für Timerinterrupt löschen
      STA *$AF
      JSR P_BK    ; und über Sprungvektor an $0311 springen
BJ01: LDX *$71    ; Leseindex für Ringpuffer mit Banddaten holen
      CPX *$72    ; und mit Schreibzeiger vergleichen
      BEQ BJ00    ; Rücksprung falls gleich, dann noch keine neuen Daten
      LDA $033C,X ; Von Band gelesene Datenbytes aus Ringpuffer holen
      PHA         ; und auf den Stack retten
      INX         ; Leseindex für Ringpuffer weiterbewegen
      TXA         ; auf Wertebereich $00..$1F begrenzen
      AND #$1F
      STA *$71    ; und wieder abspeichern
      PLA         ; Gelesenes Datenbyte zurückholen
      LDY *$B0    ; Y zurückholen
      RTS

; Start des Programms über USR-Vektor (nicht verwendet)
P_BK: JMP ($0311) ; Sprung über geänderten USR-Vektor


; "SEARCHING "
T000: DB $13,$05,$01,$12,$03,$08,$09,$0E,$07,$20

; "LOADING "
T001: DB $0C,$0F,$01,$04,$09,$0E,$07,$20

; " BLOCK? "
T002: DB $60,$02,$0C,$0F,$03,$0B,$3F,$20

; "REWIND TO"
T003: DB $12,$05,$17,$09,$0E,$04,$60,$14,$0F,$20

Decodier-Routine[Bearbeiten | Quelltext bearbeiten]

Diese Routine ist im letzten Datenblock enthalten, den der erste Schnelllader von Kassette liest. Sie wird unmittelbar nach dem Laden des ersten Programmabschnitts aufgerufen, liest noch einen Kontrollblock mit ingesamt 8 Bytes Länge von Band (unter anderem mit der Startadresse des Spiels) und decodiert dann zunächst sich selbst (ab Adresse $0820, durch XOR-Verknüpfung mit dem Code des ersten Schnellladers im Bereich $C700...$C7FF). Anschließend entschlüsselt sie den zuvor in den Bereich $0C00...$C1FF geladenen Programmabschnitt (durch sukzessive XOR-Verknüpfung mit dem Code des ersten Schnellladers in den Bereichen $C700...$C7FF und $C800...$C8FF und dem zusätzlichen Schlüssel an $0200...$023F) und startet dann per Unterprogrammaufruf den im Speicherbereich $C000...$C1FF abgelegten zweiten Schnelllader. Anschließend startet sie das Spiel durch einen indirekten Sprung an die im Kontrollblock enthaltene Startadresse $7000.

PRG $0800

; Entschlüsselung des ersten Programmblocks
P_CA: LDY #$00    ; Schreibindex für Adresszeiger
CA00: JSR P_BJ    ; Ein Datenbyte von Band aus Ringpuffer holen
      STA $0073,Y ; und als Adresszeiger abspeichern
      INY         ; Schreibindex erhöhen
      CPY #$08    ; und mit Anzahl der zu lesenden Datenbytes vergleichen
      BNE CA00    ; Rücksprung falls noch nicht alle Datenbytes gelesen
      LDY #$20    ; Entschlüsselungsroutine im Bereich $0820...$08FF decodieren
CA01: LDA $0800,Y ; Verschlüsseltes Byte holen
      EOR ($A8),Y ; Entspricht "EOR $C700,Y"
      STA $0800,Y ; und entschlüsseltes Byte zurückschreiben
      INY         ; Lese- und Schreibindex erhöhen
      BNE CA01    ; Rücksprung falls noch nicht alle Bytes entschlüsselt
      SEI         ; Interrupts verbieten
      LDA #$2F
      STA *$00
      LDA #$7F    ; Alle Interrupts von CIA 1 deaktivieren
      STA $DC0D
      LDA #$C1    ; NMI-Vektor auf "RTI" richten
      STA $0318   ; Low-Byte
      LDA #$FE
      STA $0319   ; High-Byte
; Programmcode im Bereich $0C00...$C1FF decodieren
CA02: LDA ($75),Y ; Verschlüsseltes Programmbyte holen
      EOR ($A8),Y ; Entspricht "EOR $C700,Y"
      INC *$A9    ; High-Byte des Adresszeigers für Verschlüsselung erhöhen
      EOR ($A8),Y ; Entspricht "EOR $C800,Y"
      DEC *$A9    ; High-Byte des Adresszeigers für Verschlüsselung erniedrigen
      STY *$7B    ; Lese- und Schreibindex Y merken
      PHA         ; Programmbyte in A auf Stack retten
      TYA         ; Y auf Bereich $00..$3F einschränken
      AND #$3F
      TAY
      PLA         ; Programmbyte von Stack in A zurückholen
      EOR ($73),Y ; mit zusätzlichem Schlüssel an $0200...$023F XOR-verknüpfen
      LDY *$7B    ; Lese- und Schreibindex Y zurückholen
      STA ($75),Y ; Entschlüsseltes Programmbyte zurückschreiben
      INY         ; Lese- und Schreibindex erhöhen
      BNE CA02    ; Rücksprung falls kein Überlauf
      INC *$76    ; High-Byte des Adresszeigers auf Programmcode erhöhen
      LDA *$76    ; High-Byte des Adresszeigers holen
      CMP *$78    ; und mit High-Byte des Programmendes vergleichen
      BNE CA02    ; Rücksprung, falls Programmende noch nicht erreicht
      LDA $D011   ; Bildschirm wieder einschalten
      ORA #$10
      STA $D011
      LDA *$01    ; Bandmotor wieder einschalten
      ORA #$20
      STA *$C0
      STA *$01
      LDA #$81    ; Timerinterrupt CIA 1 Timer A wieder aktivieren
      STA $DC0D
      LDA #$50    ; Synchronisationszeichen $50
      LDX #$C4    ; Meldungstexte ab Adresse $05C4 auf Bildschirm
      LDY #$05
      JSR P_DA    ; Zweiten Programmblock mit zweitem Schnelllader laden
      LDA #$31    ; Interruptvektor wieder auf Standardwert
      STA $0314   ; Low-Byte
      LDA #$EA
      STA $0315   ; High-Byte
      CLI         ; Interrupts wieder zulassen
      JMP ($0079) ; Spiel durch Sprung nach $7000 starten

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 7 Byte verlängerte Datenstruktur enthält nun die Blocknummer (1 Byte), die Ladeadresse, die Endadresse der gesamten Blocksequenz und die Einsprungadresse des enthaltenen Programmteils (alle Adressen jeweils 2 Bytes im Little Endian-Format, Einsprungadresse $0000 bedeutet "Programmteil nach dem Laden nicht anspringen"). Auf diesen Header folgen 256 Datenbytes und die über diese Bytes durch XOR-Verknüpfung errechnete Prüfsumme. Die zweite Schnelllade-Routine lädt einen letzten Programmabschnitt in den Adressbereich $E000...$F5FF.

PRG $C000

; Zweiter Schnelllader (lädt Programmblock $E000-$F5FF)
P_DA: SEI
      STA *$A3    ; Synchronisationszeichen
      STX *$A4    ; Zeiger auf Bildschirmspeicher für Meldungstexte
      STY *$A5
      SEI
      LDA #$C1    ; NMI-Vektor auf "RTI" richten
      STA $0318   ; Low-Byte
      LDA #$FE
      STA $0319   ; High-Byte
      LDA *$01    ; BASIC-ROM ausblenden
      AND #$DE
      STA *$01
      LDA #$32    ; Startwert für CIA 2 Timer A auf $0232 (562)
      STA $DD04   ; Low-Byte
      LDA #$02
      STA $DD05   ; High-Byte
      LDA #$19    ; CIA 2 Timer A laden und im "one shot"-Modus starten
      STA $DD0E
      LDA #$7F    ; Alle Interrupts von CIA 1 abschalten
      STA $DC0D
      LDA #$91    ; CIA 1 Interrupt bei Unterlauf Timer A und Impuls an Pin FLAG aktivieren
      STA $DC0D
      LDY #$00
      STY *$B1    ; Als nächstes Block $00 laden
      STY *$B4    ; Kein Interrupt von CIA 1 Timer A aufgetreten
      JSR P_DC    ; Meldung "SEARCHING" ausgeben
DA00: JSR P_DG    ; Byteweise Synchronisation
      JSR P_DH    ; Header von Band lesen
      LDA *$A9    ; Gelesene Blocknummer
      CMP *$B1    ; mit gesuchter Blocknummer vergleichen
      BEQ DA01    ; Sprung falls gleich
      JSR DC02    ; sonst "BLOCK?" ausgeben
      BEQ DA00    ; Unbedingter Sprung
DA01: JSR DC00    ; Meldung "LOADING" ausgeben
      JSR P_DI    ; Einen Datenblock einlesen
      BEQ DA02    ; Sprung, falls Prüsumme korrekt
      JSR DC02    ; sonst "BLOCK?" ausgeben
      BEQ DA00    ; Unbedingter Sprung
DA02: INC *$B1    ; Blocknummer erhöhen
      INC $D020   ; Rahmenfarbe erhöhen
      DEC *$AD    ; High-Byte der Endadresse des Programmblocks um 1 vermindern
      LDA *$AB    ; High-Byte der Startadresse des aktuellen Datenblocks nach A holen
      CMP *$AD    ; und mit um 1 vermindertem High-Byte der Endadresse vergleichen
      BNE DA00    ; Rücksprung falls nicht letzter Datenblock
      JSR P_DL    ; Initialisierungen für Start des Programms
      LDA *$AE    ; Sprungvektor im Header auf $0000?
      ORA *$AF
      BEQ DB02    ; dann Rücksprung aus Schnelllader
      JMP ($00AE) ; sonst Über den Sprungvektor im Header springen

; A als Hexadezimalzahl auf Bildschirm ausgeben
P_DB: PHA         ; A auf Stack retten
      LSR A       ; High-Nibble in Bit 0..3
      LSR A
      LSR A
      LSR A
      JSR DB00    ; und als Hexadezimalziffer ausgeben
      PLA         ; A von Stack zurückholen
      INY         ; Schreibindex erhöhen
      AND #$0F    ; Low-Nibble in Bit 0..3
DB00: ORA #$30    ; "0"
      CMP #$3A    ; War Nibble im Bereich $0A...$0F?
      BCC DB01    ; Sprung falls nicht
      SBC #$39    ; sonst durch Zeichen "A"..."F" ersetzen
DB01: STA ($A4),Y ; zeichen auf Bildschirm ausgeben
DB02: RTS

; Bildschirmmeldungen ausgeben
;  Einsprung für "SEARCHING "
P_DC: LDX #T100-T100 ; "SEARCHING "
      LDY #$28       ; Schreibindex für Bildschirmspeicher
      JMP DC03
;  Einsprung für "LOADING"
DC00: LDY #$28       ; Schreibindex für Löschen eines Bildschirmbereichs
      LDA #$20       ; Leerzeichen
DC01: STA ($A4),Y    ; Bildschirmbereich löschen
      INY            ; Schreibindes erhöhen
      CPY #$35       ; Schon ganzer Bereich gelöscht?
      BNE DC01       ; Rücksprung falls noch nicht
      LDX #T101-T100 ; "LOADING "
      LDY #$02       ; Schreibindex für Bildschirmspeicher
      JMP DC03       ; Meldung ausgeben
DC02: LDX #T102-T100 ; "BLOCK? "
      LDY #$02       ; Schreibindex für Bildschirmspeicher
      JSR DC03       ; Meldung ausgeben
      LDA *$B1       ; Gesuchte Blocknummer nach A laden
      LDY #$32       ; Schreibindex für Bildschirmspeicher
      JSR P_DB       ; Blocknummer als Hexadezimalzahl auf Bildschirm ausgeben
      LDA *$A9       ; Gelesene Blocknummer nach A holen
      CMP *$B1       ; und mit gesuchter Blocknummer vergleichen
      BCC P_DC       ; Gesuchte Blocknummer ist größer, dann "SEARCHING " ausgeben
      LDX #T103-T100 ; sonst "REWIND TO " ausgeben
      LDY #$28       ; Schreibindex für Bildschirmspeicher
DC03: LDA T100,X     ; Zeichen aus Hauptspeicher lesen
      STA ($A4),Y    ; und auf Bildschirm ausgeben
      INY            ; Schreibindex erhöhen
      INX            ; Leseindex erhöhen
      CMP #$20       ; Wortende erreicht?
      BNE DC03       ; Weiter umkopieren, falls noch nicht
      RTS

; Bitweise Synchronisation: Bit von Band lesen und auf Synchronisationszeichen prüfen
P_DD: SEI
      JSR P_DF    ; Ein Bit von Band lesen
      LDA *$BD    ; Letzte 8 gelesene Bits holen
      CMP *$A3    ; und mit gesuchtem Synchronisationszeichen vergleichen
      BNE DE00    ; Rückkehr aus Interrupt, falls nicht gefunden
      LDA #$01    ; Schieberegister für ganzes Datenbyte initialisieren
      STA *$BD
      LDA #<P_DE  ; Interruptvektor auf P_BE richten
      STA $0314   ; Low-Byte
      LDA #>P_DE
      STA $0315   ; High-Byte
      JMP DE00    ; Rückkehr aus Interrupt

; Ein Bit von Band lesen; ggf. vollständiges Byte in Ringpuffer umkopieren
P_DE: SEI         ; Interrupts deaktivieren (nicht nötig in Interruptroutine)
      JSR P_DF    ; Ein Bit von Band lesen und nach $BD
      BCC DE00    ; Sprung falls noch nicht 8 Bit gelesen
      LDY *$72    ; Schreibindex nach Y holen
      LDA *$BD    ; Gelesenes Byte nach A holen
      STA $033C,Y ; Gelesenes Byte in Ringpuffer schreiben
      LDA #$01    ; Schieberegister für gelesene Bits wieder initialisieren
      STA *$BD
      INY         ; Schreibindex für Ringpuffer weiterbewegen
      TYA         ; auf Wertebereich $00...$1F begrenzen
      AND #$1F
      STA *$72    ; und wieder merken
DE00: JMP $FEBC   ; Rückkehr aus Interrupt

; Ein Bit von Band lesen und von rechts in Adresse $BD schieben
P_DF: LDA $DC0D   ; Interruptregister von CIA 1 nach A holen
      PHA         ; und auf Stack retten
      AND #$01    ; Flag für "Unterlauf CIA 1 Timer A" isolieren
      ORA *$B4    ; und an Adresse $AF merken
      STA *$B4
      PLA         ; Interruptregister von CIA 1 zurückholen
      CLC         ; "Byte noch nicht vollständig empfangen" vormerken
      AND #$10    ; Impuls von CIA 1 Pin FLAG empfangen?
      BEQ DF00    ; Sprung falls kein Impuls vom Band
      LDA #$19    ; CIA 2 Timer A neu laden und im "one shot"-Modus starten
      STA $DD0E
      LDA $DD0D   ; Interruptregister von CIA 2 nach A holen
      LSR A       ; CIA 2 Timer A abgelaufen? ja=1-Bit, nein=0-Bit empfangen
      ROL *$BD    ; Empfangenes Bit von rechts in Adresse $BD schieben
DF00: RTS         ; CC: Byte noch nicht vollständig, CS: Byte vollständig

; Byteweise Synchronisation: Bytes von Band lesen und auf Synchronisationszeichen prüfen
P_DG: SEI         ; Interrupts verbieten, da Interruptvektor geändert wird
      LDA #$00
      STA *$72    ; Schreibindex für Ringpuffer zurücksetzen
      STA *$71    ; Leseindex für Ringpuffer initialisieren
      STA *$BD    ; Puffer für eingelesene Bits von Band initialisieren
      LDA #<P_DD  ; IRQ-Vektor zeigt auf P_BD
      STA $0314   ; Low-Byte
      LDA #>P_DD
      STA $0315   ; High-Byte
      CLI         ; Interrupts wieder zulassen
DG00: JSR P_DJ    ; Ein Datenbyte von Band aus Ringpuffer holen
      CMP *$A3    ; und mit Synchronisationszeichen vergleichen
      BEQ DG00    ; überlesen falls gleich
      EOR #$FF    ; sonst gelesenes Datenbyte invertieren
      CMP *$A3    ; und mit Synchronisationszeichen vergleichen
      BNE P_DG    ; Neustart der bitweisen Synchronisation, falls ungleich
      JSR P_DJ    ; Ein Datenbyte von Band aus Ringpuffer holen
      CMP *$A3    ; und mit Synchronisationszeichen vergleichen
      BNE P_DG    ; Neustart der bitweisen Synchronisation, falls ungleich
      RTS

; 7 Byte-Header von Band lesen und nach $A9...$AF
P_DH: LDY #$00
DH00: JSR P_DJ    ; Headerbyte von Band aus Ringpuffer holen
      STA $00A9,Y ; und an $A9...$AF merken
      INY         ; Schreibindex erhöhen
      CPY #$07    ; Schon 7 Byte von Band gelesen?
      BNE DH00    ; Rücksprung falls noch nicht
      LDA *$A9    ; Blocknummer nach A holen
      LDY #$0A    ; Schreibzeiger für Bildschirmausgabe (hinter "LOADING")
      JMP P_DB    ; Blocknummer als Hexadezimalzahl ausgeben

; Einen Datenblock von Band einlesen
P_DI: LDY #$00
      STY *$B0    ; Prüfsumme initialisieren
DI00: JSR P_DJ    ; Ein Datenbyte von Band aus Ringpuffer holen
      STA ($AA),Y ; in Hauptspeicher schreiben
      EOR *$B0    ; und in Prüfsumme einarbeiten
      STA *$B0
      INY         ; Schreibindex erhöhen
      BNE DI00    ; Rücksprung falls noch nicht 256 Bytes gelesen
      JSR P_DJ    ; Prüfsumme von Band aus Ringpuffer holen
      CMP *$B0    ; und mit berechneter Prüfsumme vergleichen
      RTS

; Ein Datenbyte aus Ringpuffer nach A holen
P_DJ: STY *$B5    ; Y zwischenspeichern
DJ00: LDA $0312   ; High-Byte des USR-Vektor nach A holen
      CMP #$B2    ; Noch auf Defaultwert ($B248, "?ILLEGAL QUANTITY ERROR")?
      BEQ DJ01    ; Sprung falls ja
      LDA *$B4    ; Timerinterrupt statt Interrupt durch Bandsignal?
      BEQ DJ01    ; Sprung falls nicht
      LDA #$00    ; sonst Flag für Timerinterrupt löschen
      STA *$B4
      JSR P_DK    ; und über Sprungvektor an $0311 springen
DJ01: LDX *$71    ; Leseindex für Ringpuffer mit Banddaten holen
      CPX *$72    ; und mit Schreibzeiger vergleichen
      BEQ DJ00    ; Rücksprung falls gleich, dann noch keine neuen Daten
      LDA $033C,X ; Von Band gelesene Datenbytes aus Ringpuffer holen
      PHA         ; und auf den Stack retten
      INX         ; Leseindex für Ringpuffer weiterbewegen
      TXA         ; auf Wertebereich $00..$1F begrenzen
      AND #$1F
      STA *$71    ; und wieder abspeichern
      PLA         ; Gelesenes Datenbyte zurückholen
      LDY *$B5    ; Y zurückholen
      RTS

; Start des Programms über USR-Vektor (nicht verwendet)
P_DK: JMP ($0311)

P_DL: LDA $D011   ; Bildschirm wieder einschalten
      ORA #$10
      STA $D011
      LDA *$01    ; Bandmotor wieder einschalten
      ORA #$20
      STA *$C0
      STA *$01
      LDA #$7F    ; Alle Interrupts von CIA 1 deaktivieren
      STA $DC0D
      LDA #$81    ; Timerinterrupt CIA 1 Timer A wieder aktivieren
      STA $DC0D
      SEI         ; Interrupts verbieten
      LDA #$31    ; Interruptvektor auf Defaultwert setzen
      STA $0314   ; Low-Byte
      LDA #$EA
      STA $0315   ; High-Byte
      CLI         ; Interrupts wieder erlauben
      RTS

; "SEARCHING "
T100: DB $13,$05,$01,$12,$03,$08,$09,$0E,$07,$20

; "LOADING "
T101: DB $0C,$0F,$01,$04,$09,$0E,$07,$20

; " BLOCK? "
T102: DB $60,$02,$0C,$0F,$03,$0B,$3F,$20

; "REWIND TO "
T103: DB $12,$05,$17,$09,$0E,$04,$60,$14,$0F,$20