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