Spiky Harold/Schnelllader
<< 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