Knight Tyme/Schnelllader

Aus C64-Wiki
Zur Navigation springenZur Suche springen

<< zurück zu Knight Tyme oder Stormbringer


Knight Tyme/Schnelllader und Stormbringer/Schnelllader: Die folgenden Abschnitte stellen den disassemblierten Kassetten-Schnelllader der Spiele Knight Tyme und Stormbringer dar. Sie sind gegliedert in einzelne Codeblöcke, die gemäß ihrer Funktion während des Ladens gruppiert sind.


Autostart-Mechanismus des Schnellladers[Bearbeiten | Quelltext bearbeiten]

Lädt man "Knight Tyme" oder "Stormbringer" von Band, so wird als erstes ein Datenblock in den Adressbereich $0326—$04FF geladen. Dieser überschreibt den OUTPUT-Vektor der Kernals mit der Startadresse des Schnellladers, wodurch dieser unmittelbar nach dem Abschluss des Ladevorgangs automatisch gestartet wird.


ORG $0326

      DW P_AA  ; OUTPUT-Vektor (umgebogen auf Schnelllader)
      DW $F6ED ; STOP-Vektor (Originalwert)
      DW $F13E ; GETIN-Vektor (Originalwert)
      DW $F32F ; CLALL-Vektor (Originalwert)
      DW $FE66 ; USR-Routine (Originalwert)
      DW $F4A5 ; LOAD-Vektor (Originalwert)
      DW $F5ED ; SAVE-Vektor (Originalwert)

; Füllbytes
      DB $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

Initialisierung und Hauptschleife[Bearbeiten | Quelltext bearbeiten]

Die Initialisierungsroutine des Schnellladers setzt alle Bildschirmfarben auf "schwarz", so dass der Schirm bis zum Erscheinen des Ladebildschirms dunkel bleibt. Ferner bereitet sie Timer A von CIA1 für das Ausmessen der Impulse vom Band vor, lässt diese Impulse als einzige Interruptquelle zu und richtet den Interruptvektor auf diejenige Routine, die dann die Verarbeitung der Banddaten übernimmt. Anschließend wartet sie, bis die ersten drei Programmteile (der Ladebildschirm, die Begleitmelodie und das Spiel) geladen sind und führt diese dann aus, während die Interruptroutine das eigentlich Spiel von Band liest. Sobald das Laden von "Knight Tyme" oder "Stormbringer" abgeschlossen ist, startet sie dieses Spiel durch einen Übergang zur BASIC-Interpreterschleife, woraufhin das Startmenü dieses Spiels auf dem Bildschirm erscheint.

P_AA: JMP P_AD           ; Sprung zum Start des Schnelllader
P_AB: JMP $F800          ; Sprung zur Initialisierung des integrierten Spiels
P_AC: JMP $F803          ; Sprung zur Ausführung des integrierten Spiels

T000: DB $03             ; Anzahl zu lesender Programmteile für das Spiel

P_AD: SEI                ; Interrupts verbieten
      LDX #$FF           ; Stackpointer initialisieren
      TXS
      LDA #$27           ; I/O-Bereich, BASIC und Kernal einblenden
      STA *$01
      LDA #$1F
      STA $DD0D          ; Alle NMIs von CIA2 abschalten
      STA $DC0D          ; Alle IRQs von CIA1 abschalten
      LDY #$00           ; Schreibindex initialisieren
      LDX #$C7           ; 199 Pages überschreiben
AD00: LDA $FCE2,Y        ; Reset-Routine
AD01: STA $0800,Y        ; Kompletten Hauptspeicher $0800...$CEFF überschreiben
      INY                ; Schreibindex erhöhen
      BNE AD00           ; Rücksprung falls noch nicht ganze Page überschrieben
      INC AD01+$02       ; High-Byte des Schreibzeigers erhöhen
      DEX                ; Page-Zähler erniedrigen
      BNE AD00           ; Rücksprung falls noch nicht 199 Pages überschrieben
      LDA #$05           ; Nur I/O-Bereich einblenden, restlicher Adressraum RAM
      STA *$01
      LDA #<P_AF         ; STOP-Vektor auf P_AF richten, STOP-Taste abschalten
      STA $0328
      LDA #>P_AF
      STA $0329
      LDA #$00           ; Farbcode für "schwarz"
      STA $D020          ; Bildschirmrahmen schwarz
      STA $D021          ; Bildschirmhintergrund schwarz
      LDY #$00           ; Schreibindex initialisieren
      LDA #$00           ; Farbcode für "schwarz"
