CyberLoad/Quellcode
<< zurück zu CyberLoad
CyberLoad/Quellcode: Die folgenden Abschnitte stellen den disassemblierten Kassetten-Schnelllader CyberLoad dar. Sie sind gegliedert in einzelne Codeblöcke, die gemäß ihrer Funktion vor dem und während des Ladens gruppiert sind.
Der zugrundeliegende Maschinencode stammt aus der Kassettenversion des Spiels Cauldron. Bei anderen Programmen, die ebenfalls den Schnelllader Cyberload nutzen, können Routinen und Adressen geringfügig abweichen, da zum einen mehrere Versionen von CyberLoad existieren und zum anderen der Schnelllader sehr flexible Konfigurationsmöglichkeiten bietet.
Initialisierung[Bearbeiten | Quelltext bearbeiten]
Die nachfolgenden Codeabschnitte initialisieren den Bildschirm für die zweite Phase des Ladevorgangs und decodieren den ersten Schnelllader. Dieser war in verschlüsselter Form im Bandheader direkt hinter dem Dateinamen gespeichert und findet sich dementsprechend nun im Kassettenpuffer ab Adresse $0351.
Die Routine P_AA
startet nach dem Laden automatisch. Erreicht wird dies dadurch, dass der Codeabschnitt als absolutes Maschinensprache-Programm ab Adresse $029F geladen wird und dort als erstes die Kopie des Interruptvektors mit der Startadresse von P_AA
überschreibt. Üblicherweise ist an Adresse $029F/$02A0 der Vektor des Systeminterrupts zwischengespeichert, so dass er nach dem Ende des Ladevorgangs wiederhergestellt werden kann, sobald die Arbeit der Interruptroutine für den Bandbetrieb erledigt ist. Nun wird stattdessen nach dem Laden der Interruptvektor auf die Routine P_AA
gerichtet — und diese startet, sobald der nächste Timerinterrupt ausgelöst wird.
ORG $029F DW P_AA ; Überschreibt Puffer für Interruptvektor während Bandbetrieb ; Wird nach dem Laden an $FCA8 als neuer Interruptvektor gesetzt DB $00,$91,$90,$91,$00 ; Flags für CIA1/CIA2 ; Initialisierung, wird per Timer-Interrupt nach dem Laden mittels Kernal-Datassettenroutinen aufgerufen P_AA: ORA ($4C,X) ; DB $01,$4C (Flag für "PAL-Version") SEI JSR $E544 ; Bildschirm löschen LDX #T001-T000-1 ; Länge des Meldungstextes minus 1 AA00: LDA #$01 ; Farbcode für "weiß" STA $D945,X ; in 9. Zeile des Farb-RAM schreiben STA $D9E5,X ; in 13. Zeile des Farb-RAM schreiben LDA #$07 ; Farbcode für "gelb" STA $D96D,X ; in 10. Zeile des Farb-RAM schreiben STA $D995,X ; in 11. Zeile des Farb-RAM schreiben STA $D9BD,X ; in 12. Zeile des Farb-RAM schreiben LDA #$A0 ; Inverses Leerzeichen STA $0545,X ; in 9. Zeile des Bildschirmspeicher schreiben STA $05E5,X ; in 13. Zeile des Bildschirmspeichers schreiben LDA #$52 ; Waagrechter Strich unten STA $056D,X ; in 10. Zeile des Bildschirmspeichers schreiben LDA #$77 ; Waagrechter Strich oben STA $05BD,X ; in 12. Zeile des Bildschirmspeichers schreiben LDA T000,X ; Meldungstext STA $0595,X ; in 11. Zeile des Bildschirmspeichers schreiben DEX ; Schreibindex vermindern BPL AA00 ; Rücksprung falls noch nicht Unterlauf LDY #$AB ; Länge des ersten Schnellladers (Routinen P_Bx) AA01: EOR P_BA-$01,Y ; Ersten Schnelllader entschlüsseln STA P_BA-$01,Y DEY ; Zähler vermindern BNE AA01 ; Rücksprung falls noch nicht ganz entschlüsselt EOR #$AF ; A=$7F STA $DC0D ; Alle Interrupts von CIA 1 abschalten BNE P_BA ; Unbedingter Sprung T000: DB $03,$19,$02,$05,$12,$0C,$0F,$01,$04,$20 ; "CYBERLOAD " DB $0E,$0F,$17,$20 ; "NOW " DB $0C,$0F,$01,$04,$09,$0E,$07,$20 ; "LOADING " DB $03,$01,$15,$0C,$04,$12,$0F,$0E ; "CAULDRON" T001: DB $39,$30,$31,$32,$33,$34,$35,$36 ; "90123456" T002: DW P_AA ; IRQ-Vektor T003: DB $6B P_AB: STX $FEC1 ; Wirkungsloser Code LDY $8B8D,X EOR #$7F ; A=$00, Farbcode für "schwarz" STA $D160 ; Bildschirm-Rahmenfarbe setzen STA $D3E1 ; Bildschirm-Hintergrundfarbe setzen ; Wirkungsloser Code, setzt nur X auf $90 und erschwert die Analyse AB00: NOP ; Illegaler Opcode $7A INY NOP $EAF6,Y ; Illegaler Opcode $DC INC $DE4C DEX CPX #$90 BNE AB00 STY $8C8C ; Y ist irrelevant CPY $8C8C BEQ P_BA ; Unbedingter Sprung
Erster Schnelllader[Bearbeiten | Quelltext bearbeiten]
Die nachfolgenden Abschnitte zeigen den Header der ersten Programmdatei auf der Kassette. Er beinhaltet zum einen die bei Datassetten-Dateien üblichen Informationen (Dateityp, Start- und Endadresse sowie Dateinamen), zum anderen findet sich in den üblicherweise nicht belegten Bytes 21...191 der erste Schnelllader in verschlüsselter Form. Normalerweise wird dieser erst nach dem Laden der Initialisierungsroutine decodiert; nachfolgend ist er in bereits entschlüsselter Form wiedergegeben.
Die Aufgabe dieser Laderoutine ist es, den zweiten Schnelllader in einzelnen Abschnitten von Band zu lesen und in einzelnen Blöcken von Band zu lesen. Jedem Abschnitt ist dabei ein in einem Byte gespeicherter Offset vorangestellt, der vor dem Laden zum Schreibzeiger addiert wird und eine entsprechend große Lücke in den geschriebenen Daten erzeugt. Auf diese Weise ist es möglich, den zweiten Schnelllader in den Adressbereich $0002...$03FF zu laden, ohne dabei kritische Inhalte von Zeropage und Stack zu überschreiben.
Die Adresse, ab der die vom Band gelesenen Daten in den Hauptspeicher übertragen werden sollen, findet sich hartcodiert hinter Label BA04
. Um die Zieladresse des Ladevorgangs zu verschleiern, startet der Ladevorgang dabei mit einer Startadresse von $FFD5, auf die aber sofort ein Offset von $2D Bytes aufaddiert wird, um (unter Vernachlässigung des Additionsübertrags) die Zieladresse $0002 zu erhalten. Jedem Datenbyte folgt normalerweise ein 1-Bit; das letzte Byte eines Blocks ist durch die Bitkombination "00" gekennzeichnet, das Ende der nachzuladenden Datei durch die Bitkombination "01".
Der Schnelllader sieht die Möglichkeit vor, das nachgeladene Programm direkt durch einen Sprung an Adresse $0400 (Label P_BD
) zu starten. Hiervon wird aber kein Gebrauch gemacht — stattdessen wird der JSR
-Befehl bei BA07
beim Nachladen des zweiten Schnellladers durch den Opcode für JMP ersetzt, und auf dem beim Nachladen überschriebenen Stack findet sich an der passenden Stelle die Startadresse des dritten Schnellladers minus eins ($0001). Sobald der Prozessor auf den nächsten RTS-Befehl stößt, geht er daher zum zweiten Schnelllader an Adresse $0002 über.
Während des Ladevorgangs erscheint auf dem Bildschirm die Meldung "CYBERLOAD NOW LOADING " gefolgt vom Dateinamen des Hauptprogramms (im Beispiel hier "CAULDRON"). Tatsächlich werden in dieser Phase aber noch der zweite und dritte Schnellllader von Band gelesen, die mit dem Spiel selbst nichts zu tun haben.
PRG $033C ; Programmheader auf Kassette DB $03 ; Code für "Absolute Programmdatei" DW $029F ; Startadresse DW $033B ; Endadresse+1 DB $08 ; Erstes Zeichen des Dateinamens: <Commodore>-<Shift> blockieren DB 'CAULDRON' ; Dateiname DB $20,$20,$20,$20,$20,$20,$20 ; Füller für Dateinamen ; Erster Schnellader, schon entschlüsselt P_BA: BNE P_AB ; Sprung falls von P_AA kommend, kein Sprung falls von P_AB kommend EOR #$16 ; LDA #$16 STA *$71,X ; STA *$01 (Bandmotor an, BASIC-ROM ausblenden) LDY #$85 ; CIA 1 Timer A mit $0285 (645) initialisieren STY $DC04 ; Low-Byte LDY #$02 STY $DC05 ; High-Byte STX $DC0D ; Impuls vom Band löst Interrupt aus (X=$90) BA00: PHA ; Gesammelte Bits retten JSR P_BC ; Ein Bit von Band einlesen PLA ; Gesammelte Bits zurückholen ROL A ; und gerade gelesenes Bit von rechts dazuschieben CMP #$0F ; Synchronisationskennzeichen $0F gelesen? BNE BA00 ; Rücksprung falls nicht BA01: JSR P_BB ; Nächstes Byte von Band einlesen CMP #$0F ; Weiteres Synchronisationszeichen? BEQ BA01 ; Rücksprung falls ja CMP #$F0 ; Invertiertes Synchronisationszeichen? BNE BA00 ; Synchronisation neu starten falls nicht BA02: JSR P_BB ; Ein Byte von Band einlesen CLC ; zum bisherigen Schreibzeiger addieren (Lücke lassen) ADC BA04+$01 STA BA04+$01 ; und zurückschreiben BCC BA03 ; Sprung falls kein Additionsüberlauf INC BA04+$02 ; Schreibzeiger um Additionsüberlauf korrigieren BA03: JSR P_BB ; Ein Byte vom Band holen PHA ; und retten JSR P_BC ; Ein Bit von Band einlesen BCC BA06 ; Sprung falls 0-Bit (=Blockende oder Dateiende) PLA ; Sonst gelesenes Byte zurückholen BA04: STA $FFD5 ; und an Zieladresse schreiben INC BA04+$01 ; Zieladresse erhöhen BNE BA05 ; Sprung falls kein Übertrag INC BA04+$02 ; Übertrag berücksichtigen BA05: BCS BA03 ; Unbedingter Sprung, da immer CS BA06: JSR P_BC ; Ein Bit von Band einlesen BCS BA02 ; Sprung falls 1-Bit (=Lücke lassen und dann weiterladen) BA07: JSR P_BB ; sonst Prüfsumme von Band einlesen (JSR wird beim Nachladen des zweiten Schnellladers durch JMP ersetzt) CMP *$EF ; und mit berechneter Prüfsumme vergleichen BEQ P_BD ; Geladenen Programmteil starten falls gleich JMP $FCE2 ; sonst Reset auslösen ; Ein Byte von Band in A einlesen P_BB: LDA #$01 ; A sammelt 8 empfangene Bits BB00: PHA ; A retten JSR P_BC ; Bit von Band einlesen und nach CF PLA ; A zurückholen ROL A ; Empfangenes Bit von rechts in A schieben BCC BB00 ; Rücksprung falls noch nicht 8 Bit empfangen PHA ; Empfangenes Byte in A retten ADC *$EF ; und in Prüfsumme einarbeiten STA *$EF PLA ; Empfangenes Byte in A zurückholen RTS ; Ein Bit von Band in CF einlesen P_BC: LDA #$10 ; Auf Impuls vom Band warten BC00: BIT $DC0D BEQ BC00 LDA $DC04 ; Low-Byte von CIA 1 Timer A holen CMP $DC04 ; Steht der Timer, weil abgelaufen? BEQ BC01 ; Sprung mit gesetztem CF wenn abgelaufen CLC ; CF löschen wenn noch nicht abgelaufen BC01: LDA #$19 ; CIA 1 Timer A wieder im "One Shot"-Modus starten STA $DC0E BC02: PHP ; Empfangenes Bit im CF retten DEC *$EE ; Zähler für Scrolling vermindern (einmal scrollen pro 32 Bit) BPL BC04 ; Sprung falls kein Unterlauf LDA #$20 ; Zähler wieder auf 32 setzen STA *$EE ; und merken LDY *$FE ; Schreibindex für Animation holen LDA #$A0 ; Reverses Leerzeichen STA $0545,Y ; an zwei Stellen auf Bildschirm darstellen STA $05E5,Y ; und damit vorige Animation löschen INY ; Schreibindex erhöhen CPY #$1E ; Maximalwert erreicht? BCC BC03 ; Sprung wenn nicht LDY #$00 ; Schreibindex zurücksetzen BC03: LDA #$20 ; Leerzeichen STA $0545,Y ; an zwei Stellen auf Bildschirm darstellen STA $05E5,Y ; und damit neue Animation anzeigen STY *$FE ; Schreibindex merken BC04: PLP ; Empfangenes Bit ins CF zurückholen RTS T100: DB $A6,$02,$00,$00,$00,$00 P_BD: ; $0400
Zweiter Schnelllader[Bearbeiten | Quelltext bearbeiten]
Der zweite Schnelllader ist ab Adresse $0002 gespeichert, belegt also sicherlich keinen Speicherplatz, der während des Ladens vom Hauptprogramm überschrieben werden könnte. Nur der erste Teil ist direkt lauffähig; ab Label CC02
an Adresse $0073 ist die Routine noch verschlüsselt und wird unmittelbar vor der Ausführung decodiert. In diesen Vorgang greift auch einmalig ein von den Timern des CIA 2 ausgelöster NMI mit Behandlungsroutine ab P_CB
ein. Das nachfolgende Listing stellt den kompletten Code in decodierter Form dar.
Zu den Merkwürdigkeiten dieser Routine gehört es, dass sie überprüft, ob im RAM unterhalb des Kernals an Adresse $F37A...$F478 nicht lauter gleiche Bytes stehen. Falls dies doch der Fall ist, so überschreibt sich der Schnelllader selbst und bringt damit den C64 zum Absturz. Reproduzieren lässt sich dieser Effekt, indem man vor dem Laden von "CAULDRON" zuerst die folgende BASIC-Zeile und erst dann den "LOAD"-Befehl eingibt:
FOR N=62330 TO 62584:POKE N,42:NEXT
Der Sinn dieser Schutzmaßnahme ist nicht unmittelbar ersichtlich.
PRG $0002 ; Zweiter Schnelllader P_CA: SEI ; Interrupts deaktivieren DEC *$01 ; $06->$05, alle ROMs ausblenden LDA $D011 ; Bildschirm ausschalten AND #$EF STA $D011 LDX #$10 ; Etwa 20 ms Pause CA00: INY BNE CA00 DEX BNE CA00 LDA #$1F ; Startwert für CIA 2 Timer A und B auf $231F (8991) STA $DD04 ; Low-Byte STA $DD06 EOR #$3C ; A=$23 STA $DD05 ; High-Byte STA $DD07 EOR #$32 ; A=$11 STA $DD0E ; CIA 2 Timer A mit Startwert laden und starten (zeitgesteuerte Änderung der Entschlüsselung) EOR #$92 ; A=$83 STA $DD0D ; NMI zulassen bei Unterlauf CIA 2 Timer A und B EOR #$83 ; A=$00 = >P_CB STA $FFFB ; NMI-Vektor auf P_CB richten, Low-Byte EOR #$3B ; A=$3B = <P_CB STA $FFFA ; NMI-Vektor auf P_CB richten, High-Byte BNE P_CC ; Unbedingter Sprung P_CB: BIT $DD0B INC CC02+$02 ; Während des Entschlüsselns per NMI Daten modifizieren PHA LDA CC02+$02 CMP #$E0 BCC CB00 LSR CC02+$02 CB00: LDA CC02+$00 EOR #$B7 STA CC02+$00 PLA RTI P_CC: LDA #$91 ; Zeitgesteuerte Änderung des Entschlüsselungsverfahrens aktivieren STA $DD0E ; CIA 2 Timer A neu laden und starten STA $DD0F ; CIA 2 Timer B neu laden und starten LDX #$8B CC00: TXA ; X=$8B AND #$0F TAY ; Y=$0B EOR CC02-$01,X ; Byteweise verschlüsselten Schnelllader lesen CC01: EOR $0314,Y ; und durch XOR-Verknüpfungen entschlüsseln DEY ; Schon fertig entschlüsselt? BPL CC01 ; Rücksprung falls nicht STA CC02-$01,X ; sonst entschlüsseltes Byte zurückschreiben DEX ; Schreibindex vermindern BNE CC00 ; Rücksprung falls noch nicht fertig entschlüsselt ; Schnelllader, schon entschlüsselt CC02: ROL $0934 ; Eigentlich Daten ($2E,$34,$09) LDA #$3E STA $FFFA LDA $F379,Y ; Prüfen, ob alle Bytes im Bereich $F37A...$F478 gleich CC03: CMP $F379,Y BNE CC05 ; Sprung falls nicht DEY ; sonst weiter vergleichen BNE CC03 ; Rücksprung falls noch nicht 255 Bytes verglichen CC04: STA $0002,Y ; Schnellladeroutinen überschreiben STA $0092,Y ; und Rechner damit zum Absturz bringen STA $0300,Y INY ; Schreibindex erhöhen BNE CC04 ; Rücksprung falls noch nicht 3*256 Bytes geschrieben CC05: LDA #$1B ; Bildschirm einschalten STA $D011 CC06: LDA #$85 ; Startwert von CIA 1 Timer A auf $0285 (645) STA $DC04 ; Low-Byte LDA #$02 STA $DC05 ; High-Byte CC07: LDA #$FF ; Von Band gelesene Daten nach jedem Bit prüfen CC08: PHA ; Aktuell eingelesene Daten retten JSR P_BC ; Ein Bit von Band einlesen und nach CF PLA ; Aktuell eingelesene Daten zurückholen ROL A ; und eingelesenes Bit von rechts hineinschieben CMP #$0F ; Synchronisationszeichen $0F? BNE CC08 ; Rücksprung falls nicht CC09: JSR P_BB ; Ein Byte von Band einlesen CMP #$0F ; Immer noch Synchronisationszeichen $0F? BEQ CC09 ; Rücksprung falls ja CMP #$F0 ; Invertiertes Synchronisationszeichen $F0? BNE CC07 ; Neu synchronisieren falls nicht LDX #$00 ; Schreibindex für Dateiheader initialisieren CC10: JSR P_BB ; Byte von Band holen EOR #$AE ; und entschlüsseln STA *$02,X ; und in Headerpuffer an $02...$05 schreiben INX ; Schreibindex erhöhen CPX #$04 ; Schon vier Byte empfangen? BCC CC10 ; Rücksprung falls noch nicht CC11: JSR P_BB ; Byte von Band holen EOR #$D2 ; und entschlüsseln LDY #$00 ; Schreibindex initialisieren DEC *$01 ; $05->$04, alle ROMs und I/O ausblenden STA ($02),Y ; Gelesenes Byte in Hauptspeicher schreiben INC *$01 ; $04->$05, I/O wieder einblenden INC *$02 ; Low-Byte des Schreibzeigers erhöhen BNE CC12 ; Sprung falls kein Überlauf INC *$03 ; High-Byte des Schreibzeigers erhöhen CC12: LDA *$04 ; Low-Byte der Dateilänge holen BNE CC13 ; Sprung falls nicht 0 DEC *$05 ; High-Byte der Länge vermindern, da Unterlauf bei Low-Byte CC13: DEC *$04 ; Low-Byte der Länge vermindern LDA *$04 ; Verbleibende Anzahl Bytes prüfen ORA *$05 BNE CC11 ; Rücksprung falls nicht 0 CC14: RTS ; Sprung nach P_DA (1.) / nach P_EA (2.)
Dritter Schnelllader[Bearbeiten | Quelltext bearbeiten]
Der dritte Schnelllader übernimmt das Lesen des eigentlichen Hauptprogramms von Band. Neben der hier gezeigten Variante an Adresse $E000 existieren auch zwei Versionen, die an Adresse $0800 bzw. $C800 geladen werden. Auf diese Weise kann auf die Speicheranforderungen des nachzuladenden Programms Rücksicht genommen werden.
Das Laden der Programmdaten geschieht in Blöcken zu jeweils maximal 256 Bytes, denen je ein Header von 8 Bytes Länge vorangestellt ist:
Länge | Inhalt |
---|---|
2 | Startadresse des Blocks im Hauptspeicher (Little Endian-Format) |
1 | Blocklänge in Bytes ($00 = 256 Bytes) |
1 | Prüfsumme (XOR-Verknüpfung von Rest des Headers und Daten) |
1 | Flags zur Ausführung von Musikroutinen und Programmteilen Bit 7: Routine ausführen, Einsprung an Adresse $16/$17 (0=nein, 1=ja) Bit 6: Nächster Block eingelesen (0=nein, 1=ja) Bit 5: Datassettenmotor während Routine ausschalten (0=nein, 1=ja) Bit 4: Musikroutine installiert (0=nein, 1=ja) Bit 3: Rasterzeileninterrupt abschalten (0=nein, 1=ja) Bit 2: Bildschirm einschalten (0=nein, 1=ja) Bit 1: Musikwiedergabe aktiv (0=nein, 1=ja) Bit 0: Fade-out erforderlich (0=nein, 1=ja) |
1 | Flag zur Steuerung des Ladebildschirms Bit 7: Ladebildschirm initialisieren (0=nein, 1=ja) |
2 | Einsprungadresse für Ausführung geladener Programmteile (Little Endian-Format) |
Das Laden findet in einer Endlosschleife statt, die schließlich dadurch verlassen wird, dass der letzte geladene Block einen Teil des Schnellladers überschreibt (ab Label DF17
, Adressbereich $E297...$E2B2). Der Code, der dabei ersatzweise geladen wird, findet sich im nächsten Abschnitt.
Der dritte Schnelllader ist in vielfacher Weise konfigurierbar. Er kann eine mehrzeilige Meldung auf dem Textbildschirm anzeigen (in komprimierter Form abgelegt ab Label T400
), per Rasterzeileninterrupt in einer wählbaren Bildschirmzeile einen Scrolltext darstellen (gespeichert ab Label T401
), Musik abspielen, den Bildschirmrahmen flackern lassen, an beliebiger Stelle ein blinkendes Sprite zeigen (Definition ab Label T200
, Farbverlauf ab Label T302
, hier nicht genutzt), einen geladenen Programmteil ausführen und währenddessen auf Wunsch auch den Datassettenmotor abschalten, oder (durch Aufruf einer Musikroutine alle 20 ms, hier nicht genutzt) eine zwischenzeitlich geladene Begleitmelodie abspielen.
Und wer sich die Mühe gemacht hat, die Routine bis hierher zu analysieren, der erhält an Label T301
die wohlverdiente Anerkennung: "HACKERS, FUCK OFF AND DIE...".
PRG $E000 P_DA: JSR P_DG ; VIC initialisieren, Bildschirm löschen LDA #$00 STA *$14 ; Flags initialisieren STA DF01+$01 ; Sprungverteiler für Schnelllader initialisieren STA *$EE ; Zähler für Softscrolling initialisieren LDA #<T401 ; Zeiger auf zu scrollende Meldung initialisieren STA *$0E ; Low-Byte LDA #>T401 STA *$0F ; High-Byte LDA #$1B ; Bildschirm einschalten STA $D011 LDA #$00 ; Rasterinterrupt in Rasterzeile 0 STA $D012 LDA #$01 ; Rasterinterrupt STA $D019 ; Anforderungen löschen STA $D01A ; und aktivieren durch Setzen des Masken-Bit LDA #<P_DF ; IRQ-Vektor zeigt auf Routine P_DF STA $FFFE ; Low-Byte LDA #>P_DF STA $FFFF ; High-Byte LDA #<DA07 ; NMI-Vektor zeigt auf Adresse DA07 STA $FFFA ; Low-Byte LDA #>DA07 STA $FFFB ; High-Byte LDA #$85 ; Startwert für CIA 1 Timer A auf $0285 (645) STA $DC04 ; Low-Byte LDA #$02 STA $DC05 ; High-Byte LDX #$12 ; Stackzeiger TXS ; auf $12 setzen BNE DA01 ; Unbedingter Sprung ; Code, der bei "CAULDRON" nicht ausgeführt wird LDA #$18 ; A=24 STA $D00E ; als X-Position von Sprite 7 setzen LDA #$E5 ; A=229 STA $D00F ; als Y-Position von Sprite 7 setzen LDA #$00 ; MSB der X-Position aller Sprites löschen STA $D010 LDA #$80 ; Sprite 7 aktivieren STA $D015 LDA #$00 ; Lesezeiger für Farbverlauf an T302 initialisieren STA *$08 STA $D02E ; Farbe von Sprite 7 auf "schwarz" LDX #$3E ; X auf Länge der Spritedefinition=63 Bytes setzen DA00: LDA T200,X ; Spritedefinition byteweise lesen STA $0200,X ; und umkopieren nach $0200...$023E DEX ; Lese-/Schreibindex vermindern BPL DA00 ; Rücksprung falls noch nicht ganzes Sprite umkopiert LDA #$08 ; Binärdaten in Block 8 (Adressbereich $0200...$023E) STA $07FF ; als Spritedefinition für Sprite 7 verwenden DA01: LDA #$00 ; Flag für "Sprite blinken lassen" STA *$0A ; aktivieren LDA DE02+$01 ; Rasterinterrupt aktiv? BEQ DA03 ; Sprung falls nicht LDX #$27 ; Schreibindex auf Zeilenlänge-1 initialisieren LDA #$01 ; Farbcode für "weiß" DA02: STA $DB98,X ; Farb-RAM der zweitletzten Bildschirmzeile füllen DEX ; Schreibindex vermindern BPL DA02 ; Rücksprung falls noch nicht ganze Zeile gefüllt DA03: CLI ; Interrupts aktivieren DA04: LDA #$20 DA05: BIT *$14 ; Warten, bis Bit 6 an Adresse $14 gesetzt ("Block vollständig empfangen") BVC DA05 ; (geschieht an Adresse DF17) PHP ; Statusregister retten DA06: LDA $D011 ; Warten, bis Rasterzeile 256 erreicht BPL DA06 SEI ; Interrupts deaktivieren LDA $DD04 ; Laufen beide Timer von CIA 2 synchron? CMP $DD06 BNE DA07 ; Sprung falls nicht CMP $DD04 ; Timer 1 von CIA 2 abgelaufen? BNE DA08 ; Sprung falls nicht DA07: ASL DF13+$01 ; Anzahl Headerbytes von 8 auf 16 verdoppeln ROL *$17 ; Möglicherweise weiteres Laden unmöglich machen? ASL *$16 ROL *$14 DA08: CLI ; Interrupts wieder aktivieren BIT *$15 ; Ist Bildschirm schon initialisiert? BPL DA09 ; Sprung falls ja JSR P_DG ; Bildschirm initialisieren LDA *$15 ; und Flag für "Bildschirm initialisieren" löschen AND #$7F STA *$15 DA09: PLP ; Statusregister zurückholen BPL DA04 ; Rücksprung falls Bit 7 an Adresse *$14 nicht gesetzt ("Routine ausführen") BEQ DA10 ; Sprung falls Bit 5 an Adresse *$14 nicht gesetzt ("Motor währenddessen ausschalten") LDA *$01 ; Motor ausschalten ORA #$20 STA *$01 DA10: LDA *$14 ; Bit für "Routine ausführen" zurücksetzen AND #$7F STA *$14 JSR P_DB ; Routine ausführen LDA *$01 ; Motor ggf. wieder einschalten AND #$DF STA *$01 JMP DA04 ; Auf nächsten Block warten P_DB: JMP ($0016) ; Spritedefinition (drei ineinandergeschachtelte Rechtecke) T200: DB $FF,$FF,$FF,$FF,$FF,$FF,$C0,$00,$03,$CF,$FF,$F3,$CF,$FF,$F3,$CC DB $00,$33,$CC,$FF,$33,$CC,$FF,$33,$CC,$C3,$33,$CC,$C3,$33,$CC,$C3 DB $33,$CC,$C3,$33,$CC,$C3,$33,$CC,$FF,$33,$CC,$FF,$33,$CC,$00,$33 DB $CF,$FF,$F3,$CF,$FF,$F3,$C0,$00,$03,$FF,$FF,$FF,$FF,$FF,$FF ; VIC-Rasterinterrupt-Routine unterhalb des gescrollten Bilschirmteils P_DC: LDA #$00 ; 38 Spalten, kein Scrolling in X-Richung STA $D016 ; aktivieren LDA #$00 ; Rasterzeile 0 STA $D012 ; als Rasterzeile für nächsten Interrupt setzen CLI ; Interrupts wieder zulassen (wegen Datassette) LDA *$EE ; Scrolling in X-Richtung SEC ; um 1 nach links bewegen SBC #$01 AND #$07 ; dann auf Wertebereich 0...7 begrenzen STA *$EE ; und zurückschreiben BCC DC00 ; Sprung falls Unterlauf PLA ; sonst A zurückholen RTI ; und aus Interrupt zurückkehren DC00: TYA ; Y retten PHA LDY #$00 ; Index für Umkopieren der Meldung initialisieren DC01: LDA $0799,Y ; Meldungstext byteweise lesen STA $0798,Y ; und um 1 Zeichen (=8 Pixel) nach links scrollen INY ; Index erhöhen CPY #$27 ; Schon 39 Zeichen umkopiert? BCC DC01 ; Sprung falls noch nicht LDY #$00 ; Leseindex initialisieren LDA ($0E),Y ; Weiteres Zeichen für Meldungstext holen BPL DC02 ; Sprung falls nicht Ende-Kennzeichen $FF LDA #<T401 ; sonst Lesezeiger auf Anfang des Meldungstexts richten STA *$0E ; Low-Byte LDA #>T401 STA *$0F ; High-Byte LDA ($0E),Y ; Zeichen vom Anfang der Meldung holen DC02: STA $07BF ; und an gescrollten Text anhängen INC *$0E ; Lesezeiger weiterbewegen BNE DC03 ; Sprung falls kein Überlauf INC *$0F ; sonst Überlauf berücksichtigen DC03: PLA ; Y zurückholen TAY PLA ; A zurückholen RTI ; Rückkehr aus Interrupt P_DD: JSR $FFFF ; Einsprung Musikwiedergabe (alle 20 ms), hier nicht verwendet DD00: LDA T300 ; A=$83 STA $DD0D ; CIA 2 löst bei Unterlauf von Timer 1 oder Timer 2 einen NMI aus DD01: PLA ; Y zurückholen TAY PLA ; X zurückholen TAX PLA ; A zurückholen RTI ; Rückkehr aus Interrupt ; VIC-Rasterinterrupt-Routine P_DE: STA $D019 ; Rasterinterrupt-Request löschen LDA $D012 ; Aktuelle Bildschirmzeile holen CMP DE03+$01 ; Anfang des gescrollten Bereichs erreicht (Rasterzeile $E2=226)? BCC DE01 ; Sprung falls noch nicht erreicht CMP DE00+$01 ; Ende des gescrollten Bereichs erreicht (Rasterzeile $F3=243)? BCS P_DC ; Sprung falls erreicht oder überschritten ; VIC-Rasterinterrupt-Routine für gescrollten Bereich LDA *$EE ; Aktuelles Scrolling der Laufschrift in X-Richtung STA $D016 ; aktivieren DE00: LDA #$F3 ; Rasterzeile 243 STA $D012 ; als Rasterzeile für nächsten Interrupt setzen CLI ; Interrupts wieder zulassen (unnötig, denn RTI beinhaltet PLP) PLA ; A zurückholen RTI ; Rückkehr aus Interrupt ; VIC-Rasterinterrupt-Routine für nicht gescrollten Bereich DE01: CLI DE02: LDA #$FF ; Flag für "Rasterinterrupt aktiv" ($00="Nein") BEQ DE04 DE03: LDA #$E2 ; Rasterzeile 226 STA $D012 ; als Rasterzeile für nächsten Interrupt setzen DE04: INC *$08 ; Lesezeiger für Farbe von Sprite 7 erhöhen TXA ; X retten PHA TYA ; Y retten PHA LDA *$0A ORA *$09 BNE DE05 LDA *$08 ; Lesezeiger für Farbe von Sprite 7 holen AND #$1F ; auf Wertebereich 0...31 beschränken TAX ; und als Index für Zugriff auf Farbtabelle nutzen LDA T302,X ; Farbcode holen STA $D02E ; und als Farbe für Sprite 7 setzen INC *$09 DE05: LDA $0075 ; Bei CAULDRON A=$09 EOR #$0A ; Bei CAULDRON A=$03 AND *$14 ; Musikwidergabe erforderlich? BEQ DD00 ; Rückkehr aus Interrupt, falls keine Musikwiedergabe ; Code, der bei "CAULDRON" nicht ausgeführt wird CMP #$02 ; Flag "Musikwiedergabe" prüfen BEQ P_DD ; Sprung falls gesetzt BCC DE06 ; Sprung falls nicht gesetzt JSR $FFFF ; Initialisierung der Musikwiedergabe, hier nicht verwendet LDA #$0F ; Start-Lautstärke für Fade-out STA *$0C ; setzen LDA #$FE ; Flag für "Fade-out erforderlich" JMP DE07 ; löschen und Rückkehr aus Interrupt DE06: LDA *$0C ; Lautstärke schon auf 0 BEQ DD00 ; Sprung falls ja LDA *$08 AND #$0F ; sonst bei jedem 16. Rasterinterrupt BNE P_DD DEC *$0C ; die Lautstärke um eine Stufe vermindern LDA *$0C ; Aktuelle Lautstärke holen STA $D418 ; und an SID übertragen BNE P_DD ; Sprung falls Lautstärke noch nicht auf 0 LDA #$FC ; sonst Flags für Musikwiedergabe löschen DE07: AND *$14 STA *$14 JMP DD00 ; Rückkehr aus Interrupt ; Interruptroutine P_DF: PHA ; A retten LDA $D019 ; Rasterzeileninterrupt? BMI P_DE ; Sprung falls ja LDA $DC0D ; Interrupt-Register von CIA 1 LSR A ; Empfangenes Bit nach CF (CS falls Timer A abgelaufen) LDA #$11 ; CIA 1 Timer A neu starten STA $DC0E BEQ DF00 ; Unnötig, springt nie LDA *$02 ; Rahmenfarbe blinken lassen EOR #$05 STA *$02 STA $D020 DF00: LDA $DC0D ; Alle Interruptanforderungen von CIA 1 löschen LDA #$00 STA *$09 DF01: BEQ DF02 ; Sprungverteiler, unbedingter Sprung ; Bitweise Synchronisation mit Banddaten DF02: ROL *$0B ; Empfangenes Bit von rechts in Adresse $0B schieben LDA *$0B ; Empfangenes Byte holen CMP #$0F ; und mit Synchronisationszeichen $0F vergleichen BNE DF04 ; Rückkehr aus Interrupt falls ungleich LDA #DF05-DF02 ; sonst Sprung von DF02 nach DF05 vorbereiten STA DF01+$01 LDA #$00 ; Sprung von DF06 nach DF07 vorbereiten STA DF06+$01 DF03: LDA #$01 ; Empfang des nächsten Byte vorbereiten STA *$0B ; und dafür Bitzähler an Adresse $0B initialisieren DF04: PLA ; Rückkehr aus Interrupt RTI ; Byteweise Synchronisation mit Banddaten DF05: ROL *$0B ; Empfangenes Bit von rechts in Adresse $0B schieben BCC DF04 ; Rückkehr aus Interrupt falls noch nicht 8 Bit LDA *$0B ; Empfangenes Byte holen EOR *$EF ; und in Prüfsumme einarbeiten STA *$EF TXA ; X retten PHA TYA ; Y retten PHA LDA *$0B ; Empfangenes Byte nach A holen LDX #$01 ; Empfang des nächsten Byte vorbereiten STX *$0B ; und dafür Adresse $0B als Bitzähler initialisieren DF06: BCS DF07 ; Sprungverteiler, unbedingter Sprung DF07: CMP #$0F ; Immer noch Synchronisationszeichen $0F? BEQ DF09 ; dann Sprung von DF06 nach DF11 vorbereiten DF08: LDA #$00 ; sonst Neustart der Synchronisation STA DF01+$01 LDA #$FF ; Nach jedem Bit auf Synchronisationszeichen prüfen STA *$0B JMP DD01 ; Rückkehr aus Interrupt DF09: LDA #DF11-DF07 STA DF06+$01 DF10: JMP DD01 DF11: CMP #$0F ; Immer noch Synchronisationszeichen $0F? BEQ DF10 ; dann Rückkehr aus Interrupt falls ja CMP #$CC ; Synchronisationszeichen $CC? BNE DF08 ; Erneute Synchronisation falls nicht LDA #DF12-DF07 ; sonst Sprung von DF07 nach DF12 vorbereiten STA DF06+$01 LDA #$00 STA *$0D ; Schreibindex für Dateiheader initialisieren STA *$EF ; Prüfsumme initialisieren JMP DD01 ; Rückkehr aus Interrupt ; 8 Byte langen Blockheader von Band einlesen DF12: LDY *$0D ; Schreibindex für Header holen STA $0010,Y ; Dateiheader nach $0010...$0017 einlesen INY ; Schreibindex erhöhen STY *$0D ; und merken DF13: CPY #$08 ; Schon 8 Headerbytes empfangen? BCS DF14 ; Sprung falls ja JMP DD01 ; sonst Rückkehr aus Interrupt DF14: LDA #$00 ; Schreibindex initialisieren STA *$0D LDA #DF16-DF07 ; Sprung von DF06 nach DF16 vorbereiten STA DF06+$01 DF15: JMP DD01 ; Block (maximal 256 Bytes) von Band einlesen DF16: LDY *$0D ; Schreibindex holen LDX #$04 ; I/O-Bereich und alle ROMs ausblenden STX *$01 STA ($10),Y ; Gelesenes Byte in Speicher schreiben LDX #$05 ; I/O-Bereich wieder einblenden STX *$01 INY ; Schreibindex erhöhen STY *$0D ; und merken DEC *$12 ; Bytezähler vermindern BNE DF15 ; Rücksprung falls noch nicht 0 INC *$02 ; Rahmenfarben wechseln LDA *$EF ; Prüfsumme holen BEQ DF17 ; Sprung falls 0 STA *$0A ; sonst Sprite nicht mehr blinken lassen DF17: LDA CC02+$00 ; A=$2E EOR #$6E ; A=$40 ORA *$14 ; Bit 6 an Adresse $14 setzen("Block vollständig empfangen") STA *$14 LSR A ; Bit 2 von Adresse $14 ins CF holen LSR A LSR A LDA $D011 ; Bildschirm abschalten AND #$6F BCC DF18 ; falls Bit 2 an Adresse $14 gelöscht ORA #$10 ; sonst Bildschirm einschalten DF18: STA $D011 LDA *$14 ; Bit 3 von Adresse $14 prüfen AND #$08 BEQ DF19 ; Sprung falls nicht gesetzt LDA #$00 ; sonst kein Rasterinterrupt in Zeile 226 STA DE02+$01 DF19: LDA *$14 ; Adresse $14 nochmals prüfen AND #$13 CMP #$11 ; Code für "Fade-out"? BNE DF20 ; Sprung falls nicht LDA #$00 ; Flag für "Keine Musik" STA *$0C ; merken STA $D418 ; Lautstärke auf 0 DF20: JMP DF08 ; Neustart der Synchronisation T300: DB $83 T301: DB 'HACKERS, FUCK OFF AND DIE...' ; Farbverlauf für blinkendes Sprite (von der dunkelsten zur hellsten Farbe und wieder zurück) T302: DB $00,$06,$09,$02,$0B,$04,$08,$0C,$0E,$05,$0A,$03,$0F,$07,$0D,$01 DB $0D,$07,$0F,$03,$0A,$05,$0E,$0C,$08,$04,$0B,$02,$09,$06,$00,$00 T303: DB $B7,$93 P_DG: LDA $DD00 ; VIC-Speicherbank auf $0000-$3FFF setzen ORA #$03 STA $DD00 LDA #$14 ; Adressen von VideoRAM und CharGen auf Defaultwerte STA $D018 LDA $D011 ; Bildschirm einschalten AND #$1F ORA #$10 STA $D011 LDA #$00 ; 38 Spalten, kein Scrolling in X-Richtung STA $D016 ; Bildschirm löschen LDX #$00 ; Schreibindex initialisieren LDA #$20 ; Leerzeichen DG00: STA $0400,X ; Bereich $0400...$04FF löschen STA $0500,X ; Bereich $0500...$05FF löschen STA $0600,X ; Bereich $0600...$06FF löschen STA $06E8,X ; Bereich $06E8...$07E7 löschen INX ; Schreibindex erhöhen BNE DG00 ; Rücksprung falls noch nicht 256 Bytes geschrieben LDA #<T400 ; Low-Byte Startadresse der Meldungstexte STA *$03 ; als Low-Byte des Lesezeigers LDA #>T400 ; High-Byte Startadresse der Meldungstexte STA *$04 ; als High-Byte des Lesezeigers LDA #$00 ; Low-Byte der Startadresse des Video-RAM STA *$05 ; als Low-Byte des Schreibzeigers LDA #$04 ; High-Byte der Startadresse des Video-RAM STA *$06 ; als High-Byte des Schreibzeigers LDY #$00 ; Leseindex initialisieren LDA ($03),Y ; Farboode für "Hellrot" STA $D020 ; als Rahmenfarbe setzen INY ; Leseindex erhöhen LDA ($03),Y ; Farbcode für "Schwarz" STA $D021 ; als Hintergrundfarbe setzen INY ; Leseindex erhöhen LDX #$00 ; Schreibindex initialisieren, bleibt immer 0 DG01: LDA ($03),Y ; Byte aus Meldung holen CMP #$FF ; Steuercode? BEQ DG04 ; Sprung falls ja DG02: STA ($05,X) ; sonst Zeichen auf Bildschirm ausgeben LDA *$06 ; High-Byte des Schreibzeigers auf Video-RAM holen PHA ; und retten AND #$03 ; Entsprechendes High-Byte der Adresse im Farb-RAM berechnen ORA #$D8 STA *$06 ; und als Adresszeiger setzen LDA *$07 ; Zeichenfarbe holen STA ($05,X) ; und in Farb-RAM schreiben PLA ; High-Byte des Schreibzeigers auf Video-RAM zurückholen STA *$06 ; und wiederherstellen INC *$05 ; Schreibzeiger weiterbewegen BNE DG03 ; Sprung falls kein Übertrag INC *$06 ; sonst Übertrag berücksichtigen DG03: INY ; Leseindex erhöhen BNE DG01 ; Rücksprung falls kein Übertrag INC *$04 ; Übertrag berücksichtigen BNE DG01 ; Unbedingter Rücksprung ; Steuercode verarbeiten DG04: INY ; Lesezeiger erhöhen BNE DG05 ; Sprung falls kein Übertrag INC *$04 ; sonst Übertrag berücksichtigen DG05: LDA ($03),Y ; Nächstes Byte aus Meldung lesen CMP #$10 ; Nächstes Byte kleiner als 16? BCS DG06 ; Sprung falls nicht STA *$07 ; Byte kleiner 16 als neue Zeichenfarbe merken BCC DG03 ; unbedingter Rücksprung DG06: CMP #$FF ; Nächstes Byte gleich $FF? BEQ DG08 ; dann Ende der Meldung CMP #$FE ; Nächstes Byte gleich $FE? BNE DG07 ; Sprung falls nicht LDA #$FF ; Karomuster BNE DG02 ; ausgeben und zum nächsten Byte DG07: AND #$7F ; Unterste 7 Bit isolieren CLC ; und als Offset zum derzeitigen Schreibzeiger ADC *$05 ; addieren STA *$05 ; und zurückschreiben LDA *$06 ; Eventuellen Additionsübertrag ADC #$00 ; berücksichtigen STA *$06 BNE DG03 ; Unbedingter Sprung, Lesezeiger erhöhen und nächstes Byte DG08: RTS T400: DB $0A ; Rahmenfarbe DB $00 ; Hintergrundfarbe DB $FF,$FD ; Schreibzeiger +115 DB $FF,$FD ; Schreibzeiger +115 DB $FF,$D0 ; Schreibzeiger +80 DB $FF,$01 ; Zeichenfarbe weiß DB $0E,$0F,$17,$20 ; "NOW " DB $0C,$0F,$01,$04,$09,$0E,$07,$20 ; "LOADING " DB $03,$01,$15,$0C,$04,$12,$0F,$0E ; "CAULDRON" DB $FF,$BB ; Schreibzeiger +59 DB $10,$12,$0F,$04,$15,$03,$05,$04,$20 ; "PRODUCED " DB $15,$0E,$04,$05,$12,$20 ; "UNDER " DB $0C,$09,$03,$05,$0E,$13,$05 ; "LICENSE" DB $FF,$90 ; Schreibzeiger +16 DB $06,$12,$0F,$0D ; "FROM" DB $FF,$82 ; Schreibzeiger +2 DB $10,$01,$0C,$01,$03,$05,$20 ; "PALACE " DB $13,$0F,$06,$14,$17,$01,$12,$05,$20 ; "SOFTWARE " DB $0C,$14,$04,$2E ; "LTD." DB $FF,$B6 ; Schreibzeiger +54 DB $02,$19,$20 ; "BY " DB $14,$05,$0C,$05,$03,$0F,$0D,$13,$0F,$06,$14 ; "TELECOMSOFT" DB $FF,$82 ; Schreibzeiger +2 DB $13,$09,$0C,$16,$05,$12,$02,$09,$12,$04 ; "SILVERBIRD" DB $FF,$FD ; Schreibzeiger +115 DB $FF,$FD ; Schreibzeiger +115 DB $FF,$FD ; Schreibzeiger +115 DB $FF,$C8 ; Schreibzeiger +72 DB $FF,$FF ; Ende der Meldung T401: DB $0E,$0F,$17,$20 ; "NOW " DB $0C,$0F,$01,$04,$09,$0E,$07,$2C,$20 ; "LOADING, " DB $10,$0C,$05,$01,$13,$05,$20 ; "PLEASE " DB $17,$01,$09,$14,$2E,$20 ; "WAIT. " DB $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20 ; " " DB $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20 ; " " DB $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20 ; " " DB $FF,$FF ; Ende der Meldung
Das folgende Programmsegment ist der letzte Block, der mit dem dritten Schnelllader von Band gelesen wird und einen Teil der Laderoutine überschreibt. Es modifiziert zunächst noch geringfügig den zweiten Schnelllader, indem es die bisherigen Ladeanzeige ("CYBERLOAD NOW LOADING ...") durch einen in allen Farben flackernden Bildschirm ersetzt. Anschließend übergibt die Routine die Kontrolle wieder an den zweiten Schnelllader, damit dieser noch den Startcode des Programms von Band liest.
PRG $E297 ; DF17
LDA #>[P_EA-$01] ; Sprung nach P_EA durch RTS
an CC14 vorbereiten
PHA
LDA #<[P_EA-$01]
PHA
; Statt Animation an BC02 Bildschirmrahmenfarbe hochzählen
LDA #$EE ; Opcode für "INC abs"
STA BC02+$00
LDA #<$D020 ; Adresse des VIC-Register für Rahmenfarbe, Low-Byte
STA BC02+$01
LDA #>$D020 ; Adresse des VIC-Register für Rahmenfarbe, High-Byte
STA BC02+$02
LDA #$60 ; Opcode für "RTS"
STA BC02+$03
LDA $D015 ; Sprite 7 abschalten
AND #$7F
STA $D015
JMP CC06 ; Letzten Programmteil laden
Start des Hauptprogramms[Bearbeiten | Quelltext bearbeiten]
Die Startroutine löscht noch die Interruptmasken des VIC und der CIAs und sowie eventuell ausstehende Interruptanforderungen. Anschließend blendet sie alle ROMs wieder in den Adressraum ein und stellt damit die Standardkonfiguration wieder her, schaltet noch den Bildschirm wieder ein und springt schließlich zur Startadresse des geladenen Programms.
PRG $0400 P_EA: SEI ; Interrupts deaktivieren LDA #$FF ; Alle Interruptanforderungen löschen STA $D019 LDA #$00 ; Interruptmaske löschen STA $D01A LDA #$00 STA $DC0E ; CIA 1 Timer A stoppen STA $DD0E ; CIA 2 Timer A stoppen LDA #$7F STA $DC0D ; Keine Interrupts von CIA 1 STA $DD0D ; Keine Interrupts von CIA 2 LDA $DC0D ; Interruptanforderungen von CIA 1 löschen LDA $DD0D ; Interruptanforderungen von CIA 2 löschen LDA #$37 ; I/O und alle ROMs einblenden STA *$01 LDA #$1B ; Bildschirm einschalten STA $D011 JMP $8009 ; Spiel starten