Extended VT52-Emulator Teil 1

Bild 1. Auch das Anzeigen und Übertragen (DFÜ) von Bildern in DOODLE-Format ist möglich.

Bekanntlich können Computer und Programme gar nicht schnell genug sein, um eingefleischte Hacker zu befriedigen. Dies gilt nicht zuletzt auch für die Bildschirmausgabe, die Teil des Betriebssystems und darum nicht so einfach durch eigene Routinen zu ersetzen ist. Wie man die Textausgabe um bis zu Faktor sechs beschleunigt und dergleichen mehr, zeigt ein in Assembler geschriebenes Programm, dessen erster Teil in dieser Ausgabe der ST Computer veröffentlicht wird.

Mag sein, daß es etwas ungewöhnlich ist, den Anfang mit dem Ende zu verbinden (Sokrates’ Lieblingsbeschäftigung einmal ausgekreist), aber da dieser Artikel ob seiner Länge nur häppchenweise publiziert werden kann, sollten Sie wenigstens jetzt schon erfahren, was Sie darin erwartet und was Sie damit anfangen können. Es handelt sich bei xVT52.PRG um einen Patch des für “normale” Textausgabe (also solche ohne Attribute) zuständigen VT52-Emulators. Das heißt, es ist eigentlich kein Patch mehr, sondern viel mehr ein komplett neuer Emulator, denn wenn er erst einmal installiert ist, kann sich der Origina1-Emulator auf Rente begeben, weil keine einzige Funktion des TOS ihn mehr eines Bytes würdigt. Dafür ist das komplett in Profimat-Assembler geschriebene Programm wesentlich leistungsfähiger im Funktionsumfang und auch bis zu sechsmal schneller als sein in C geschriebener Kollege. C erzeugt sicherlich kompakten und recht schnellen Code, wenn man der Perversion übersichtlicher Programmierung mächtig ist, aber schon das Runtime-Gerüst eines C-Programmes ist bei weitem größer als der komplette xVT52-Emulator, und was die Geschwindigkeit angeht, spricht ein Zeitvergleich zwischen altem und neuem Emulator für sich.

Features

Sind Sie Mitglied der immer größer werdenden DFÜ-Fan-Gemeinde? Dann haben Sie es bestimmt schonmal vermißt, daß der VT52 zwar ASCII-Texte übermitteln kann, nicht aber Grafiken. Häufig kommt es auch vor, daß man eben mal einen Text unterstreichen möchte, um ihn hervorzuheben. Vor derlei Sonderwünsche hat das TOS das VDI gesetzt; und das ist nun nicht eben ein Ausbund an Bedienungsfreundlichkeit. In aufwendigeren Tabellen kann es auch nötig werden, mehr als nur eine Cursorposition zwischenzuspeichern (mit ESC j); hatte man doch keine legale Möglichkeit im VT52, an die derzeit adressierte Spalte/Zeile heranzukommen... Ein häufig geäußerter Benutzerwunsch ist auch das pixelweise Verschieben des Bildschirmbereiches (Finescrolling genannt), das sich nun sehr einfach erreichen läßt - sofern Sie zu den achtzig Prozent der ST-Besitzer zählen, die einen SM 124 ihr monochromes eigen nennen. Dagegen spielt es keine Rolle, ob nun ein 260er oder ein Mega ST4 Ihren Schreibtisch ziert: xVT52 läuft auf allen Konfigurationen und TOS-Versionen.

Im übrigen mag Ihnen diese Serie dazu dienen, das eine oder andere dazuzulernen, derweil zumindest die wichtigsten Programmteile nochmal ausführlich auseinandergenommen werden, um ihre Funktionsweise zu erläutern.

Bild 2: Umschalten von GEM-Fonts

Installation

Für den Anwender reduziert sich der Aufwand zum Installieren des Programmes auf einen Maus-Doppelklick, den man sich aber auch ersparen kann, wenn man xVT52.PRG in den AUTO-Ordner des Bootlaufwerks legt. Um den Emulator im Betriebssystem zu verankern, ist allerdings erheblicher Aufwand zu betreiben: nicht weniger als alle vom TOS benutzten TRAP-Vektoren sind umzubiegen, um alle Aufrufe abzufangen, die irgendwas mit Bildschirmausgabe zu tun haben. Und das sind derer reichlich viele...

Des’ Trap ich bieg’, des’ GEM ich sing’

