Starquake/Schnelllader

Aus C64-Wiki
Zur Navigation springenZur Suche springen

<< zurück zu Starquake


Starquake/Schnelllader: Die folgenden Abschnitte stellen den disassemblierten Kassetten-Schnelllader des Spiels Starquake 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 des Spiels Starquake 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
      DB $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