Knight Tyme/Schnelllader
<< 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