Wie man so im allgemeinen bestimmte TOS-Funktionen “pätscht”, wurde in zahlreichen Publikationen bereits demonstriert: man legt den zum TOS-Bereich gehörigen Vektor (z.B. $84 für GEMDOS) auf eine eigene Routine, in der man die auf dem Stack befindliche Funktionsnummer dahingehend untersucht, ob sie mit der zu patchenden übereinstimmt. Ist dem so, ruft man die eigene, ansonsten die Originalroutine auf. Klingt einfach und plausibel? Ist es auch - sofern man Funktionen von GEMDOS, BIOS und XBIOS ändern möchte. Beim Versuch, AES/VDI-Funktionen auf diese Weise zu manipulieren, könnte die Mega-atarianische Selbstmordrate leicht in ungeahnte Höhen steigen: alle Marvins der Galaxis zusammen könnten nicht deprimierter sein als ein programmierender Erdling, der es einfach nicht begreifen kann, warum sein ST Dinge tut, die er eigentlich gar nicht tun dürfte - und so sieht’s aus: man biegt den AES/VDI-Trap-Vektor an Adresse $88 auf eine eigene Routine um (wie es oben geschildert wurde) und ruft probehalber die geänderte Funktion auf. Und siehe da: es funktioniert. Derart begeistert, probiert man’s gleich nochmal... Und siehe da: es funktioniert nicht. Aha. Sicherheitshalber Resetknöpfchen drücken und nochmaliges Installieren sind eins; der anschließende Test ergibt das erwartete Ergebnis. Es klappt. Wenn man Glück hat, klappt’s sogar noch ein paarmal, um dann aber widersinnigerweise wieder nicht zu klappen, obwohl auch die tausendste Analyse des Listings nicht den Hauch eines Fehlers zutage fördern konnte. Der Ausdruck: “Klappt nicht” ist übrigens dahingehend zu verstehen, daß nicht etwa Bombenteppiche den entgeistert auf den Bildschirm gerichteten Blick weich abzufangen versuchten - nein, das wäre auch zu trivial -, es ist nur einfach so, daß es ganz danach aussieht, als liefe ein total anderes Programm als das eingegebene. Verwirrt? Keine Panik - es gibt eine Lösung. Sie ist eigentlich verblüffend einfach, bloß muß man erst mal draufkommen: durch einen glücklichen Zufall kam heraus, daß der Vektor, der - logisch betrachtet -auf die eigene Routine hätte zeigen müssen, perfiderweis’ wieder ins ROM und somit auf die Originalroutinen zeigte!!! Wenn Sie mir jetzt einreden möchten, ich sei von Blind-und/oder Blödheit geschlagen, wenn ich noch nicht mal merken würde, daß nicht mehr meine, sondern die Originalroutinen ausgeführt werden, möchte ich Ihnen zwar nicht grundsätzlich widersprechen, aber zu bedenken geben, daß es sich bei den entsprechenden Routinen um solche handelte, die zum Original kompatibel sind und sich also nicht so besonders gut unterscheiden lassen... Ich hoffe, daß wenigstens den Programmierern von Digital Research der Sinn bekannt ist, warum der GEM-Vektor so ab und an immer mal wieder ins ROM geschubst wird. Daß hierzu auch noch der jedes Programm unglaublich übersichtlich und lesbar machende Line-F-Emulator Verwendung findet, legt die Vermutung nahe, unter den VDI-Programmierern sei ein Sadist gewesen...

Da es außer TRAP #2 keine weitere Schnittstelle zum GEM gibt, mußte der ominöse Pointerverbieger aus GEM selbst kommen; da der Vektor jedoch gar nicht mehr aufs Original zeigte und trotzdem wieder auf die ROM-Adresse umgesprungen wurde, konnte es nur noch eine Interrupt-Routine sein, die da ihr wüstes Unwesen treibt. So ist’s denn auch: der erste Slot des Vertical-Blank-Interrupts ist für GEM reserviert, und inmitten dieser IR-Routine residiert der Unhold. Weil ich mich ganz nett über den Typen geärgert hab’, verzichtete ich darauf herauszufinden, unter welchen Umständen er in Aktion tritt. Falls es jemand herausgefunden haben sollte (es ist ja durchaus auch eine sehr einfache Erklärung möglich), möge er sich doch bitte melden. Danke.

Die Lösung des Problems war jetzt eigentlich nur noch Formsache und kann in der Cursor-Interrupt-Routine CRS_IRR nachgelesen werden: der Vektor wird auf die eigene Routine gebogen; war er zwischenzeitlich ins ROM gewandert, wird der Cursor ausgeschaltet. Die Praxis hat gezeigt, daß dies so sinnvoll ist; richtig begründen kann ich’s aber nicht, weil ich, wie gesagt, keine Lust hatte, in den unergründlichen Tiefen des VDI herumzustöbern (wo Line-F-Emulator und ähnlich gräßliche Dinge zu Hause sind...). Bemerkenswert ist noch, daß die Vektormanipulation innerhalb der Interrupt-Routine herzerfrischend unanständig ist: wenn GEM auf die hinterlistige Idee kommt, den Vektor ins ROM zu legen, kann es dies nur während des Vertical-Blank-Interrupts in die Tat umsetzen, wobei es die allererste Routine innerhalb der VBL-Schlange benutzt. Die Routine, die den Vektor wieder ins RAM biegt, kommt direkt dahinter zur Ausführung, ohne daß GEM etwas davon wüßte oder es gar verhindern könnte. Ganz schön fies, gell? Und da sich das alles während des VBL-Interruptes abspielt und also zwischenzeitlich kein Programm aktiv sein kann, wird auf diese Tour sichergestellt, daß GEM-Aufrufe im Patch und nicht im ROM landen. Schubidu. Sollten Sie irgendwann einmal in die Verlegenheit kommen, Veränderungen im GEM vorzunehmen (z.B. weil Ihnen die File-Select-Box nicht mehr gefällt oder Sie mehr als vier Fenster haben möchten...), würde ich dringend empfehlen, ebenfalls diese “Technik” zu verwenden; sie hat sich bisher als ebenso problemlos wie zuverlässig erwiesen.

