Ghettoblaster/Schnelllader

Aus C64-Wiki
Zur Navigation springenZur Suche springen

<< zurück zu Ghettoblaster


Ghettoblaster/Schnelllader: Die folgenden Abschnitte stellen den disassemblierten Kassetten-Schnelllader des Spiels Ghettoblaster dar. Dazwischenliegende Bereiche mit Daten ohne Funktion sind weggelassen, um die Darstellung kompakt zu halten.

Autostart des Schnellladers[Bearbeiten | Quelltext bearbeiten]

Wenn das Spiel Ghettoblaster von Kassette geladen wird, werden zunächst die Kernal-Vektoren mit den folgenden Adressen überschrieben. Insbesondere wird hierbei der OUTPUT-Vektor auf die Initialisierungsroutine P_AH des Schnellladers umgebogen, so dass diese nach dem Abschluss des Ladevorgangs angesprungen und gestartet wird. Dass hierbei auch — vermutlich versehentlich — der CHKOUT-Vektor auf einen ungültigen Wert gesetzt wird, fällt beim Laden nicht auf, weil die Initialisierungsroutine die Originalwerte der Vektoren wieder herstellt.

ORG $0316

      DW $FE66 ; BRK-Vektor (unverändert)
      DW $FE47 ; NMI-Vektor (unverändert)
      DW $F34A ; OPEN-Vektor (unverändert)
      DW $F291 ; CLOSE-Vektor (unverändert)
      DW $F20E ; CHKIN-Vektor (unverändert)
      DW $F25A ; CHKOUT-Vektor (sollte $F250 sein)
      DW $F333 ; CLRCH-Vektor (unverändert)
      DW $F157 ; INPUT-Vektor (unverändert)
      DW P_AH  ; OUTPUT-Vektor (ursprünglich $F1CA)
      DW $F6ED ; STOP-Vektor (unverändert)
      DW $F13E ; GET-Vektor (unverändert)
      DW $F32F ; CLALL-Vektor (unverändert)
      DW $FE66 ; Warmstart-Vektor (unverändert)
      DW $F4A5 ; LOAD-Vektor (unverändert)
      DW $F5ED ; SAVE-Vektor (unverändert)

Unmittelbar auf diese Routine folgen 204 Datenbytes, die hauptsächlich den Kassettenpuffer überschreiben, jedoch keine Funktion erfüllen.

Schnelllade-Routine[Bearbeiten | Quelltext bearbeiten]

Die nachfolgende Routine P_AA ist der zentrale Programmteil des Schnellladers. Hier synchronisiert sich der Schnelllader zunächst mit den Banddaten, bevor er den Header des nächsten Programmteils einliest und — falls die dort abgelegte Sequenznummer mit der Nummer des gewünschten Programms an T006 übereinstimmt — diesen in den Speicher überträgt.

Ein 0-Bit ist auf Band als Impuls von 312 Systemtakten Dauer codiert, ein 1-Bit als Impuls mit einer Länge von 504 Takten. Die für die Zeitmessung verwendeten Zähler von CIA 2 werden so initialisiert, dass ein Impuls mit einer Länge von maximal 339 Takten als 0-Bit, ein längerer, maximal 539 Takte langer Impuls als 1-Bit interpretiert wird. Noch längere Impulse werden als Lesefehler gewertet und lösen einen Neustart der Synchronisation aus.

Ein Auftreten eines solchen Fehlers während eines Datentransfers führt, wie auch alle anderen Arten von Fehlern, zu einem Halt des Schnellladers. Hierbei lässt das Programm nur noch in einer Endlosschleife alle Bildschirmelemente blinken. Aus dieser Situation führt auch kein Reset und kein Druck auf RUN/STOP +RESTORE  mehr heraus, da der Schnelllader mit Hilfe einer "CBM80"-Signatur an Adresse $8004 ein Steckmodul vortäuscht und dessen Reset- und NMI-Vektor ebenfalls auf die Endlosschleife gerichtet sind.

ORG $0400

