Starquake/Schnelllader
<< zurück zu Starquake, Bristles, Aztec (Action-Adventure) oder Astro Chase
Starquake/Schnelllader, Bristles/Schnelllader, Aztec_(Action-Adventure)/Schnelllader und Astro_Chase/Schnelllader: Die folgenden Abschnitte stellen den disassemblierten Kassetten-Schnelllader der Spiele "Starquake", "Bristles", "Aztec (Action-Adventure)" und "Astro Chase" 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