Nachdem der “Brocken” nun aus dem Weg geräumt ist, läßt sich der Rest der Installation wesentlich lockerer nachvollziehen. Als da wäre: Reservieren des benötigten Speicherplatzes, Einfügen der Cursor-Interrupt-Routine in die VBL-Slots sowie Umbiegen der Vektoren für Aufrufe von GEMDOS, AES/VDI, BIOS und XBIOS.

Bild 3: Verschiedene Schriftattribute

Traphandler

Da - wie bereits erwähnt - alle vier Teile des Betriebssystems über Funktionen zur Textausgabe via VT52 verfügen, mußten dementsprechend auch neue Traphandler geschrieben werden (im Listing tragen sie die ungewöhnlichen Namen GEMDOS, AES_VDI, BIOS und XBIOS). Ihre Lebensaufgabe besteht darin, TOS-Aufrufe dahingehend zu analysieren, ob die angeforderte Funktion zu patchen ist. Wenn ja, werden die Daten-und Adreßregister des Prozessors gerettet, die neugeschriebene Routine angestoßen und die Register wieder restauriert. In allen anderen Fällen wird die Programmkontrolle an den Origina1-Traphandler weitergegeben, dessen Adresse beim Installieren der eigenen Routinen ja gemerkt wurde. Obwohl durch das Patchen pro Aufruf etwas mehr Code abgearbeitet werden muß, ist keine Verlangsamung des Systems zu befürchten. In der folgenden Übersicht sind alle Funktionen aufgelistet, die von xVT52 abgefangen werden, wobei ein Sternchen darauf hinweist, daß diese Funktionen neu hinzugekommen sind.

GEMDOS:

VDI:

BIOS:

XBIOS:

Wie man sieht, wird die Wichtigkeit der textuellen Ausgabe von TOS mit einem ziemlichen Funktionsaufgebot gebührend gewürdigt und so nimmt es nicht Wunder, daß xVT52 mit seinen über 1800 Zeilen nicht eben zu den Programm-Winzlingen gehört... Die neue Betriebssystemfunktion XBIOS 100 dient dazu, die Ausgabe in einen definierten Zustand zu bringen. Die im Listing mit INIT_CON-OUT bezeichnete Routine, die auch mit ESC i aktiviert werden kann, wird während des Installierens aufgerufen und beginnt ihren Dienst mit dem Retten der Zeiger auf die beiden Systemfonts der Kategorie 8x8 und 8x16, wobei zugleich letzterer zum aktuellen Zeichensatz erhoben wird. Der Grund liegt zum einen darin, daß die Fontdaten für die Ausgabe zwingend benötigt werden, andererseits bietet xVT52 auch die Möglichkeit, mit einer einfachen Escape-Sequenz zwischen den beiden Fonts hin- und herzuschalten! Desweiteren werden die Ausgabe-Attribute wie Wrapping, inverse Darstellung, Blinkfrequenz des Cursors etc. auf ihre Defaultwerte gebracht. Vor allem aber wird der “Ausgabevektor” initialisiert. Was es mit diesem auf sich hat, erfahren Sie im folgenden Kapitel.

Vektor-Wirrwarr

Vektoren - neudeutsch Pointer - sind nichts anderes als Speicherbereiche (beim MC68000 sind sie 4 Bytes lang), die eine Adresse enthalten und auf “irgendwas” zeigen. So z.B. auf Variablen, Funktionen oder auch auf weitere Pointer. Die Tatsache, daß man Pointer nicht so einfach verfolgen und manipulieren kann wie normale Variablen, führt gewöhnlich dazu, daß dieses Kapitel beim Erlernen einer Programmiersprache meistens auf dem letzten Platz der Beliebtheitsskala rangiert. Dennoch sind sie absolut unverzichtbar, denn egal, ob man sie in “getarnter” Form (wie in Pascal durch Voransetzen des Schlüsselwortes VAR vor ein Variable oder Prozedur/Funktion innerhalb einer Übergabe liste) oder explizit (wie in C durch den Stern-Operator, z.B. char * string) benutzt: ohne sie geht nichts. Auch nicht bei der Textausgabe. Unter den TOS-Variablen findet man an der Adresse $4A8 einen dieser Zeiger, dessen Zweck mit “Interner Zeiger für Bildschirmausgaberoutinen” angegeben ist. Ich nehme an, daß diese Art kryptischer Erklärungen nicht ganz unschuldig am weitverbreiteten Pointer-Desinteresse ist, weshalb ich ihn etwas genauer beschreiben möchte. Wenn Sie irgendeinen Text auf den Bildschirm ausgeben möchten, rufen Sie hierzu eine TOS-Funktion auf. In dieser wird geprüft, ob der derzeit verwendete Zeichensatz das gewünschte Zeichen enthält. Wenn ja, wird es dargestellt, die Cursorposition erhöht und evtl, der Bildschirminhalt nach oben gescrollt. Damit kann man schon ganz gut leben, bloß muß es auch Möglichkeiten geben, die Ausgabe zu steuern (Bildschirm ganz oder teilweise löschen etc.). Für diese Aufgaben stellt der im TOS vorhandene VT52 Emulator die sog. ESC-Sequenzen zur Verfügung, d.h. durch das ASCII-Zeichen 27 eingeleitete Strings. Für den Emulator bedeutet dies, daß er dieses sowie die nachfolgenden Zeichen nicht einfach ausgeben, sondern interpretieren soll. Dies wird so gelöst, daß die Ausgaberoutine des Betriebssystems nicht direkt angesprungen wird, sondern eben über einen Vektor, der im Normalfall auf diese zeigt. Beim xVT52 Emulator heißt dieser Vektor VEC_BASE und enthält die Adresse der Standard-Ausgaberoutine STD_VEC. Sobald das ESC-Zeichen erkannt wird, wird der Vektor auf eine andere Routine (ESC_SEQ) umgebogen, die dann die gewünschte Funktion in Abhängigkeit des nächsten Zeichens aktiviert. Ohne den “Trick” mit dem Vektor wäre dies -wenn überhaupt-nicht so elegant zu lösen!