AD02: STA $D800,Y        ; Komplettes Farb-RAM überschreiben
      STA $D900,Y
      STA $DA00,Y
      STA $DB00,Y
      INY                ; Schreibindex erhöhen
      BNE AD02           ; Rücksprung falls noch nicht ganzes Farb-RAM gefüllt
      LDA $DD0D          ; Alle ausstehenden NMIs von CIA2 löschen
      LDA $DC0D          ; Alle ausstehenden IRQs von CIA1 löschen
      LDA #$68           ; Startwert für CIA1 Timer A auf 872 setzen
      STA $DC04
      LDA #$03
      STA $DC05
      LDA #$90           ; Interrupt bei Impuls an Pin "Flag" auslösen
      STA $DC0D          ; IRQ-Vektor direkt auf P_AH richten
      LDA #<P_AH
      STA $FFFE
      LDA #>P_AH
      STA $FFFF
      LDA #$00           ; Prüfsumme initialisieren
      STA *$C1
      STA *$C2
      LDA #<P_AG         ; NMI-Vektor auf P_AG richten, NMI ist damit wirkungslos
      STA $FFFA
      LDA #>P_AG
      STA $FFFB
      LDA #$00           ; Zähler für geladene Programmteile initialisieren
      STA *$06
      CLI                ; Interrupts zulassen
AD03: LDA *$06           ; Anzahl bisher gelesener Programmteile holen
      CMP T000           ; und mit Anzahl zu lesender Programmteile vergleichen
      BNE AD03           ; Weiter warten, falls ungleich
      LDA #$00           ; Zähler für gelesene Programmteile zurücksetzen
      STA *$06
      LDA *$C1           ; Berechnete Prüfsumme holen
      CMP *$C2           ; und mit gelesener Prüfsumme vergleichen
      BNE P_AE           ; Ladevorgang neu starten falls ungleich
      JSR P_AB           ; Initialisierung des geladenen Programms
AD04: JSR P_AC           ; Kurzzeitige Ausführung des geladenen Programms
      LDA *$06           ; Schon ein weiterer Programmteil geladen?
      BEQ AD04           ; Rücksprung falls noch nicht
      SEI                ; Interrupts verbieten
      LDA #$27           ; I/O-Bereich und alle ROMs einblenden
      STA *$01
      LDA *$C1           ; Errechnete Prüfsumme holen
      CMP *$C2           ; und mit gelesener Prüsumme vergleichen
      BNE P_AE           ; Ladevorgang neu starten falls ungleich
      LDX *$02           ; Endadresse des geladenen Programmteils holen
      LDY *$03
      STX *$2D           ; Endadresse als Programmende/Start der Variablen merken
      STY *$2E
      LDA #$01           ; Low-Byte des Basic-Programmstarts
      STA *$2B
      STX *$AE           ; Endadresse als Zeiger auf Programmende merken
      STY *$AF
      LDA #$08           ; High-Byte des BASIC-Programmstarts
      STA *$2C           ; BASIC-Programmstart nun auf $0801
      LDA #$00           ; Nullbyte vor Programmstart ablegen
      STA $0800
      LDA #$CA           ; Output-Vektor auf ursprünglichen Wert $F1CA setzen
      STA $0326
      LDA #$F1
      STA $0327
      LDA #$00
      STA $D418          ; Lautstärke auf 0 setzen
      STA $D015          ; Alle Sprites ausschalten
      JSR $FF84          ; CIAs initialisieren
      JSR $A663          ; BASIC-Befehl CLR
      JSR $A68E          ; Programmzeiger auf BASIC-Start
      LDA #$00           ; In Programm-Modus umschalten
      STA *$9D
      JMP $A7AE          ; und zur Interpreterschleife

; Neustart des Ladevorgangs
P_AE: JMP P_AD

; Wirkungslose Stop-Routine
P_AF: LDA *$91           ; Flag für Stop-Taste holen
      RTS

Interruptroutine[Bearbeiten | Quelltext bearbeiten]

Die Interruptroutine des Schnellladers (Routine P_AH) lädt selbständig beliebig viele Datenblöcke von Band in den Hauptspeicher des C64 und und erhöht anschließend jeweils den Blockzähler an Adresse $0006. Je nachdem, welche Phase des Ladevorgangs (beispielsweise bitweise oder byteweise Synchronisation, oder Einlesen des Headers) gerade aktiv ist, zeigt der BCC-Befehl bei AH01 auf den jeweils benötigten Codeabschnitt.

; Rückkehr aus NMI
P_AG: RTI                ; Rücksprung ohne weitere Aktionen

; Interruptroutine
P_AH: PHA                ; A auf Stack retten
      TYA                ; Y-Register auf Stack retten
      PHA
      LDA $DC05          ; High-Byte von CIA1 Timer A nach A holen
AH00: LDY #$19           ; Timer A neu laden und starten (one shot)
      STY $DC0E
      EOR #$02           ; Bit 9 des Timerstands von CIA1 Timer A invertieren
      LSR A              ; in CF übertragen (Timerstand>=512: 0-Bit, <512: 1-Bit)
      LSR A
      ROL *$A9           ; und von rechts in das empfangene Byte schreiben
      LDA *$A9           ; Empfangenes Byte nach A holen
