Spiky Harold/Schnelllader
<< zurück zu Cholo oder Spiky Harold
Cholo/Schnelllader und Spiky Harold/Schnelllader: Die folgenden Abschnitte stellen den disassemblierten Kassetten-Schnelllader der Spiele "Cholo" und "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 der Spiele "Cholo" und "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üfsumme 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üfsumme 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