In der nächsten Folge werde ich damit beginnen, die neu hinzugekommenen ESC-Sequenzen unter die Lupe zu nehmen und Ihnen erklären, warum die Textausgabe nun viel schneller geworden ist. Die Zwischenzeit können Sie ja mit dem Eintippen des ersten Teils des Listings überbrücken...

MS

; *************************************
; * >EXTENDED VT52-TERMINAL EMULATOR< *
; * Entwickelt mit PROFIMAT-Assembler *
; *     M.Schumacher, (p) 12/87       *
; *************************************

LOGBASE equ $44E        ; Zeiger auf logischen Bildschirm
VID_R0 equ $FFFF8240    ; Video-Farbregister 0
REGISTER equ d3-d7/a0/a3-a6 ;benötigte Register für Verschieberoutinen 
RESETCO equ INIT_CONOUT ; Termina1-Initialisierungs-Routine
NVBLS equ $454          ; Anzahl VBL-Routinen
VBLQUEUE equ $456       ; Zeiger auf Zeiger auf VBL-Routinen

text

INSTALL: move.l 4(a7),a4    ; ^Basepage
    move.l  12(a4),d7   ; Länge TXT
    add.l   20(a4),d7   ; +DTA
    add.l   28(a4),d7   ; +BSS
    add.l   #256,d7     ; +256
    move.l  d7,-(a7)    ; = Anzahl zu reservierender Bytes
    clr.l   -(a7)       ; SUPER ON
    move.w  #$20,-(a7)
    trap    #1
    move.l  d0,2(a7)    ; alten SSP merken

    bsr INIT_CONOUT     ; Emulator initialisieren
    move.l  VBLQUEUE,a0 ; AVBL-Zeiger
    move.w  NVBLS,d0    ; Anzahl der Routinen
    subq.w  #1,d0       ; in dbra-Zähler wandeln
\test_slot: 
    tst.l   (a0)        ; Slot  frei?
    beq.s   \freeslot   ; ja
    addq.l  #4,a0       ; sonst halt
    dbra    d0, \test_slot ; den nächsten Slot testen
    bra.s   \noslot     ; kein  freier Slot mehr?! (normal unmöglich)
\freeslot:
    lea CRS_IRR,a1      ; Cursor-Interrupt-Routine
    move.l  a1,(a0)     ; einbinden
\noslot:
    lea TRAPS,a4        ; ^Speicher für Origina1-Trap-Handler
    move.l  $84, (a4)+  ; alten GEMDOS-Vektor merken
    lea GEMDOS,a2       ; und neuen
    move.l  a2,$84      ; installieren
    move.l  $88, (a4)+  ; dasselbe mit GEM-Vektor
    lea AES_VDI,a2
    move.l  a2,$88
    move.l  $B4,(a4)+   ; BIOS-Vektor
    lea BIOS,a2
    move.l  a2,$B4
    move.l  $B8,(a4)    ; XBIOS-Vektor
    lea XBIOS,a2
    move.l  a2,$B8
    trap    #1          ; SUPER OFF
    addq.l  #6,a7       ; Stack aufräumen
    move.w  #$31,-(a7)  ; KEEP PROCESS (hi zombie!)
    trap    #1          ; auf Wiedersehen im GEMDOS

;*****************************************
;* G E M D O S / A E S+V D I / B I O S / *
;* X B I O S - TRAPHANDLER               *
;*****************************************

SAVE_REGS:  ; Register retten
    move.l  (a7)+,d0    ; Rücksprungadresse retten
    lea SAVE_TOP,a1     ; A1=Registerspeicher, Obergrenze 
    move.w  (a7)+,-(a1) ; save (SR)
    move.l  (a7)+,-(a1) ; save(PC)
    movem.l d3-d7/a3-a7,-(a1) ;save(Register) 
    lea 2(a0),a7        ; SSP auf 1. Parameter setzen
    move.l  d0,-(a7)    ; Rücksprungadresse zurück
    rts

REST_REGS:              ; Register zurückholen
    lea SAVE_BOT,a1     ; A1=Registerspeicher, Untergrenze
    movem.l (a1)+,d3-d7/a3-a7 /Register zurück 
    move.l  (a1)+,-(a7) ; PC zurück
    move.w  (a1)+,-(a7) ; SR zurück
    rte                 ; zurück ins aufrufende Programm

SAVE_BOT:   ; Untergrenze Zwischenspeicher
    ds.w    23,0        ; Platz für Register, PC und SR
SAVE_TOP:   ; Obergrenze Zwischenspeicher

GEMDOS:
    movea.l a7,a0   ; AO-SSP;
    btst    #5,(a0) ; if (Aufruf aus S-Mode)
    beq.s   \from_user
    addq.l #6,a0    ; then Offset addieren
    bra.s \test_pline ; (PC.L+SR.W)