AH01: BCC AH02           ; Zentraler Sprungverteiler, falls 0-Bit herausgeschoben
      BCS AH04           ; sonst Ende der Interruptroutine
; 1. Einsprung: Suche nach dem 1. Synchronisationszeichen
AH02: CMP #$40           ; Synchronisationszeichen $40 empfangen?
      BNE AH04           ; zum Ende der Interruptroutine falls nicht
      LDA #AH06-AH01-$02 ; sonst nächstes Byte beim 2. Einsprung verarbeiten lassen
      STA AH01+$01
AH03: LDA #$FE           ; %11111110, Empfang eines Byte vorbereiten
      STA *$A9
AH04: LDA $DC0D          ; Alle Interruptanforderungen von CIA1 löschen
AH05: PLA                ; Y von Stack zurückholen
      TAY
      PLA                ; A von Stack zurückholen
      RTI                ; Rückkehr aus Interrupt
; 2. Einsprung: Suche nach dem 2. Synchronisationszeichen
AH06: CMP #$40           ; Weitere Synchronisationszeichen $40 überlesen
      BEQ AH03
      CMP #$5A           ; Synchronisationszeichen $5A empfangen?
      BEQ AH07           ; dann Synchronisation erfolgreich
      LDA #AH02-AH01-$02 ; sonst Resynchronisation, Sprung nach AH02 vorbereiten
      STA AH01+$01
      BNE AH03           ; Unbedingte Rückkehr aus Interrupt
AH07: LDA #AH08-AH01-$02 ; Nächstes Byte bei 3. Einsprung verarbeiten lassen
      STA AH01+$01
      LDA #$00           ; Prüfsumme initialisieren
      STA *$C1
      BEQ AH03           ; Unbedingte Rückkehr aus Interrupt
; 3. Einsprung: Header einlesen und in Speicher schreiben
AH08: STA *$02           ; Von Band gelesenes Headerbyte in $02...$05 bereitstellen
      INC AH08+$01       ; Schreibadresse für Headerbytes erhöhen
      LDA AH08+$01       ; Schreibadresse für Headerbytes holen
      CMP #$06           ; und mit Header-Endadresse+1 vergleichen
      BNE AH03           ; Rückkehr aus Interrupt falls noch nicht alle Headerbytes
      LDA #AH09-AH01-$02 ; sonst nächstes Byte bei 4. Einsprung verarbeiten lassen
      STA AH01+$01
      BNE AH03           ; Unbedingte Rückkehr aus Interrupt
; 4. Einsprung: Datenblock einlesen und in Speicher schreiben
AH09: LDY #$00           ; Schreibindex initialisieren
      DEC *$01           ; I/O-Bereich ausblenden, ganzer Adressraum mit RAM belegt
      STA ($02),Y        ; Gelesenes Byte in Speicher schreiben
      INC *$01           ; I/O-Bereich wieder einblenden
      EOR *$C1           ; Gelesenes Byte in Prüfsumme einarbeiten
      STA *$C1
      INC $D020          ; Rahmenfarbe des Bildschirms erhöhen
      INC *$02           ; Low-Byte des Schreibzeigers erhöhen
      BNE AH10           ; Sprung falls kein Überlauf
      INC *$03           ; Sonst High-Byte des Schreibzeigers erhöhen
AH10: DEC $D020          ; Rahmenfarbe des Bildschirms erniedrigen
      LDA *$02           ; Low-Byte der Schreibadresse
      CMP *$04           ; mit Low-Byte der Endadresse+1 vergleichen
      LDA *$03           ; High-Byte der Schreibadresse
      SBC *$05           ; einschließlich Übertrag mit High-Byte der Endadresse+1 vergleichen
      BCC AH03           ; Sprung falls Endadresse noch nicht erreicht
      LDA #AH11-AH01-$02 ; sonst nächstes Byte bei 5. Einsprung verarbeiten lassen
      STA AH01+$01
      BNE AH03           ; Unbedingte Rückkehr aus Interrupt
; 5. Einsprung: Prüfsumme merken und Anzahl gelesener Programmteile hochzählen
AH11: STA *$C2           ; Gelesenes Byte (Prüfsumme) merken
      INC *$06           ; Zähler für Programmteile erhöhen
      LDA #AH02-AH01-$02 ; Nächstes Byte bei 1. Einsprung verarbeiten lassen
      STA AH01+$01
      LDA #$02           ; Schreibadresse für Headerbytes zurücksetzen
      STA AH08+$01
      BNE AH03           ; Unbedingte Rückkehr aus Interrupt