Novaload/Quellcode2
<< zurück zu Novaload
Novaload/Quellcode2: Der folgende Codeabschnitt stellt die zweite Interruptroutine des Kassetten-Schnellladers Novaload dar. Ausgangspunkt für die Analyse ist der disassemblierte Code aus der Kassettenversion des Spiels "Buggy Boy".
Die Routine wird bei jedem Leseimpuls vom Datassetten-Eingang (jeder fallenden Flanke an Pin FLAG von CIA 1) aufgerufen. Sie überprüft zunächst den Stand des Timers A von CIA 1: Dieser startet bei jedem Leseimpuls beim Startwert 1012 und zählt Systemtakte abwärts; falls seit dem letzten Impuls mehr als 500 Systemtakte vergangen sind (der Zählerstand also kleiner als 512 ist), so wurde ein 1-Bit gelesen, ansonsten ein 0-Bit.
Gelesene Bits werden von links in die Speicherzelle an Adresse $A9 geschrieben. Diese wird während des Ladens vor jedem neuen Byte mit dem Wert 127 = %01111111 geladen und dient damit als Bitzähler: Sobald der Rotationsbefehl ein gelöschtes Bit ins Carry-Flag schiebt, wurde eine Gruppe von 8 Bits von der Datassette empfangen.
Der BCS-Befehl hinter dem Label BA00 ist der Dreh- und Angelpunkt der Routine: Die Sprungdistanz dieses Befehls codiert den Zustand des zugrundeliegenden endlichen Automaten und wird bei jedem Zustandswechsel angepasst.
Die Aufgabe der Routine ist es, eine zu dem per SETNAM festgelegten Dateinamen passende Datei auf der Kassette zu finden und absolut in den Hauptspeicher des C64 zu laden. Der Dateiname kann dabei maximal 255 Zeichen lang sein. Bei der Kassettenversion des Spiels "Buggy Boy" haben allerdings nachzuladenden alle Dateien einen Namen der Länge 0.
Die Routine berechnet in der Speicherzelle $AA die Summe aller Header- und Datenbytes (ohne Berücksichtigung von Additionsüberträgen). Diese Prüfsumme wird beim Erreichen des Dateiendes mit dem an die Datei angehängten Sollwert verglichen.
ORG $0334
P_BA: PHA ; Register A retten
TYA ; Register Y retten
PHA
LDA $DC05 ; High-Byte CIA 1 Timer A
LDY #$11 ; Timer A mit Startwert laden und starten
STY $DC0E
INC $D020 ; Rahmenfarbe hochzählen
EOR #$02 ; Bit 1 des Highbyte von Timer A negieren
LSR A ; Zählerstand<512 ergibt CS, sonst CC
LSR A
ROR *$A9 ; CF von links in Byte an Adresse $A9 schieben
BCS BA07 ; Rückkehr aus Interrupt, falls nicht 8. Bit eines Byte
LDA *$AA ; Aktuelle Prüfsumme holen
STA *$A8 ; und merken
ADC *$A9 ; Gelesenes Byte zu Prüfsumme addieren
STA *$AA ; und zurückschreiben
LDY *$B4 ; Low-Byte des Schreibzeigers holen
LDA *$A9 ; Empfangenes Byte holen
SEC
BA00: BCS BA01 ; Zentraler Sprungverteiler
; 1. Einsprung: Synchronisation (Suche nach dem ersten 1-Bit)
BA01: CMP #$80 ; Erstes 1-Bit gelesen?
BNE BA07 ; sonst Rückkkehr aus Interrupt
LDA #BA02-BA00-$02 ; Sprung von BA00 nach BA02 vorbereiten
BNE BA05
; 2. Einsprung: Synchronisation (Suche nach $AA=%10101010)
BA02: SBC #$AA ; Bitkombination $AA=%10101010 gelesen?
BEQ BA04 ; Sprung falls ja
BA03: LDA #$00 ; sonst wieder Sprung von BA00 nach BA01 vorbereiten
BEQ BA19
BA04: STA *$AA ; Prüfsumme initialisieren
LDA #BA08-BA00-$02 ; Sprung von BA00 nach BA08 vorbereiten
BA05: STA BA00+$01
BA06: LDA #$7F ; Bitzähler auf 8 Bit (%01111111) setzen
STA *$A9
STY *$B4 ; Low-Byte des Schreibzeigers zurückschreiben
BA07: LDA $DC0D ; Interrupt Request löschen
PLA ; Gerettetes Register Y zurückholen
TAY
PLA ; Gerettetes Register A zurückholen
RTI ; Rückkehr aus Interrupt
; 3. Einsprung: Länge des Dateinamens verarbeiten
BA08: STA *$B5 ; Namenslänge der gefundenen Datei merken
CMP *$B7 ; mit Namenslänge der zu ladenden Datei vergleichen
BEQ BA09 ; Sprung falls gleich
LDA *$B7 ; Namenslänge der zu ladenden Datei holen
BNE BA03 ; Sprung falls Dateinamen nicht Leerstring
LDA #BA11-BA00-$02 ; Sprung von BA00 nach BA11 vorbereiten
BNE BA05
BA09: LDA #BA10-BA00-$02 ; Sprung von BA00 nach BA10 vorbereiten
CPY *$B5 ; Name der zu ladenden Datei leer?
BNE BA05 ; Sprung falls nicht leer
LDA #BA12-BA00-$02 ; Sprung von BA00 nach BA12 vorbereiten
BNE BA05
; 4: Einsprung: Dateinamen vergleichen
BA10: CMP ($BB),Y ; Gelesenes Zeichen gleich gesuchtes Zeichen?
BNE BA03 ; Neustart des Ladens falls nicht
BA11: LDA #BA12-BA00-$02 ; Sprung von BA00 nach BA12 vorbereiten
BNE BA18 ; und Low-Byte des Schreibzeigers erhöhen
; 5. Einsprung: 6 Byte langen Header nach $00AC..$00B1 holen
; $AC..$AD: Anfangsadresse low/high
; $AE..$AF: Endadresse low/high
; $B0..$B1: Länge low/high
BA12: STA $00AC,Y ; Gelesenes Byte in Puffer speichern
LDA #BA13-BA00-$02 ; Sprung von BA00 nach BA13 vorbereiten
INY ; Schreibzeiger erhöhen
STY *$B4 ; und merken
CPY #$06 ; Schon 6 Bytes empfangen?
BNE BA06 ; Rückkehr aus Interrupt falls noch nicht
BEQ BA19 ; Sonst Index auf 0
; 6. Einsprung: Prüfsumme vergleichen
BA13: CMP *$A8 ; Gelesene mit berechneter Prüfsumme vergleichen
BNE BA16 ; Fehler melden wenn ungleich
LDA *$B1 ; Endet nachgeladenes Programm auf Page-Grenze?
BEQ BA15 ; Sprung falls ja
INC *$AD ; High-Byte des Schreibzeigers erhöhen
DEC *$B1 ; High-Byte der Länge vermindern
BNE BA14 ; Sprung falls nicht letzte Page
LDA *$B0 ; Low-Byte der Länge holen
BEQ BA15 ; Sprung falls Vielfaches von 256 Byte
STA *$B5 ; sonst als Obergrenze des Index merken
BA14: LDA #BA17-BA00-$02 ; Sprung nach BA17 vorbereiten
BNE BA05
BA15: STA *$AB ; Rückmeldung "Erfolg" bereitstellen
LDA #$1F ; Alle Interrupts von CIA1 deaktivieren
STA $DC0D
BNE BA07 ; Rückkehr aus Interrupt
BA16: LDA #$80 ; Rückmeldung "Fehler" bereitstellen
BNE BA15 ;
; 7. Einsprung: Gelesenes Byte in Speicher schreiben
BA17: STA ($AC),Y ; Gelesenes Byte in Speicher schreiben
LDA #BA13-BA00-$02 ; Sprung von BA00 nach BA13 vorbereiten
BA18: INY ; Schreibindex erhöhen
CPY *$B5 ; Obergrenze erreicht?
BNE BA06 ; Rückkehr aus Interrupt falls nicht
BA19: LDY #$00 ; Schreibindex zurücksetzen
STY *$B5
BEQ BA05 ; Sprung nach BA13 vorbereiten und Rückkehr aus Interrupt