\from_user:
    move.l USP,a0   ; else User Stack benutzen; 
\test_pline:
    cmpi.w  #9, (a0)    ; if (Funktion == PRINT LINE)
    beq.s   \pline      ; then eigene Funktion benutzen
    cmpi.w  #64, (a0)   ; else if (Funktion == WRITE)
    beq.s   \write      ; then auf Ausgabe-Kanal prüfen
    cmpi.w  #2,(a0)     ; else if (Funktion == CCONOUT)
    beq.s   \cconout    ; then eigene Funktion benutzen
    cmpi.w  #6, (a0)    ; else if (Funktion != CRAWIO)
    bne.s   \orig       ; then Originalroutine rufen
    cmpi.b  #-1,3 (a0)  ; else if (Zeichen — 255)
    beq.s   \orig       ; then Original aufrufen
\cconout:
    bsr SAVE_REGS       ; Register retten
    bsr CON_OUT         ; Zeichen ausgeben
    bra REST_REGS       ; Register zurück, fertig
\pline:
    bsr SAVE_REGS       ; Register retten
    bsr WRITE           ; String ausgeben
    bra REST_REGS       ; Register zurück, fertig
\orig:
    move.l  TRAPS,a0    ; else Originalroutine
    jmp (a0)            ; benutzen
\write:
    cmpi.w  #1,2(a0)    ; Ausgabe auf CON:?
    bne.s   \orig       ; nein, ham wa nix mit zu tun
    addq.w  #2,a0       ; sonst Anzahl auszugebender
    move.l  2(a0),d1    ; Zeichen holen
    beq.s   \retour     ; kein Zeichen ist definitiv zu wenig!
    bsr SAVE_REGS       ; ansonsten Register retten
    addq.w  #4,a7       ; (a7) - String-Pointer
    move.l  d1,-(a7)    ; Anzahl auf Stack retten
    move.l 4(a7),-(a7) ; String-Pointer auf Stack legen 
\lp:
    move.l (a7),a0      ; String-Pointer holen 
    addq.l #1,(a7)      ; auf nächstes Zeichen zeigen lassen 
    move.b (a0),d1      ; Zeichen holen 
    and.w #$FF,d1       ; nur LSB beachten 
    move.w  d1,-(a7)    ; Zeichen auf Stack legen
    bsr CON_OUT         ; und ausgeben
    addq.w  #2,a7       ; Stack korrigieren
    subq.l  #1,4(a7)    ; Anzahl Zeichen dekrementieren
    bne.s   \lp         ; und nächstes Zeichen ausgeben
    addq.l  #8,a7       ; Zähler und Pointer löschen
    bra REST_REGS       ; Register restaurieren und zurückspringen

\retour: 
    rte