P_AA: SEI          ; Interrupts abschalten
      LDA #$00     ; Zähler 1 von CIA 1 stoppen
      STA $DC0E
      LDA *$01     ; Datassettenmotor einschalten
      AND #$C7
      STA *$01
      LDA #$0B     ; Bildschirm abschalten
      STA $D011
      LDA #<P_AG   ; IRQ würde Bildschirm endlos flimmern lassen
      STA $0318
      LDA #>P_AG
      STA $0319
      LDA $DC0D    ; Alle aufgelaufenen Interrupts von CIA 1 löschen
      LDA #$90     ; Impuls an FLAG (Datassette) als einzige Interruptquelle
      STA $DC0D
      LDA #$54     ; Zähler 1 von CIA 2 startet bei $0154 = 340
      STA $DD04
      LDA #$01
      STA $DD05
      LDA #$1C     ; Zähler 2 von CIA 2 startet bei $021C = 540
      STA $DD06
      LDA #$02
      STA $DD07
      LDA $DD0D    ; Alle aufgelaufenen Interrrupts von CIA 2 löschen
      LDA #$19     ; Timer neu laden und im "One Shot"-Betrieb starten
      STA $DD0E    ; ... CIA 2 Timer A
      STA $DD0F    ; ... CIA 2 Timer B
; Synchronisation (mindestens 256 1-Bits, dann 1 0-Bit)
AA00: LDX #$00     ; Synchronisation mit 256 1-Bits
      STX T003     ; Prüfsumme initialisieren
AA01: JSR P_AF     ; Bit von Band lesen
      BNE AA00     ; Neustart falls Impuls zu lang
      BCC AA00     ; Neustart falls 0-Bit
      DEX          ; Zähler für 1-Bits erniedrigen
      BNE AA01     ; Rücksprung falls noch nicht 256 1-Bits
AA02: JSR P_AF     ; Bit von Band lesen
      BNE AA00     ; Neustart falls Impuls zu lang
      BCS AA02     ; Auf erstes 0-Bit warten
; Programmheader laden
      JSR P_AE     ; Sequenznummer von Band einlesen
      CMP T006     ; und mit gewünschter Sequenznummer vergleichen
      BNE P_AB     ; bei falscher Sequenznummer Laderoutine verlassen
      JSR P_AE     ; Anfangsadresse (low) von Band einlesen
      STA AA04+$01 ; und in Programmcode einarbeiten
      JSR P_AE     ; Anfangsadresse (high) von Band einlesen
      STA AA04+$02 ; und in Programmcode einarbeiten
      JSR P_AE     ; Endadresse (low) von Band einlesen
      STA T004+$00 ; und merken
      JSR P_AE     ; Endadresse (high) von Band einlesen
      STA T004+$01 ; und merken
      JSR P_AE     ; Einsprungpunkt (low) von Band einlesen
      STA T005+$00 ; und setzen
      JSR P_AE     ; Einsprungpunkt (high) von Band einlesen
      STA T005+$01 ; und merken
; Programm laden
AA03: JSR P_AE     ; Datenbyte von Band einlesen
AA04: STA $FFFF    ; in Speicher schreiben
      EOR T003     ; und in Prüfsumme einberechnen
      STA T003
      LDA AA04+$01 ; Aktuellen Schreibzeiger (low) holen
      CMP T004+$00 ; und mit Endadresse (low) vergleichen
      BNE P_AD     ; Sprung falls Endadresse noch nicht erreicht
      LDA AA04+$02 ; Aktuellen Schreibzeiger (high) holen
      CMP T004+$01 ; und mit Endadresse (high) vergleichen
      BNE P_AD     ; Sprung falls Endadresse noch nicht erreicht
; Endadresse erreicht
      JSR P_AE     ; Prüfsumme von Band einlesen
      CMP T003     ; und mit berechneter Prüfsumme vergleichen
      BNE P_AC     ; Bei Prüfsummenfehler flimmert Bildschirm endlos
      INC T006
      JSR $FDA3    ; Interrupt-Initialisierung
      JSR $FC93    ; Rekorderbetrieb beenden
      LDA T005+$00 ; Einsprungpunkt von 0 verschieden?
      ORA T005+$01
      BEQ AB00     ; Programmteil nicht anspringen falls 0
      CLI          ; Interrupts wieder zulassen
      JMP (T005)   ; Programm starten

P_AB: JSR $FDA3    ; Interrupt-Initialisierung
      JSR $FC93    ; Rekorderbetrieb beenden
AB00: CLI          ; Interrupts wieder zulassen
      RTS

P_AC: JMP P_AG     ; Bildschirm flimmert endlos

; Schreibzeiger erhöhen
P_AD: INC AA04+$01 ; Schreibzeiger (low) erhöhen
      BNE AA03     ; Falls kein Überlauf nächstes Datenbyte holen
      INC $D020    ; Rahmenfarbe hochzählen
      INC AA04+$02 ; Schreibzeiger (high) erhöhen
      JMP AA03     ; Nächstes Datenbyte holen

