Starquake/Schnelllader
<< zurück zu Starquake, Astro Chase, Aztec (Action-Adventure), Bristles oder Flip and Flop
Starquake/Schnelllader, Astro_Chase/Schnelllader, Aztec_(Action-Adventure)/Schnelllader, Bristles/Schnelllader und Flip and Flop/Schnelllader: Die folgenden Abschnitte stellen den disassemblierten Kassetten-Schnelllader der Spiele "Starquake", "Astro Chase", "Aztec (Action-Adventure)", "Bristles" und "Flip and Flop" dar. Sie sind gegliedert in einzelne Codeblöcke, die gemäß ihrer Funktion vor dem und während des Ladens gruppiert sind.
Decodierung des Schnellladers[Bearbeiten | Quelltext bearbeiten]
Die folgenden Routinen werden beim Laden der Spiele "Starquake", "Bristles", "Aztec (Action-Adventure)" oder "Astro Chase" von Kassette ab Adresse $02A7 in den Speicher des C64 übertragen. Da hierbei sowohl der Sprungvektor für die Eingabe einer BASIC-Zeile als auch der Vektor für das Ausführen von BASIC-Befehlen auf die Routine P_BA umgebogen werden, startet nach dem Laden automatisch sofort dieser Programmteil.
Die Routine P_BA hat die Aufgabe, den eigentlichen Schnelllader — der in verschlüsselter Form direkt hinter dem Dateinamen im Programmheader gespeichert ist und sich daher zu diesem Zeitpunkt bereits im Kassettenpuffer befindet — zu decodieren und zu starten. Um diese Vorgehensweise zu verschleiern, besteht die Decodierroutine zu fast zwei Dritteln aus illegalen Opcodes, von denen wiederum mehr als die Hälfte keine Funktion haben. Zudem wird die Decodierung (eine byteweise "Exklusiv-Oder"-Verknüpfung mit dem Wert $59) durch zwei getrennte EOR-Befehle mit absoluter Adressierung realisiert, die auf bekannte Werte im Hauptspeicher zugreifen, und das Low-Byte der Adresse, über die der Schnelllader angesprungen wird, wird erst durch Rechtsverschieben auf den endgültigen Wert gebracht.
Unmittelbar nach dem Laden des Spiels hat die Routine P_BB noch die Aufgabe, den Schnellader durch Linksverschieben aller Bytes zu zerstören, bevor die Startadresse des Spiels angesprungen wird. Die Routine P_BC ist das Sprungziel des STOP-Vektors, kommt aber nicht zum Einsatz, da der Schnelllader die STOP-Taste nicht abfragt.
START EQU $0C03 ; Startadresse des Spiels
ORG $02A7
; Decodieren des Schnellladers im Kassettenpuffer
P_BA: NOP *$AE
BA00: LSR BA02+$01 ; Sprung an BA02 zu "JMP $0351" abändern
NOP *$CC,X
LDX #$FF ; Nachfolgende AND-Verknüpfungen mit X haben keine Wirkung
ANE #$50 ; A in den Bereich $00..$50 bringen
SAX *$FB ; Low-Byte des Adresszeigers setzen
NOP *$4C
ANE #$E1
SAX $0328 ; Stop-Vektor kurzzeitig auf ungültigen Wert setzen
LAX T000 ; A=$03
NOP *$EE
SAX *$FC ; High-Byte des Adresszeigers setzen
LDY #$FF ; Decodierzeiger initialisieren
BA01: LAX ($FB),Y ; Byte aus Kassettenpuffer lesen
NOP *$20,X
EOR $0308 ; EOR #$A7
NOP *$CD
EOR $0317 ; EOR #$FE
NOP #$20
STA ($FB),Y ; Decodiertes Byte zurückschreiben
NOP *$CC,X
DEY ; Decodierzeiger erniedrigen
NOP #$EE
BNE BA01 ; Rücksprung falls Decodieren noch nicht abgeschlossen
NOP *$4C,X
BA02: JMP $03A2 ; Wird durch LSR an BA00 zu "JMP $0351"
; Abschluss des Ladevorgangs, angesprungen mit Startadresse-1 auf dem Stack
P_BB: NOP #$EE
LDY #$C0 ; Bytezähler initialisieren
BB00: SLO $033C ; Schnellladeroutine im Bereich $33D..$3FC zerstören
DEY ; Bytezähler erniedrigen
BNE BB00 ; Rücksprung falls Zerstören noch nicht abgeschlossen
NOP $2E,X
JMP $FC93 ; Rekorderbetrieb beenden, abschließendes RTS springt zum Programmstart
; Abschluss nach Drücken der Stop-Taste
P_BC: NOP *$34,X
JSR $A533 ; BASIC-Programmzeilen neu binden
NOP #$EE
JSR $A659 ; Einsprung in BASIC-Befehl NEW
JMP $A7AE ; und zur Interpreterschleife
; Nicht benötigter Code
DB $A9,$04,$60,$41
; Autostart durch Überschreiben von Sprungvektoren
DW $E38B ; Unveränderter Vektor für BASIC-Warmstart
DW P_BA ; Verbogener Vektor für Eingabe einer Zeile, nach direktem LOAD angesprungen über $A480
DW $A57C ; Unveränderter Vektor für Umwandlung in Interpretercode
DW $A71A ; Unveränderter Vektor für Umwandlung in Klartext (LIST)
DW P_BA ; Verbogener Vektor für BASIC-Befehlsadresse holen, nach LOAD in einem Programm angesprungen über $A7E1
DW $AE86 ; Unveränderter Vektor für Ausdruck auswerten
Schnelllade-Routine[Bearbeiten | Quelltext bearbeiten]
Die nachfolgende Abschnitte dokumentieren den vollständigen Aufbau des Programmheaders, der der oben beschriebenen Decodierroutine vorausgeht. Unmittelbar auf den Dateinamen folgt der Schnelllader, der nachfolgend in decodierter Form wiedergegeben wird, auf der Kassette allerdings verschlüsselt (byteweise "Exklusiv-Oder"-verküpft mit dem Wert $59) gespeichert ist.
Für jeden der insgesamt 6 nachzuladenden Programmteile ist auf der Kassette zunächst ein Synchronisationsheader (95 Mal das Byte $E3, gefolgt von einem einzelnen Byte mit dem Wert $ED) gespeichert. Die nachfolgenden vier Bytes beinhalten die Startadresse und die Endeadresse+1 des nachzuladenden Programmteils, hieran schließen sich unmittelbar die Datenbytes an.
Die Unterscheidung von 0- und 1-Bits geschieht mit Hilfe des Timers A der CIA 2 in Routine P_AD. Dieser zählt für jedes Bit, beginnend bei einem Startwert von 384, 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 528 Takte), ansonsten um ein 0-Bit (Abstand zum vorigen Signal 272 Takte). Routine P_AC fasst jeweils 8 Bit zu einem Byte zusammen. Da der Programmcode von Starquake zu 60,5% aus 0-Bits und zu 39,5% aus 1-Bits besteht, dauert das Einlesen eines Byte vom Band durchschnittlich 2985 Systemtakte (rund 3 ms).
ORG $033C ; Kassettenpuffer
; Programmheader auf Kassette
T000: DB $03 ; Code für "Absolute Programmdatei"
DW $02A7 ; Startadresse
DW $030C ; Endadresse+1
DB 'STARQUAKE64' ; Dateiname
DB $03,$03,$03,$03,$03 ; Füller für Dateinamen
; Schnelllader, folgt in verschlüsselter Form auf den Dateinamen
P_AA: SEI
LDA #<P_BC ; Adresse der Routine P_BC, die den Speicherinhalt löscht
STA $0328 ; als Stop-Vektor setzen
LDA #$02
STA $0329
LDA $D011 ; Bildschirm abschalten
AND #$EF
STA $D011
LDA #$00
STA *$C6 ; Tastaturpuffer löschen
STA *$9D ; Flag für Programm-Modus setzen
LDA #$80 ; Low-Byte des Startwerts 384 für CIA2 Timer A
STA $DD04
LDA #$01 ; High-Byte des Startwerts 384 für CIA2 Timer A
STA $DD05
LDA #$19 ; CIA2 Timer läuft im "One Shot"-Betrieb
STA $DD0E
LDA *$01 ; Prozessorport lesen
AND #$1F ; Motor der Datassette dauerhaft einschalten
STA *$01 ; und Prozessorport zurückschreiben
LDY #$00 ; Kein Offset bei nachfolgenden indirekt Y-indizierten Zugriffen
AA00: JSR P_AB ; Headerbytes suchen und überlesen
JSR P_AC ; Ein Byte per Schnelllader holen und nach A
STA *$C1 ; als Low-Byte der Startadresse speichern
JSR P_AC ; Ein Byte per Schnelllader holen und nach A
STA *$C2 ; als High-Byte der Startadresse speichern
JSR P_AC ; Ein Byte per Schnelllader holen und nach A
STA *$C3 ; als Low-Byte der Endadresse+1 speichern
JSR P_AC ; Ein Byte per Schnelllader holen und nach A
STA *$C4 ; als High-Byte der Endadresse+1 speichern
AA01: JSR P_AC ; Ein Byte per Schnelllader holen und nach A
STA ($C1),Y ; an Zieladresse im Speicher schreiben
INC *$C1 ; Low-Byte des Schreibzeigers erhöhen
BNE AA02 ; Sprung falls kein Übertrag
INC *$C2 ; sonst High-Byte des Schreibzeigers erhöhen
AA02: LDA *$C1 ; Low-Byte des aktuellen Schreibzeigers
CMP *$C3 ; mit Low-Byte der Endadresse+1 vergleichen
LDA *$C2 ; High-Byte des aktuellen Schreibzeigers
SBC *$C4 ; einschließlich Übertrag mit High-Byte der Endadresse+1 vergleichen
BCC AA01 ; Rücksprung falls Dateinde noch nicht erreicht
DEC T001 ; Anzahl nachzuladender Programmteile erniedrigen
BNE AA00 ; Sprung falls noch weitere Programmteile nachzuladen
LDA #>[START-1]
PHA ; High-Byte der Startadresse-1 auf Stack
LDA #<[START-1]
PHA ; Low-Byte der Startadresse-1 auf Stack
JMP P_BB ; Sprung zum Abschluss des Ladevorgangs
T001: DB $06 ; Anzahl nachzuladender Programmteile
; Auf Headerbytes $E3 ... $E3 $ED warten
P_AB: JSR P_AD ; Ein Bit von Datasette holen
ROR *$BD ; und von links in Byte an Adresse $BD schieben
LDA *$BD ; Aktuellen Wert dieses Byte holen
CMP #$E3 ; und mit Startkennzeichen $E3 (%11100011) vergleichen
BNE P_AB ; Rücksprung solange gesammelte Bits nicht $E3 ergeben
AB00: JSR P_AC ; Nächstes Byte von Datassette nach A holen
CMP #$E3 ; Überlesen, solange weitere Headerbytes $E3 folgen
BEQ AB00 ; (insgesamt 95 Headerbytes)
CMP #$ED ; Header-Endekennzeichen $ED (%11101101) gefunden?
BNE P_AB ; Synchronisation neu starten falls nicht
RTS
; Ein Byte per Datassetten-Schnelllader nach <A> holen
P_AC: LDX #$08 ; Bitzähler initialisieren
AC00: JSR P_AD ; Ein Bit von Band einlesen
ROR *$BD ; und von links in Byte an Adresse $BD schieben
INC $D020 ; Rahmenfarbe des Bildschirms weiterzählen
DEX ; Bitzähler erniedrigen
BNE AC00 ; Rücksprung falls noch nicht 8 Bit
LDA *$BD ; Gelesenes Byte nach A holen
RTS
; Ein Bit von Band ins CF holen (0-Bit ist 272 Takte, 1-Bit 528 Takte lang)
P_AD: LDA #$10 ; Bitmaske für Interrupt durch Bit vom Band
AD00: BIT $DC0D ; Gegen Interrupt Requests von CIA1 testen
BEQ AD00 ; und auf Bit vom Band warten
LDA $DD0D ; Interrupt Requests von CIA2 lesen
LSR A ; und Bit 0 nach CF holen: Zähler 1 abgelaufen?
LDA #$19 ; Zähler 1 von CIA2 wieder
STA $DD0E ; im Modus "One Shot" starten
RTS
; Nicht benötigter Code
DB $EA,$03,$5A,$5A,$5A,$5A,$5A,$5A,$59,$59,$59,$59