AES_VDI:
    cmpi.w  #$73,d0     ; VDI-Aufruf?
    bne.s   \orig       ; nein, AES
    move.l  a0,-(a7)    ; a0 retten
    move.l  d1,a0       ; ^ParameterBlock
    move.l  (a0),a0     ; ^ContrlBlock
    cmpi.w  #5,(a0)     ; VDI-ESC-Sequenz? (CONTRL[0))
    bne.s   \op_close   ; nein
    lea 10(a0),a0       ; ^Unterfunktionsnummer (CONTRL[5)
    cmpi.w  #16,(a0)    ; <16?
    bmi.s   \ok         ; ja, eigene Routine ausführen
    cmpi.w  #101,(a0)   ; VDI ESC 101?
    beq.s   \ok         ; ja
    cmpi.w  #102, (a0)  ; Font installieren?
    bne.s \fail         ; nein, dann Originalroutine benutzen 
\ok:                    ; *** eigene Routinen ausführen ***
    lea VDI_TOP,a0      ; ^Zwischenspeicher
    move.l  (a7)+,-(a0) ; alten Inhalt von aO retten
    move.w  (a7)+,-(a0) ; SR
    move.l  (a7)+,-(a0) ; PC
    movem.l d0-d7/a1-a7,-(a0) /alle Register retten 
    bsr VDI_ENTRY       ; Routine ausführen
    lea VDI_BOT,a0      ; ^Zwischenspeicher
    movem.l (a0)+,d0-d7/a1-a7 ;Register wieder zurück 
    move.l  (a0)+,-(a7) ; pc,
    move.w  (a0)+,-(a7) ; SR und
    move.l  (a0)+,a0    ; a0 restaurieren,
    rte         ; Exception beenden
\fail:
    move.l  (a7)+,a0    ; a0 restaurieren
\orig:
    move.l TRAPS+4,-(a7)    ; Originaladresse AES/VDI
    rts                 ; benutzen
\op_close:
    cmpi.w #100,(a0) ; openVirtualScreenWorkstation? 
    bne.s   \close  ; nein
    lea TCB,a0      ; ^TerminalControlBlock
    clr.l   (a0)    ; Zeile und Spalte auf 0
    bra.s   \fail   ; zusätzlich Originalroutine ausführen
\close:
    cmpi.w #101,(a0); CloseVirtualScreenWorkstation? 
    bne.s   \clear  ; nein
    lea CCB,a0      ; ^CursorControlBlock
    bclr    #3,(a0) ; Cursor abschalten
    bra.s   \fail   ; und Originalroutine ausführen
\clear:
    cmpi.w  #3,(a0) ; ClearWorkstation?
    bne.s   \fail   ; nein
    movem.i d0-d7/a1-a5,-(a7)   ; Register retten
    lea TCB, a0     ; ^TerminalControlBlock
    bsr CLS         ; Bildschirm löschen
    movem.i (a7)+,d0-d7/a1-a5   ; Register zurück
    bra.s   \fail   ; zusätzlich Origina1routine ausführen

VDI_BOT:
    ds.w    35,0    ; Platz für d0-a7, SR & PC
VDI_TOP:

BIOS:
    movea.l a7,a0   ; A0=SSP;
    btst    #5,(a0) ; if (Aufruf aus S-Mode)
    beq.s   \from_user
    addq.l  #6,a0   ; then Offset addieren
    bra.s   \test_wr_asc    ; (PC.L+SR.W)

\from_user:
    move.l USP,a0   ; else User Stack benutzen;
\test_wr_asc:
    cmpi.w #12,(a0) ; if (Funktion != WRITE_ASC)
    bne.s   \test_bconout   ; then auf BCONOUT testen
    bsr SAVE_REGS   ; else { Register retten;
    bsr WRITE_ASC   ; String ausgeben;
    bra REST_REGS   ; Register zurück; beenden }
\test_bconout: cmpi.w   #3,(a0) ; if (Funktion == BCONOUT)
    beq.s   \test_dev   ; then auf Ausgabegerät testen
\orig_bios:
    move.l TRAPS+8, - (a7)  ; else Originalroutine benutzen
    rts 
\test_dev:
    addq.w  #2,a0   ; Zeiger auf Gerätenummer setzen
    cmpi.w #2, (a0) ; if (Ausgabegerät != Console)
    bne.s \test_dev_vid ; then auf ASCII-Ausgabe testen
    bsr SAVE_REGS   ; else { Register retten;
    bsr CON_OUT ; Zeichen ausgeben;
    bra REST_REGS   ; Register  zurück; beenden )
\test_dev_vid: 
    cmpi.w  #5, (a0)    ; if (!ASCII_Ausgabe)
    bne.s   \orig_bios  ; then Originalroutine benutzen
    bsr SAVE_REGS       ; else { Register retten;
    bsr ASC_OUT         ; Zeichen ausgeben;
    bra REST_REGS       ; Register zurück; beenden )

XBIOS:
    movea.l a7,a0   ; A0=SSP;
    btst    #5,(a0) ; if (Aufruf aus S-Mode)
    beq.s   \from_user
    addq.l #6,a0    ; then Offset addieren (PC.L+SR.W)
    bra.s   \test_cursconf
\from_user:
    move.l  USP,a0  ; else User Stack benutzen;
\test_cursconf: 
    cmpi.w  #21,(a0)    ; if (Funktion == CURSCONF)
    beq.s   \cursconf   ; then eigene Funktion benutzen
    cmpi.w  #100, (a0)  ; else if (Funktion INIT_SCREEN) 
    beq.s   \init       ; then Bildschirmausgabe initialisieren 
    move.l TRAPS+12,-(a7) ; else Originalroutine benutzen
    rts 
\cursconf:
    bsr SAVE_REGS       ; Register retten
    bsr CURSCONF        ; eigene Routine ansto_en
    bra REST_REGS       ; Register zurück, beenden
\init:
    bsr SAVE_REGS       ; Register retten
    bsr.s   INIT_CONOUT ; Ausgabe initialisieren
    bra REST_REGS       ; Register zurück, beenden

; ***********************************************
; * NEUE ROUTINEN FÜR VT52 UND VDI-ESCAPES      * 
; ***********************************************

INIT CONOUT:            ; ESC 'i', Terminal initialisieren
    dc.w    $A000       ; Line A-Pointer holen
    lea TCB,a2          ; TerminalControlBlock initialisieren 
    move.l 4(a1),a4     ; ^8*8-Font
    move.l  $4C (a4),28(a2) ; ^Fontdaten speichern
    move.l  8(a1),a4    ; ^8*16-Font
    move.l  $4C(a4),a4  ; ^Fontdaten
    move.l  a4,24 (a2)  ; speichern
    move.l  a4,32(a2)   ; und 8x16 zum aktuellen Font machen
    move.l  #$4f0018,4(a2)  ; max.Spalte=79, max.Zeile=24
    move.b  #2,8(a2)    ; Wrapping ein-, inverse Darstellung ausschalten 
    clr.w   10 (a2)     ; keine Cursor-Position gespeichert (ESC 'j') 
    move.l #$100500,36(a2) ; Zeichenhöhe 16 Pixels, 16*80 Bytes/Textzeile
    lea LINE_A,a2       ; Line-A Adresse
    move.l  a0,(a2)     ; merken
    lea VEC_BASE,a2     ; Ausgabevektor initialisieren
    lea STD VEC,a3      ; ^Standardausgabe
    move.l a3,(a2)      ; als Default übernehmen
    lea CCB,a4          ; CursorControlBlock initialisieren 
    move.w #$200, (a4)  ; Cursor ausschalten, Blinkmodus wählen 
    move.l #$140014,2 (a4)  ; Blinkrate und -Zähler auf 20 stellen
    move.l LOGBASE,6(a4)    ; abs. Cursorposition auf Bildschirm-Anfang
    lea BEL_ADR,a0      ; ^Speicher für Adresse Gong-Routine
    move.l  #$FC201C,d0 ; TOS-Adresse (Version vom 06.02.1986) 
    cmpi.w  #$1986,$FC001A  ; "altes" TOS?
    beq.s   \old_tos    ; ja
    move.l #$FC2270,d0  ; sonst Blitter-TOS-Adresse nehmen
\old_tos:
    move.l d0, (a0)     ; Vektor installieren
    lea TCB,a0          ; ^TerminalControlBlock
    bsr CLS             ; Bildschirm löschen+Home
    bsr DEF_TABS        ; Tabulatoren auf default setzen
    bra UPDATE_END      ; Cursor freigeben und zurück

CRS_IRR:                ; Cursor-Interruptroutine
    move.l  $88,d1      ; derzeit aktive AES/VDI-Adresse
    lea AES_VDI,a0      ; eigenen Trap-Handler neu installieren
    move.l  a0,$88      ; AES/VDI überlisten (grins)
    cmp.l   a0,d1       ; hat sich Adresse geändert?
    bne UPDATE_CRS      ; dann Cursor abschalten
    lea CCB,a4          ; ^CursorControlBlock
    btst    #3,(a4)     ; Cursor eingeschaltet?
    beq.s   \irr_end    ; nein, fertig
    btst    #0,(a4)     ; wird gerade Text ausgegeben?
    beq.s   \irr_end    ; ja, dann nicht stören
    btst    #1,(a4)     ; Blinken eingeschaltet?
    bne.s   \blink      ; ja
    btst    #2, (a4)    ; Cursorposition schon invertiert?
    bne.s   \irr_end    ; ja, nicht mehr invertieren
\inv:
    bsr CUR_INV         ; Cursorposition invertieren
\irr_end:
    rts                 ; zurück zum IR-Slot-Handler
\blink:
    subq.w  #1,4(a4)    ; Blinkzähler —
    bne.s   \irr_end    ; noch nicht 0, fertig
    move.w  2(a4),4(a4) ; sonst Zähler neu laden
    bra.s   \inv        ; invertieren und zurück

CURSCONF:               ; XBIOS (21)
    bsr UPDATE_CRS      ; Cursor abschalten
    move.w  4(a7),d1    ; Funktionsnummer holen
    bne.s   \f1         ; nicht 0
    bclr    #3,(a4)     ; Cursor ausschalten
    bra.s   \zurück     ; fertig
\f1:
    cmpi.w  #1,d1       ; Fkt.-Nr. 1?
    bne.s   \f2         ; nein
    bset    #3,(a4)     ; Cursor einschalten
    bra.s   \zurück
\f2:
    cmpi.w  #2,d1       ; Fkt.-Nr. 2?
    bne.s   \f3         ; nein
    bset    #1,(a4)     ; Cursor in Blinkmodus versetzen
    bra.s   \zurück
\f3:
    cmpi.w  #3,d1       ; Fkt.-Nr. 3?
    bne.s   \f4         ; nein
    bclr    #1,(a4)     ; Blinkmodus ausschalten
    bra.s   \zurück
\f4:
    cmpi.w  #4,d1       ; Fkt.-Nr. 4?
    bne.s   \f5         ; nein
    move.w  6(a7),2(a4) ; Blinkrate setzen
    move.w  6(a7),4(a4) ; Zähler initialisieren
    bra.s   \zurück
\f5:
    cmpi.w  #5,01       ; Fkt.-Nr. 5?
    bne.s   \zurück     ; nein, ignorieren
    clr.l   d0          ; d0.L wegen Wortoperation löschen
    move.w  2(a4),d0    ; Blinkrate holen
\zurück:
    move.l  d0,-(a7)    ; Rückgaberegister retten
    bsr UPDATE_END      ; Cursor wieder freigeben
    move.l  (a7)+,d0    ; Register zurück
    rts

WRITE_ASC:  ; String ausgeben (ohne Steuerzeichen) 
    move.l 4(a7),a6     ; ^Ausgabestring
\lp:
    move.b  (a6)+,d0    ; auszugebendes Byte holen
    beq.s   \ende       ; falls 0: fertig
    bsr.s   ASC_ENTRY   ; ausgeben
    bra.s   \lp         ; bis String-Ende
\ende: 
    rts

ASC_OUT:                ; DIREKTE ZEICHENAUSGABE(OxOO-OxFF)
    move.w  4(a7),d0    ; Zeichen holen
ASC_ENTRY: 
    andi.w  #$FF,d0     ; nur Byte beachten
    bra PUT             ; und ausgeben

WRITE:  ; String ausgeben (mit Steuerzeichen) 
    move.l 4 (a7),a6    ;   ^Ausgabestring
\lp:
    move.b  (a6)+,d1    ; auszugebendes Byte holen
    bne.s   \aus        ; ausgeben, falls<>O
    lea TCB+9,a0        ; ^Grafik-Flag
    tst.b   (a0)        ; Grafik eingeschaltet?
    beq.s   \ende       ; nein, fertig
\aus:
    move.b  d1,d0       ; Zeichen ausgeben
    bsr.s   CON_ENTRY
    bra.s   \lp         ; bis String-Ende
\ende: 
    rts

CON_OUT:                ; AUSGABE MIT STEUERZEICHEN
    move.w 4(a7),d0     ; Zeichen holen
CON_ENTRY: 
    andi.w  #$FF,d0 ; LSB isolieren
    lea VEC_BASE,a1 ; ^Vektor
    move.l  (a1),a0 ; Vektor holen
    jmp (a0)    ; und anspringen

STD_VEC:
    cmpi.w  #" ",d0     ; Zeichen < Blank?
    bpi PUT             ; nein, ausgeben
    cmpi.w  #$1B,d0     ; ESC?
    bne.s   \control    ; nein
    lea ESC_SEQ,a0      ; AESC-Handler
    move.l a0,(a1)      ; als Vektor für nächstes Zeichen merken
\return: 
    rts                 ; fertig
\control: 
    subq.w  #7,d0       ; <7?
    bmi.s   \return     ; wenn ja, dann nicht beachten
    cmpi.w  #7,d0       ; >13?
    bpl.s   \return     ; ja: Ausgabe unterdrücken
    lea BEL,a1          ; ^dingelingeling
    add.w   d0,d0       ; Zeichenoffset in Word-Pointer umwandeln
    move.w CTRL(PC,d0.w),a2 ; entsprechenden Vektor 
    add.l   a1,a2       ; +Offset 1. Routine
    lea TCB,a0          ; ^TerminalControlBlock
    bsr.s   UPDATE_CRS  ; Cursor abschalten
    jsr (a2)            ; ausführen
    bra.s   UPDATE_END  ; Cursor wieder einschalten

CTRL:   dc.w 0          ; Glocke
    dc.w BS-BEL         ; Backspace
    dc.w TAB-BEL        ; Tabulator
    dc.w LF-BEL         ; Zeilenvorschub
    dc.w LF-BEL         ; Vertikaltabulator (wie Zeilenvorschub) 
    dc.w LF-BEL         ; Formularvorschub (wie Zeilenvorschub) 
    dc.w CR-BEL         ; Wagenrücklauf

UPDATE_CRS:             ; Cursor für Textausgabe ausschalten 
    lea CCB,a4          ; ^CursorControlBlock
    bclr    #0,(a4)     ; disable Cursor
    btst    #2,(a4)     ; Cursor sichtbar?
    beq.s   \return     ; nein
    bra CUR_INV         ; sonst Cursorposition invertieren
\return: 
    rts

UPDATE_END:             ; Cursor wieder freigeben
    lea TCB,a0          ; ^TerminalControlBlock
    move.l  LOGBASE,a1  ; ^Video-RAM
    move.w  38(a0),d0   ; Bytes/Textzeile
    mulu    2(a0),d0    ; * akt. Zeile
    adda.l  d0,a1       ; + Bildschirm-Anfang
    adda.w  (a0),a1     ; + akt. Spalte
    move.l  a1,6(a4)    ; = abs. Cursorposition
    bset    #0,(a4)     ; enable Cursor
    rts

ESC_SEQ:    ; Esc-Sequenz verarbeiten; Einsprung mit:
            ; d0.w = Steuerzeichen; a1 = AVEC_BASE 
    lea VEC_BASE, a1    ; Ausgabe-Vektor
    lea STD_VEC,a0      ; wieder auf die normale Ausgabe biegen 
    move.l  a0,(a1)     ; umschalten
    subi.w  #"A",d0     ; Offset abziehen
    bmi.s   \esc_fail   ; zu klein, ungültig
    cmpi.w  #"Z"-"A",d0 ; <="Z"?
    ble.s   \esc_big    ; ja, entsprechende Routine ausführen
    bra.s   \esc_sml    ; sonst auf Kleinbuchstaben testen
\esc_fail: 
    rts 
\esc_big:
    bsr.s   UPDATE_CRS  ; Cursor ausschalten
    lea CRS_UP,a1       ; Adresse der 1. Routine
    add.w   d0,d0       ; Zeichen als Wort-Offset
    lea E_BIG,a2        ; Adress-Tabelle
    move.w  0(a2,d0.w),a2   ; Adress-Offset holen
    adda.l  a1,a2       ; + 1. Routine
    lea TCB,a0          ; ^TerminalControlBlock
    jsr (a2)            ; Routine ausführen
    bsr.s   UPDATE_END  ; Cursor wieder ein
    rts                 ; fertig
\esc_sml:
    subi.w  #"a"-"A",d0 ; Offset für Kleinbuchstaben abziehen
    bmi.s   \esc_fail   ; <"a", ignorieren
    cmpi.w  #"z"-"a",d0 ; >"z"?
    bgt.s   \esc_fail   ; ja, ignorieren
    bsr UPDATE_CRS      ; Cursor ausschalten
    lea SET_COLOR,a1    ; Adresse der 1. Routine
    add.w   d0,d0       ; Zeichen als Wort-Offset
    lea E_SML,a2        ; AAdress-Tabelle
    move.w  0(a2,d0.w),a2   ; Adress-Offset holen
    adda.l  a1,a2       ; + 1. Routine
    lea TCB,a0          ; ^TerminalControlBlock
    jsr (a2)            ; Routine ausführen
    bsr UPDATE_END      ; Cursor einschalten
    rts                 ; fertig


Links

Copyright-Bestimmungen: siehe Über diese Seite
Classic Computer Magazines
[ Join Now | Ring Hub | Random | << Prev | Next >> ]