; Ein Byte von Band einlesen
P_AE: LDY #$08     ; Bitzähler initialisieren
AE00: JSR P_AF     ; Ein Bit vom Band holen
      BNE P_AC     ; Bei Lesefehler zu flimmerndem Bildschirm wechseln
      ROL T002     ; Gelesenes Bit von rechts in Adresse T002 schieben
      DEY          ; Bitzähler erniedrigen
      BNE AE00     ; Rücksprung falls noch nicht 8 Bit
      LDA T002     ; Gelesenes Byte nach A holen
      RTS

; Ein Bit von Band einlesen
; 1..339 Takte: 0-Bit, 340..539 Takte: 1-Bit, 540.. Takte: Lesefehler
P_AF: LDA $DC0D    ; Interruptregister CIA 1 holen
      AND #$10     ; Warten auf Impuls vom Band
      BEQ P_AF
      LDA $DD0D    ; Interruptregister CIA 2 löschen
      PHA          ; und merken
      LDA #$19     ; Timer neu laden und im "One Shot"-Betrieb starten
      STA $DD0E    ; ... CIA 2 Timer A
      STA $DD0F    ; ... CIA 2 Timer B
      LDA $D020    ; Rahmenfarbe holen
      EOR #$05     ; blinken lassen
      STA $D020    ; und zurückschreiben
      PLA          ; Wert Interruptregister zurückholen
      AND #$03     ; Bits für Unterlauf Timer A/Timer B isolieren
      LSR A        ; Unterlauf Timer A nach CF, Unterlauf Timer B nach ZF
      RTS

; Fehler: Bildschirm flimmert endlos
P_AG: LDX #$08     ; Bytezähler initialisieren
AG00: LDA T000,X   ; Cartridge-Signatur byteweise lesen
      STA $8000,X  ; und an Adresse $8000 kopieren
      DEX          ; Bytezähler vermindern
      BPL AG00     ; Rücksprung falls noch nicht alle Bytes kopiert
      LDA #$1B     ; Bildschirm einschalten
      STA $D011
AG01: INC $D020    ; Rahmenfarbe erhöhen
      DEC $D021    ; Hintergrundfarbe erhöhen
      INC $D800,X  ; Gesamten Inhalt des Farb-RAM erhöhen
      INC $D900,X
      INC $DA00,X
      INC $DB00,X
      INX          ; Schreibzeiger für Farb-RAM erhöhen
      BNE AG01     ; endlos Rahmenfarbe, Hintergrundfarbe und Farb-RAM erhöhen
      BEQ AG01

T000: DW P_AG                ; Reset-Vektor des Steckmoduls
      DW P_AG                ; NMI-Vektor des Steckmoduls
T001: DB $C3,$C2,$CD,$38,$30 ; Steckmodul-Signatur "CBM80"

T002: DB $00                 ; Empfangenes Datenbyte

T003: DB $00                 ; Prüfsumme

T004: DB $00,$00             ; Endadresse des zu ladenden Programms (low/high)

T005: DB $00,$00             ; Einsprung des zu ladenden Programms (low/high)

T006: DB $00                 ; Sequenznummer des zu ladenden Programms

P_AH: LDA #$01    ; Sequenznummer des nachzuladenden Programms
      STA T006    ; initialisieren
      JSR $FD15   ; Kernal-Sprungvektoren wiederherstellen
      LDY #$04    ; Bytezähler für Cartridge-Signatur
AH00: LDA T001,Y  ; Zeichenweise Signatur "CBM80" lesen
      CMP $8004,Y ; Mit Modul-Bereich ab $8000 vergleichen
      BNE AH01    ; Sprung falls kein Steckmodul erkannt
      DEY         ; Lesezeiger erniedrigen
      BPL AH00    ; Rücksprung falls noch nicht alle Zeichen verglichen
      BMI P_AG    ; Sprung falls Steckmodul erkannt
AH01: LDY #$08
AH02: LDA T000,Y  ; Reset-/NMI-Vektor und Steckmodul-Signatur
      STA $8000,Y ; nach $8000 umkopieren
      DEY         ; Schreibzeiger erniedrigen
      BPL AH02    ; Rücksprung falls noch nicht alle Bytes kopiert
AH03: JSR P_AA    ; Programmteil mit Sequenznummer T006 laden
      JMP AH03    ; Nächsten Programmteil laden

Auf diesen Code folgen auf der Kassette noch weitere 666 Bytes, die keine Funktion besitzen, jedoch den Ladevorgang unnötigerweise um rund 13 Sekunden verlängern.