← ST-Computer 05 / 1988

Extended VT52-Emulator Teil 2

Grundlagen

In der letzen Folge ging es um die Installation des Emulators und die Problematik mit dem GEM-Vektor. Diesmal werde ich Ihnen u.a. einige der neuen bzw. erweiterten Escape-Sequenzen vorstellen.

Bevor ich mit der weiteren Besprechung des Programms fortfahre, möchte ich Ihre Aufmerksamkeit zunĂ€chst einmal auf die in Tabelle la und lb dargestellten globalen Variablen TCB und CCB lenken. Da fast alle Funktionen auf diese Strukturen zugreifen, ist es fĂŒr das VerstĂ€ndnis des Programms nicht ganz unwichtig, zu wissen, welche Informationen sie enthalten. Im allgemeinen wird auf beide Strukturen nur ĂŒber Offsets zur Basisadresse zugegriffen. Weil sich das so herzerfrischend wissenschaftlich und also chinesisch anhört, hier ein Beispiel: Zu Beginn einer Funktion wird die Adresse des Cursor-Control-Blocks CCB mit dem Befehl "lea CCB,a4” in ein Adress-Register des MC68000 geladen. Möchte man nun die absolute Cursorposition (irgendeine Adresse im Video-RAM) eruieren, so kann man mit 6(a4) darauf zugreifen. Im Prinzip ist das nichts anderes als die Assembler-Variante des von Pascal und C her bekannten Zugriffs auf Records/ Structs und Unions, also z.B. abs_curs(:)=CCB.address. Sie sehen, daß das VerstĂ€ndnis der Maschinensprache manchmal ganz nĂŒtzlich sein kann, wenn es darum geht, sich abstrakte und damit nicht immer leicht verstĂ€ndliche Sprachkonstrukte zu vergegenwĂ€rtigen, wobei dies insbesondere fĂŒr die leidigen Pointer gilt! ich nehme an, daß Sie dank der Kommentare nun keine Schwierigkeiten mehr haben werden, die beiden Kontrollvariablen zu verstehen. Aber noch eine weitere fundamentale Vorbetrachtung ist zum ProgrammverstĂ€ndnis notwendig, nĂ€mlich die Organisation der systemeigenen

Fonts

Was nĂŒtzt ein mit allen gĂ€ngigen Superlativen dekorierter Computer, wenn er sich nicht in der Lage sieht, sich im Klartext mit dem Benutzer zu unterhalten? Gewieft, wie die Hersteller nun ‘mal sind, haben sie Mittel und Wege ersonnen, die uns allen bekannten Schriftzeichen zur Darstellung zu bringen. Dabei gehen allerdings sowohl Mittel wie Wege ihre eigenen ebensolchen. Hersteller X zieht es beispielsweise vor, den ganzen Zeichenkrempel der Hardware zu ĂŒberlassen; entsprechende Chips hören auf den Namen Character Generators. Der Vorteil hierbei liegt sicherlich in der Einfachheit der Bedienung und der Darstellungsgeschwindigkeit. Man ĂŒbergibt dem Chip schlicht die Nummer des Zeichens, das darzustellen man sich wĂŒnscht - und lĂ€ĂŸt die Transistoren pfriemeln. Die Nachteile sind aber auch nicht ganz ohne: abgesehen davon, daß selbst Chips "Made in Taiwan" nicht fĂŒr umsonst zu haben sind, erlauben nur einige wenige den Gebrauch von Textattributen (hell, blinkend, kursiv...). und dann haben sie auch ihren Preis. Ergo gehen andere Hersteller den Weg ĂŒber die Software. Die ist zwar auch nicht gerade billig, aber wenn sie erst einmal erstellt ist, kann man sie beliebig oft und quasi gratis ohne QualitĂ€tsverlust vervielfĂ€ltigen (was zugegebenermaßen nicht jedem gefĂ€llt - die verschiedensten Kopierschutzmechanismen sprechen da eine deutliche Sprache...). Wie funktioniert nun aber die softwaremĂ€ĂŸige Zeichendarstellung? Die Voraussetzung hierzu ist auf jeden Fall, daß der Bildschirm zur Kategorie “bitmapped" gehört.

not found
Umschalten vom GEM-Fonts

Bitmapping?

Weil man sich darunter wieder schrecklich viel vorstellen kann, sei’s kurz erlĂ€utert. Der SM 124 bringt am ST 640 mal 400 Pixel (Abk. fĂŒr: picture elements) zur Darstellung. Jeder dieser Bildpunkte wird durch ein Bit im Hauptspeicher des Rechners reprĂ€sentiert, d.h., der Bildschirm ist nichts anderes als ein durchgehender RAM-Bereich einer durch die Auflösung bestimmten LĂ€nge. Beim Atari sind dies 640*400=256000 Bits durch 8 macht 32000 Bytes. Möchte man nun einen Punkt setzen, wird im mit diesem Punkt korrespondierenden Byte einfach das entsprechende Bit auf “1” gesetzt (raten Sie mal, wie das Löschen funktioniert!). Ein besonderer Chip ist darauf spezialisiert, das Video-RAM stĂ€ndig abzutasten und die entsprechenden Signale zu erzeugen, die der Bildschirm dann letztendlich darstellen kann. So funktioniert’s bei monochromatischer Darstellung; kommt Farbe ins Spiel, wird’s schon kniffliger. Will man beispielsweise vier Farben darstellen, so benötigt jeder Bildpunkt schon 2 Bit an Information. Die entsprechende Bitkombination muß dann ebenfalls von der Hardware interpretiert werden. Soll der Bildspeicher weiterhin seine GrĂ¶ĂŸe von 32000 Bytes behalten, lĂ€ĂŸt es sich nicht vermeiden, daß sich die Anzahl der darstellbaren Pixel halbiert! Das ist auch der Grund, warum man in der mittleren Auflösung zwar vier Farben, aber nur noch 640 mal 200 Bildpunkte darstellen kann. Übrigens nennt man die Gesamtheit eines Bits ĂŒber alle Pixel eine “bitplane” (bei Monochrombetrieb gibt’s nur eine, bei vier Farben zwei, bei sechzehn Farben vier, bei... na, Sie wissen schon: Logarithmus dualis und so).

Zeichen auf dem Monitor

Nach diesem Ausflug in die Welt der “bitmapped devices” können Sie sich sicher leicht vorstellen, wie man nun Zeichen auf den Monitor bringt. Jedes Zeichen wird durch eine Kombination aus gesetzten oder eben nicht gesetzten Bits reprĂ€sentiert, und zwar in einem Feld von soundsoviel mal soundsoviel Punkten. Im einfacheren Fall ist die GrĂ¶ĂŸe des Rasters fĂŒr alle darstellbaren Zeichen konstant. Im Atari gibt’s gleich drei System-ZeichensĂ€tze (Fonts), bei denen dies so gelöst ist (6x6,8x8 und 8x16). Daß es aber auch anders geht, wissen Kenner von Apple’s Mac zu berichten (hat da jemand Aladin gesagt?). Bei diesen Rechnern hat man nicht nur beliebig viele ZeichensĂ€tze zur VerfĂŒgung, sie sind auch alle fĂŒr Proportionalschrift ausgelegt, d.h. ein ‘i’ benötigt weniger Platz als ein ‘M’. Im Endeffekt sieht das dann zwar ein wenig Ă€sthetischer aus, ist aber wesentlich aufwendiger. Stellen Sie sich vor, Sie mĂŒssen ein Zeichen löschen: abgesehen davon, daß es nicht ganz einfach ist, den Cursor genau auf das Zeichen zu setzen - wieviele Pixel sind’s denn nun, die auszuradieren sind? Ich nehme an, daß Franze “SIGNUM!” Schmerbeck (Tagchen auch) hier ein paar Klagelieder anzustimmen weiß... Bleiben wir also lieber bei der Darstellung Ă€quidistanter Zeichen.

Fonts im ROM

Problem: wie legt man die Font-Daten im ROM ab? Exemplarisch sei’s am 8xl6-Font erklĂ€rt (bei den anderen Fonts ist es genauso). Man zerlegt hierzu die Zeichen in sogenannte “scanlines”, wovon es logischerweise 16 gibt. Ab der Startadresse der Fontdaten stehen dann hintereinander: die oberste Zeile von chr(0), gefolgt von der obersten Zeile von chr(1), gefolgt... und so geht das denn bis 255. Dann kommt die zweitoberste Zeile von chr(0) und so weiter. GlĂŒcklicherweise ist nun eine Scan-line genau acht Bit breit, so daß man sehr simpel auf ein Zeichen zugreifen kann: die oberste Zeile findet man an der Adresse FontStartAdresse + ASCII-Nummer(Zeichen). Die zweite befindet sich 256 Bytes weiter, die dritte... Nun weiß man aber von GEM/VDI-Programmen, daß auch ein ST zu Textattributen bis hin zur Proportionalschrift befĂ€higt ist. Zu diesem Behufe ist wie ĂŒblich wieder Verwaltung notwendig, welche im “Fontheader” residiert. Dort findet man z.B., welche Zeichen ĂŒberhaupt im Font gespeichert sind, wo die Startadresse des Fonts liegt, wie er heißt und einen Zeiger auf die sogenannte “Offset-Tabelle”.

In dieser ist nun gespeichert, wieviele Bits jedes Zeichen breit ist. Somit mĂŒssen nur die Scanlines des ersten darstellbaren Zeichens auf einer Wortgrenze (gerade Adresse) beginnen; die restlichen Daten können ĂŒber die Offset-Tabelle gefunden werden, was jedoch recht zeitaufwendig und rechenintensiv ist. FĂŒr den VT52-Emulator wie auch fĂŒr die VDI-Escapes ist das aber alles vollkommen wurscht: sie erwarten, daß jedes Zeichen exakt ein Byte breit ist und damit basta! Ich habe die Existenz der Offset-Tabelle lediglich der VollstĂ€ndigkeit halber erwĂ€hnt; sie ist, wie gesagt, fĂŒr den Teil der Textausgabe, um den es hier geht, nicht relevant. Was ich Ihnen nun lang und breit erklĂ€rt habe, ist die in Prosa gefaßte Prozedur PUT, die Sie am Anfang des Listings wiederfinden können. Abgesehen davon, daß sie ein Zeichen auf den Bildschirm bringt, sorgt sie auch dafĂŒr, daß - je nach eingestellten Attributen - das Zeichen unterstrichen, halbhell (disabled) oder invertiert dargestellt wird, wobei diese Attribute auch kombiniert werden können. Wie man diese Attribute wĂ€hlen kann, erfahren Sie im folgenden Kapitel.

Massenweise Esc-Sequenzen!

xVT52 erweitert die Anzahl der Steuerzeichen auf mehr als das Doppelte. Das bedeutet zwar, daß Programme, die von den neuen Möglichkeiten Gebrauch machen, nicht mehr ohne den erweiterten Emulator lauffĂ€hig sind (besser gesagt: sie laufen zwar auch unter dem normalen VT52-Emulator, weil dieser - Ausnahmen ausgenommen - die neuen ESC-Sequenzen einfach ignoriert), andererseits dĂŒrfte es wohl keine Schwierigkeiten bereiten, xVT52.PRG mit seinen 4.5 Kilobytes Speicherbedarf mit auf die entsprechende Programmdiskette zu kopieren, oder?

An den folgenden Escapes hat sich nichts geÀndert (d.h. sie unterscheiden sich nicht von denen des Originals):

  • ESC A, B, C, D, E, H, I, J, K, L, M, und Y
  • ESC d, e, f, 1, o, p, q, v und w.

Im ĂŒbrigen sind auch die normalen Steuerzeichen Carriage Return, Line Feed, VT, Backspace und Bell (Wau!) gleich geblieben.

Folgende Steuerzeichen weichen von der VT52-Norm ab:

  • ESC b und c: Diese dienen dem Setzen der Vorder- und Hintergrundfarbe. Da xVT52 jedoch einen Monochrom-Monitor voraussetzt, werden beide Sequenzen schlichtweg ignoriert.
  • ESC j und k: Mit der ersten Sequenz kann man die aktuelle Cursorposition speichern und mit ESC k wieder anspringen. Insofern sind sie zum Original kompatibel; allerdings gestatten sie nun das Speichern von bis zu drei (bisher eine) Positionen. Dabei wird mit ESC k immer die zuletzt gespeicherte Position zurĂŒckgeholt; ist keine gespeichert, so passiert auch nichts! Möchte man eine zurĂŒckgeholte Position spĂ€ter wieder anspringen, muß sie zuerst wieder gespeichert werden.

Von den gewöhnlichen Steuerzeichen hat sich TAB (0x9) geĂ€ndert. WĂ€hrend die Tabulatorweite beim VT52 konstant 8 Zeichen betrĂ€gt, gestattet xVT52 das Setzen von bis zu 80 (!) Tabulatoren an beliebigen Spalten (zugegeben: bei Ausreizen der 80 Tabs ist die Beliebigkeit zumindest beim 80. natĂŒrlich etwas eingeschrĂ€nkt!!). StandardmĂ€ĂŸig ist jede achte Spalte mit einem Tabulator versehen, so daß man auch hier ohne weiteres zur Norm kompatibel bleiben kann, sofern man’s möchte. FĂŒr den einfachen Aufbau von Tabellen ist es jedoch sicherlich nĂŒtzlich, von den erweiterten Möglichkeiten Gebrauch zu machen. Hierzu stehen vier Escapes bereit:

  • ESC N setzt auf jede achte Spalte einen Tabulator, was somit der VT52-Standardeinstellung entspricht.
  • ESC O löscht alle Tabulatoren. Das kann sinnvoll sein, wenn man neue Tabulatoren setzen möchte und nicht weiß, wo bereits welche gesetzt sind, oder es mehr Aufwand bereiten wĂŒrde, die bereits gesetzten und unerwĂŒnschten Tabs einzeln zu löschen.
  • ESC P n setzt an Spalte n-32 (die erste Spalte ist 0) einen Tabulator. Möchte man also beispielsweise einen Tabulator auf Spalte 15 setzten, benutzt man z.B. in BASIC folgenden Befehl: Print chr$(27); "P";chr$(15+32);
  • ESC Q n löscht einen an Spalte n-32 befindlichen Tabulator, wobei es keine Rolle spielt, ob dort vorher einer gesetzt war; will heißen es passiert nichts, wenn Sie einen nicht existierenden Tabulator löschen (warum sollte auch??!). Wenn Sie sich ĂŒber den Offset von 32 wundem sollten: auch hierfĂŒr hat’s eine ErklĂ€rung. Wir reden hier die ganze Zeit von Emulatoren, als wĂ€ren sie die natĂŒrlichsten Dinge der Welt neben Bananen und BĂŒhnenlasern! Mal ganz ehrlich: Haben Sie ‘ne Ahnung, warum es VT52-Emulator heißt? Geschweige denn, was man sich darunter vorzustellen hat? If not then read Historie eise goto end.

not found
Verschiedene Schriftattribute

Historie

Beginnen wir der Einfachheit halber wieder mit dem Ende: Ein Emulator ist die Simulation von Hardware mittels Software, d.h. ein Programm tut so, als ob, ist es aber gar nicht! So ist es ist beispielsweise möglich, den guten alten ST per Programm in einen noch guteren und Ă€lteren Macintosh odergar in den allergutesten und alleraltesten Ih BĂ€h Mhh zu verwandeln, ohne auch nur ein SchrĂ€ubchen berĂŒhren zu mĂŒssen. “Fein!”, werden Sie jetzt sagen, “Und was hat das alles mit meinem VT52 zu tun?”. Es hat. Sie mĂŒssen sich vorstellen, daß es Zeiten gab, in denen Computer sehr, sehr teuer waren. Anstatt also jedem Mitarbeiter einen Mega ST4 mit 60MB-Festplatte und Laserdrucker auf den Tisch zu stellen, gab es nur einen biederen grĂŒn leuchtenden Monitor, wĂ€hrend sich der sogenannte Mainframe irgendwo zwischen WaschkĂŒche und Heizungskeller befand (zum einen, weil er so laut war, und zum anderen, weil die Warmluft der Röhren sich vortrefflich zum WĂ€schetrocknen eignete). Jaja, damals gab’s halt noch keine Stiftung Staren-Nest und der Begriff “71 Hertz” wurde noch vergeblich in BiologiebĂŒchern gesucht. Zu dieser Zeit also trug es sich zu, daß man vor dem Problem stand, daß der Benutzer irgendwie mit dem Rechner in Verbindung treten mußte (darf ich’s kommunizieren nennen?). Also baute man eine Hardware, nannte sie Terminal und befahl ihr, Dolmetscher zu spielen. In ihrem Innersten gab es eine Schaltung (heute wĂŒrde man ja “Chip” sagen, aber damals gab es nur solche aus Kartoffeln), also etwas ureigen Elektrisches, was mittels eines Protokolls den Datenaustausch zwischen Tastatur, Mainframe und Monitor bewerkstelligte (MĂ€use gab’s ja nur im Heizungskeller...). NatĂŒrlich ist die Problematik bis heute die gleiche geblieben; es gibt nach wie vor Zentralrechner, die eine bestimmte Anzahl von Terminals bedienen können. Nur werden die Protokolle nicht mehr nur ĂŒber die Hardware realisiert - zunehmend ĂŒbernehmen auch Programme diese Aufgaben. Womit wir den Kreis geschlossen hĂ€tten, denn nun sind wir wieder beim Emulator. Bleibt immer noch die Frage nach dem ominösen Offset von 32. Er hĂ€ngt halt zusammen mit dem erwĂ€hnten Protokoll (keine Angst, das ist nichts UnanstĂ€ndiges und gibt auch keine Flensburger!). Es regelt nĂ€mlich solch enorm wichtige Dinge wie etwa die Übertragungsgeschwindigkeit und Datensicherheit (Stichwort: Checksummenbildung), unterstĂŒtzt aber auch besondere Funktionen, die mit speziellen Steuerzeichen ausgelöst werden (wie z.B. mit ESC, 0x1 B). Damit nun aber unterschieden werden kann, was simpler Text ist und was nicht, hat man den Steuerzeichen einfach spezielle Codes zugeordnet. Beim ASCII-Code zum Bleistift liegen die Steuerzeichen im Bereich bis OxlF, Zahlen, Buchstaben und Sonderzeichen zwischen 0x20 und 0x7F. Das genau ist der Grund, warum der Offset unbedingt notwendig ist - ohne ihn wĂŒrde man sonst ungewollt ein Steuerzeichen abschicken, was schlimmstenfalls einen Abbruch der DatenĂŒbertragung zur Folge hĂ€tte! Das hĂ€tte ich Ihnen zwar alles viel kĂŒrzer verklik-kern können, aber warum sollte ich mich bremsen, wenn’s gerade gut aus der Feder lĂ€uft? Ich hoffe. Sie teilen meine Ansicht, nichts sei langweiliger als geballtes Wissen im DIN-Format; und vom Schmunzeln ist ja wohl auch noch keine Muskelfaser gerissen...

Zum Thema Textattribute stehen unter xVT52 folgende ESCapes zu Ihren Diensten:

  • ESC y: Schaltet den Unterstreichungsmodus ein.
  • ESC z: Schaltet den Unterstreichungsmodus aus.
  • ESC R: Schaltet die halbhelle Darstellung ein.
  • ESC S: Schaltet die halbhelle Darstellung aus.

Halbhelle Darstellung ist dabei gleichbedeutend mit dem Attribut “disabled” bei Drop-Down-MenĂŒs und Dialogboxen. Die fĂŒr diesen Effekt verantwortliche Funktion heißt LIGHTMASK und löscht fĂŒr jede Scanline abwechselnd alle geraden und ungeraden Pixel, indem sie den ursprĂŒnglichen Inhalt mit 0x55 (%01010101) bzw. 0xAA (%10101010) verUNDet. Das Unterstreichen dagegen ist noch in der Routine PUT untergebracht, weil hier ja lediglich die unterste Scanline des Zeichens mit 0xFF zu ersetzen ist -sehr ökonomisch. Zwei weitere neue Sequenzen kĂŒmmern sich um den Font:

  • ESC F: Schaltet auf den “normalen” 8x16-Font um
  • ESC G: Umschalten auf den “kleinen” 8x8-Font.

Hierbei ist zu erwĂ€hnen, daß beim Umschalten vom 8x16 in den 8x8-Font die Zeile verdoppelt und umgekehrt halbiert wird, damit die Cursorposition (zumindest bei geraden Zeilen) unverĂ€ndert bleibt. Bereits zu Beginn des ersten Teils hatte ich ja bereits erwĂ€hnt, daß man mit ESC i die Ausgabeparameter zurĂŒcksetzen kann, so daß sich eine weitere ErklĂ€rung erĂŒbrigt. Ich verweise hier nochmal auf die Funktion INIT_CONOUT, die sich in des Listings erstem Teil befindet und aus der Sie ersehen können, was alles initialisiert wird. Da fast jede Zeile des Listings dokumentiert ist (was ich Ihnen fĂŒr Assemblerprogramme nur wĂ€rmstens zur Nachahmung empfehlen kann!), hoffe ich, daß Sie mit dem Programm einigermaßen zurecht kommen - auch wenn Sie nicht gerade zu den KoryphĂ€en des Motorola-Jargons zĂ€hlen sollten...

TCB: 
	dc.w 0 		; 0(TCB) aktuelle Spalte
	dc.w 0 		; 2(TCB) aktuelle Zeile
	dc.w 79 	; 4(TCB) maximale Spalte
	dc.w 24 	; 6(TCB) maximale Zeile
	dc.b 2		; 8(TCB) Bitvektor fĂŒr Attribute:
					Bit 0=1: Invertieren ein (ESC p)  
					Bit 1=1: Wrapping ein (ESC v)
					Bit 2=1: Unterstrich ein (ESC y)  
					Bit 3=1: Halbhell ein (ESC R) 
	dc.b 0		; 9(TCB) Grafikflag (ESC r; 0=aus, -1=ein)
	dc.w 0		; 10(TCB) Anzahl gespeicherter Cursorpositionen (ESC j)
	dc.w 0,0 	; 12(TCB) 1. gespeicherte Position (x, y)
	dc.w 0,0 	; 16(TCB) 2. -"-
	dc.w 0,0 	; 20(TCB) 3. -"-
	dc.l 0 		; 24(TCB) Zeiger auf 8x16-Fontdaten (GEM).
	dc.l 0 		; 28(TCB) Zeiger auf 8x8-Fontdaten (GEM)
	dc.l 0 		; 32(TCB) Zeiger auf aktuellen Font
	dc.w 16 	; 36(TCB) Höhe eines Zeichens in Pixel
	dc.w 16*80	; 38(TCB) Bytes pro Textzeile

Tabelle 1a: Aufbau der Variablen TCB (TerminalControlBlock)

Damit’s fĂŒr diesmal nicht zu lang wird, möchte ich an dieser Stelle einen Breakpoint setzen; jedoch nicht ohne Ihnen beim Abtippen viel Spaß und wenig KrĂ€mpfe gewĂŒnscht und Sie darauf hingewiesen zu haben, was Sie im dritten Teil erwartet: Einerseits die weitere Besprechung der neuen ESC-Sequenzen, andererseits will ich Ihnen verraten, wie man zeitkritische Programme auf Trab bringen kann und - naja, mal sehen... Alsdann tschĂŒĂŸ bis neulich!

MS


	CCB: 
		dc.b 2 	; 0(CCB) Bitvektor fĂŒr Cursor-Status:
					Bit 0=1: Cursor enable (fĂŒr Interrupt!)
					Bit 1=1: Cursor darf blinken 
					Bit 2=1: Cursorposition invertiert 
					Bit 3=1: Cursor eingeschaltet
		dc.b 0	; 1(CCB) Anzahl gespeicherter CUR_OFFs (ESC f)
		dc.w 20 ; 2(CCB) Blinkrate
		dc.w 20 ; 4(CCB) ZĂ€hler fĂŒr Blinken (fĂŒr Interrupt)
		dc.l 0	; 6(CCB) absolute Cursorposition (im Video-RAM)

**Tabelle 1b: Aufbau der Variablen CCB (CursorControlBlock)	**

PUTs					; Zeichen in d0.w ausgeben
						; (verÀndert! a0-a4/d0-d3) 
	lea		CCB.a4		; ^Cursor-Controlblock
	bclr	#0,(a4)		;	disable Cursor
	bclr	#2,(a4)		; Cursorposition nicht invertiert
	lea		TCB,a0		; ^TerminalControlBlock
	move.l	32(a0),a1	; ^aktuelle Fontdaten
	lea		\wr_16,a3	; (16 Scan-Zeilen ausgeben)
	cmp.l	24(a0),a1	; aktueller Font=8xl6?
	beq.s	\write		; ja
	lea		\wr_end,a3	; sonst nur 8 Scan-Zeilen ausgeben

\write:
	adda.w	d0,a1		; Offset addieren
	move.l	6(a4),a2	; abs. Cursorposition
	move.b	(a1),(a2)+	; Scan-Zeilen ĂŒbertragen; Spalte ++ 
	move.b	01*256(a1).01*80-1(a2) 
	move.b	02*256(a1),02*80-1(a2) 
	move.b	03*256(a1),03*80-1(a2) 
	move.b	04*256(a1),04*80-1(a2) 
	move.b	05*256(a1),05*80-1(a2) 
	move.b	06*256(a1),06*80-1(a2)
	move.b	07*256(a1),07*80-1(a2) ; letzte Scan-Zeile 8x8
	jmp	(a3)			; Je nachdem, ob 8x8 oder 8xl6-Font gewÀhlt 
\wr_16:					; ist. \wr_end oder \wr_16 anspringen
	move.b	08*256(a1),08*80-1(a2) 
	move.b	09*256(a1),09*80-1(a2) 
	move.b	10*256(a1),10*80-1(a2) 
	move.b	11*256(a1),11*8B-1(a2) 
	move.b	12*256(a1),12*80-1(a2) 
	move.b	13*256(a1).13*80-1(a2) 
	move.b	14*256(a1),14*80-1(a2)
	move.b	15*256(a1).15*80-1(a2) ; letzte Scan-Zeile 8x16
\wr_end:
	btst	#2,8(a0)	; Unterstrich ein?
	beq.s	\half		; nein
	moveq	#80,d1		; Bytes/Pixelzeile
	move.w	36(a0),d2	; Zeichenhöhe in Pixel
	subq.w	#1,d2		; -1
	mulu	d2,d1		; Offset bestimmen
	move.b	#$FF,-1(a2,d1,w); letzte Scan-Zeile unterstreichen
\half:
	btst	#3,8(a8)	; halbe Helligkeit ein?
	beq.s	\inv	; nein
	bsr	LIGHTMASK	; sonst maskieren
\inv:
	btst	#0,8(a0)	; inverse Darstellung?
	beq.s	\inc_col	; nein
	bsr		CUR_INV		; sonst invertieren
	bchg	#2,(a4)		; alten Cursor-Status restaurieren
\inc_col:
	moveq	#80,d1		; Bytes/Pixelzeile
	move.l	a2,6(a4)	; abs. Cursorposition++
	addq.w	#1,(a0)		; Spalte inkrementieren
	cmp.w	(a0),d1		; letzte Spalte erreicht?
	bgt.s	\return		; nein, fertig
	btst	#1,8(a0)	; wrap eingeschaltet?
	bne.s	\wrap		; ja
	subq.w	#1,(a0)		; sonst letzte Spalte
	subq.l	#1,6(a4)	; und abs. Cursorposition beibehalten 
	bra.s	\return		; Ende
\wrap:
	clr.w	(a0)		; Spalte 0
	move.w	38(a0),d3	; Bytes/Textzeile
	ext.l	d3			; auf Langwort bringen
	add.l	d3,6(a4)	; + abs. Cursorposition
	sub.l	dl,6(a4)	; - 1 Pixelzeile = neue Position
	addq.w	#1,2(a0)	; Zeile	++
	move.w	6(a0),d2	; letzte Zeile
	cmp.w	2(a0),d2	; ĂŒberschritten?
	bpl.s	\return		; nein,	fertig
	subq.w	#1,2(a0)	; sonst	letzte	Zeile beibehalten
	sub.l	d3,6(a4)	; ebenso abs. Position
	bsr		SCROLL_UP	; alles 1 Zeile hochschieben
	bsr		DEL_LINE	; und letzte Zeile löschen
\return:
	bset	#0,(a4)		; enable Cursor
	clr.l	d0			; "kein	Fehler"
	rts					; tschau

CUR_INV:				; Cursorposition invertieren
						; (verÀndert! d1/a1-a2/a4) 
	move.l	a2,-(a7)	; Register retten
	lea		TCB,a0		; ^TerminalControlBlock
	lea		CCB,a4		; ^CursorControlBlock
	move.l	6(a4),a1	; abs. Cursor-Adresse
	lea		\inv_end,a2	; 8*8 annehmen
	move.w	36(a0),d1	; Zeichenhöhe
	subq.w	#8,d1		; -Offset fĂŒr 8*8
	beq.s	\inv		; ok, 8*8-Font
	lea		\inv_16,a2	; sonst 16 Bytes NOTten
\inv:
	not.b	(a1)
	not.b	0080(a1)
	not.b	0160(a1)
	not.b	0240(a1)
	not.b	0320(a1)
	not.b	0400(a1)
	not.b	0480(a1)
	not.b	0560(a1)
	jmp		(a2)
\inv_16:
	not.b	0640(a1)
	not.b	0720(a1)
	not.b	0800(a1)
	not.b	0880(a1)
	not.b	0960(a1)
	not.b	1040(a1)
	not.b	1120(a1)
	not.b	1200(a1)
\inv_end:
	bchg	#2,(a4)		; Cursorposition invertiert
	move.l	(a7)+,a2	; Register zurĂŒck
	rts

LIGHTMASK:				; Zeichen unter Cursor halbhell darstellen 
	movem.l	d3/d4/a2,-(a7)	; Register retten
	lea		TCB,a0		; ^TerminalControlBlock
	lea		CCB,a4		; ^CursorControlBlock
	move.w	#$AA,d3		; Bitmaske fĂŒr gerade Pixelzeilen
	moveq	#$55,d4		; Bitmaske fĂŒr ungerade Pixelzeilen
	move.l	6(a4),a1	; abs. Cursor-Adresse
	lea		\light_end.a2 ;	8*8 annehmen
	move.w	36(a0),d1	; Zeichenhöhe
	subq.w	#8,d1		; -Offset fĂŒr 8*8
	beq.s	\light		; ok, 8*8-Font
	lea		\light_16,a2	; sonst 16 Bytes maskieren
\light: 
	and.b	d3,(a1)
	and.b	d4,0080(a1)
	and.b	d3,0160(a1)
	and.b	d4,0240(a1)
	and.b	d3,0328(a1)
	and.b	d4.0400(a1)
	and.b	d3.0480(a1)
	and.b	d4.0560(a1)
	jmp		(a2)
\light_16: 
	and.b	d3.0640(a1)
	and.b	d4,0720(a1)
	and.b	d3,0800(a1)
	and.b	d4.0880(a1)
	and.b	d3.0960(a1)
	and.b	d4,1040(a1)
	and.b	d3.1120(a1)
	and.b	d4.1200(a1)
\1ight_end:
	movem.l (a7)+,d3/d4/a2	; Register zurĂŒck
	rts

DEL_SCRUP:					; ESC M'
	move.w	6(a0),d1		; max. Zeile
	sub.w	2(a0),d1		; -akt. Zeile=Anzahl zu scrollender Zeilen 
	beq.s 	\del			; schon in letzter Zeile, nur noch löschen 
	move.l	6(a4),a1		; abs. Cursorposition
	suba.w	(a0),a1			; -akt. Spalte=^Zeilenanfang
	bsr.s	SCR_UP_ENTRY	; hochscrollen
\del:
	move.w	6(a0),d3		; letzte Zeile
	bra		DEL_ENTRY		; löschen und zurĂŒckkehren

SCROLL_UP:					; Bildschirminhalt	incl. der akt. Zeile 
							; um eine Zeile nach oben schieben
	move.l	LOGBASE,a1		; ^Video-RAM
	move.w	2(a0).dl		; aktuelle Zeile
	beq	SCR_UP_END			; nicht scrollen, falls schon oben
SCR_UP_ENTRY:				; Einsprung fĂŒr E5C 'M'
	subq.w	#1,d1			; d1 in dbra-ZĂ€hler wandeln
	move.w	38(a8),d0		; Bytes/Textzeile
	move.w	3G(a0),d2		; Zeichenhöhe
	lsr.w	#2,d2			; div 4 (es werden immer 4 Pixelzeilen verschoben) 
	subq.w	#1,d2			; in dbra-ZĂ€hler wandeln
	move.l	a1,a2			; Zieladresse (a1)
	adda.w	d0,a2			; + Bytes/Textzeile = Quelladresse (a2) 
	movem.l	a0/a4/a6,-(a7)	; Register retten
\scr_lns:					; d1+1 Zeilen nach oben scrollen
	move.w	d2,d0			; Zeichenhöhe/4-1
\scr_ln:					; 1 Textzeile nach	oben scrollen
	movem.l	(a2)+,REGISTER
	movem.l REGISTER,(a1) 
	movem.l	(a2)+,REGISTER
	movem.l REGISTER,40(a1)	; 4 komplette Pixelzeilen verschieben
	movem.l	(a2)+,REGISTER
	movem.l REGISTER,80(a1) 
	movem.l	(a2)+,REGISTER
	movem.l REGISTER,120(a1) 
	movem.l	(a2)+,REGISTER
	movem.l REGISTER,160(a1) 
	movem.l (a2)+,REGISTER 
	movem.l REGISTER,200(a1) 
	movem.l (a2)+,REGISTER 
	movem.l REGISTER,240(a1) 
	movem.l (a2)+,REGISTER 
	movem.l REGISTER,280(a1) 
	adda.w	#320,a1
	dbra	d0,\scr_ln		; die nÀchsten 4 Pixelzeilen verschieben
	dbra	d1,\scr_lns 	; nÀchste Textzeile 
	movem.l (a7)+,a0/a4/a6	; Register zurĂŒck 
SCR_UP_END: 
	rts

INS_SCRON:					; ESC 'L'
	bsr.s	SCROLL_DOWN 	; Zeile einfĂŒgen
	bra 	DEL_LINE		; und löschen

DEL_SCRDN:					; ESC 't'
	move.w	2(a0),d1		; akt. Zeile
	beq.s	\del			; schon oben, nur noch löschen
	move.l	6(a4),a1		; abs. Cursorposition
	suba.w	(a0),a1			; -akt. Spalte=^Zeilenanfang
	adda.w	38(a0),a1		; +Bytes/Textzeile (1 Textzeile tiefer)
	suba.w	#80,a1			; -80 Bytes (1 Pixelzeile höher)
	bsr.s	SCR_ON_ENTRY	; runterscrollen
\del:
	clr.w	d3				; oberste Zeile (0)
	bra		DEL_ENTRY		; löschen und zurĂŒckkehren

SCROLL_DOWN:				; Bildschirminhalt incl. der akt. Zeile 
							; um eine Zeile nach unten schieben
	move.l	LOGBASE,a1		; ^Video-RAM
	adda.l	#32000-80,a1	; ^letzte Pixel-Zeile
	move.w	6(a0),d1		; max. Zeile
	sub.w	2(a0),d1		; -aktuelle=Anzahl zu scrollender Zeilen 
	beq	SCR_DN_END			; nicht	scrollen, falls schon unten
SCR_DN_ENTRY:				; Einsprung fĂŒr ESC 't'
	subq.w	#1,d1			; sonst in dbra-ZĂ€hler wandeln
	move.w	38(a0),d0		; Anzahl Bytes/Textzeile * Offset
	move.w	36(a0),d2		; Zeichenhöhe
	lsr.w	#2,d2			; div 4 (es werden immer 4 Pixelzeilen verschoben) 
	subq.w	#1,d2			; in dbra-ZĂ€hler wandeln
	move.l	a1,a2			; Zieladresse (a1)
	suba.w	d0,a2			; - Offset = Quelladresse (a2)
	movem.l a0/a4/a6,-(a7)	; Register retten
\scr_lns:					; d1+1 Zeilen nach unten scrollen
	move.w d2,d0			; Zeichenhöhe/4-1
\scr_ln:					; 1 Textzeile nach unten scrollen
	movem.l	(a2)+,REGISTER
	movem.l	REGISTER,(a1)
	movem.l	(a2),REGISTER
	movem.l REGISTER,40(a1) ; 4 komplette Pixelzeilen verschieben
	suba.w	#120,a2
	movem.l	(a2)+,REGISTER
	movem.l	REGISTER,-80(a1)
	movem.l	(a2),REGISTER
	movem.l	REGISTER,-40(a1)
	suba.w	#120,a2
	movem.l	(a2)+,REGISTER
	movem.l REGISTER,-160(a1) 
	movem.l	(a2),REGISTER
	movem.l REGISTER,-120(a1) 
	suba.w	#120,a2
	movem.l	(a2)+,REGISTER
	movem.l REGISTER,-240(a1) 
	movem.l	(a2),REGISTER
	movem.l REGISTER,-200(a1) 
	suba.w	#120,a2
	suba.w	#320,a1
	dbra	d0,\scr_ln 		; die nÀchsten 4 Pixelzeilen verschieben
	dbra	d1,\scr_lns		; nÀchste Textzeile
	movem.l	(a7)+,a0/a4/a6	; Register zurĂŒck
SCR_ON_END: 
	rts

DEL_LINE:					; akt. Zeile löschen, Cursor auf Spalte 0 
	move.w	2(a0),d3		; akt. Zeile
DEL_ENTRY:					; Einsprung	fĂŒr Löschen beliebiger Zeilen 
	move.l	LOGBASE,a1		; ^Video-RAM
	addq.w	#1,d3			; ++
	move.w	38(a0),d1		; Bytes/Textzeile
	mulu	d3,d1			; (akt. Zeile+1)*(Bytes/Textzeile)
	adda.l	d1,a1			; - Bildschirm-Offset
	move.w	36(a0),d2		; Zeichenhöhe
	lsr.w	#2,d2			; div 4 (es werden immer 4 Pixelzeilen gelöscht) 
	subq.w	#1,d2			; in dbra-ZĂ€hler Handeln
	movem.l	a0/a4/a6,-(a7)	; Register retten
	mouem.w	ZEROES,REGISTER	; Register löschen
\lp:
	movem.l	REGISTER,-(a1)	; 4 komplette Pixelzeilen löschen
	movem.l	REGISTER,-(a1)
	movem.l	REGISTER,-(a1)
	movem.l	REGISTER,-(a1)
	movem.l	REGISTER,-(a1)
	movem.l	REGISTER,-(a1)
	movem.l	REGISTER,-(a1)
	movem.l	REGISTER,-(a1)
	dbra	d2,\lp			; nÀchste Pixelzeile
	movem.l	(a7)+,a0/a4/a6	; Register zurĂŒck
	clr.w	(a0)			; Cursor in Spalte 0
	rts						; fertig

BEL:						; Control G (bingggg!)
	suba.l	a5,a5			; A5 löschen
	move.l	BEL_ADR,-(a7)	; Original TOS-Routine
	rts						; anspringen

CRS_LEFT:					; ESC 'C' (=Backspace BS)
BS:
	tst.w	(a0)			; Spalte B?
	beq.s	\zurĂŒck			; ja. BS nicht möglich
	subq.w	#1,(a0)			; sonst Spalte--
\zurĂŒck:
	rts						; das war's

CALC_POS:					; nÀchste Tab-Position berechnen
	clr.l	d7				; Default-RĂŒckgabewert; Kein Fehler
	move.w	d0,d1			; Spalte retten	fĂŒr Byte-Offset
	move.w	d0,d2			; nochmal retten fĂŒr Bit-Offset
	moveq	#7,d3			; Bit-ZĂ€hler initialisieren
	lsr.w	#3,d1			; Byteoffset berechnen (Spalte div 8)
	and.w	d3,d2			; Divisionsrest (Spalte mod 8)
	sub.w	d2,d3			; von 7 subtrahiert = Bit-Offset
\bit_lp;
	btst	d3.0(a1,d1.w)	; Tab gesetzt?
	beq.s	\weiter			; nein
	rts						; sonst zurĂŒck
\weiter:
	addq.w	#1,d0			; Spalte ++
	dbra	d3,\bit_lp		; und weitersuchen
\byte_lp:
	addq.w	#1,d1			; nÀchstes Byte	des Tabulatoren-Bit-Vektors 
	cmpi.w	#10,d1			; schon letztes	Byte ĂŒberschritten? 
	bge.s	\fail			; ja, mit Fehler zurĂŒck
	tst.b	0(a1,d1.w)		; gibt's hier'n Tab?
	bne.s	\s_next			; yeah!
	addq.w	#8,d0			; sonst	Spalte +=8 (!@$X"?C-Syntax!) 
	bra.s	\byte_lp		; und nÀchstes Byte untersuchen
\s_next;
	moveq	#7,d3			; wieder beim MSB mit der
	bra.s	\bit_lp			; Suche	beginnen
\fail;
	moveq	#-1,d7			; nicht fĂŒndig geworden...
	rts						; und tschĂŒĂŸ

TAB:
	lea		TABS,a1			; ^TabulatorenBitVektor
	move.w	(a0),d0			; akt. Spalte
	addq.w	#1,d0			; ++
	cmpi.w	#80,d0			; >=letzte?
	bge.s	\wrap			; ja, auf Wrap testen
	bsr.s	CALC_POS		; sonst nÀchsten Tab suchen
	tst.l	d7				; gefunden?
	bmi.s	\wrap			; nö
	move.w	d0,(a0)			; sonst Spalte setzen
\ende;
	rts						; und zurĂŒck
\wrap;
	btst	#1,8(a0)		; Wrapping erlaubt?
	beq.s	\ende			; nein,	nichts Àndern
	clr.w	d0				; sonst	ersten
	bsr.s	CALC_POS		; Tabulator finden
	tst.1	d7				; keiner da?
	bmi.s	\ende			; letztendlich nicht, fertig
	move.w	d0,(a0)			; sonst Spalte setzen und LF ausfĂŒhren

LF:
	move.w	2(a0),d0		; akt. Zeile
	cmp.w	6(a0),d0		; 'letzte?
	bmi.s	\ok				; nein
	bsr		SCROLL_UP		; sonst	scrollen
	move.w	(a0),-(a7)		; akt. Spalte merken
	bsr		DEL_LINE		; und letzte Zeile löschen
	move.w	(a7)+,(a0)		; akt. Spalte zurĂŒck
	rts 
\ok:
	addq.w	#1,2(a0)		; Zeile++
	rts

CR:
	clr.w	(a0)			; Cursor in Spalte 0 setzen
	rts						; fertig

CLS:						; Bildschirm löschen*Home
	movem.l a0/a6,-(a7)		; Register retten
	moveq	#49,d0			; 50 mal 8 Pixelzeilen löschen
	move.l	LOGBASE,a6		; ^Video-RAM
	adda.w	#32000,a6		; +32000=Bildschirmende

CLS_ENTRY:					; Entry point fĂŒr teilweises Löschen
	movem.w ZEROES,d1-a2 	; 10 Register löschen 
\lp:
	movem.l d1-a2.-(a6)		; 8 komplette Pixelzeilen löschen
	movem.l d1-a2,-(a6)		; (jeder movem-Befehl löscht 40 Bytes)
	movem.l	d1-a2,-(a6)
	movem.l	d1-a2,-(a6)
	movem.l	d1-a2,-(a6)
	movem.l	d1-a2,-(a6)
	movem.l	d1-a2,-(a6)
	movem.l	d1-a2,-(a6)
	movem.l	d1-a2,-(a6)
	movem.l	d1-a2,-(a6)
	movem.l	d1-a2,-(a6)
	movem.l	d1-a2,-(a6)
	movem.l	d1-a2,-(a6)
	movem.l	d1-a2,-(a6)
	movem.l	d1-a2,-(a6)
	movem.l	d1-a2,-(a6)
	dbra	d0,\lp			; die nÀchste 10 Zeilen
	movem.l	(a7)+,a0/a6		; Register zurĂŒck

HOME:						; Cursor nach 0,0
	clr.w	(a0)			; Spalte 0
	clr.w	2(a0)			; Zeile	0
	rts

E_BIG:						; Offset-Tabelle fĂŒr ESC "Großbuchstabe" 
	dc.w 0					; A Cursor rauf
	dc.w CRS_DOWN-CRS_UP	; B Cursor runter
	dc.w CRS_RIGHT-CRS_UP	; C	Cursor rechts
	dc.w CRS_LEFT-CRS_UP	; D	Cursor links
	dc.w CLS-CRS_UP			; E Bildschirm löschen
	dc.w BIG_FONT-CRS_UP	; F 8xl6-Font aktivieren
	dc.w SML_FONT-CRS_UP	; G 8x8-Font aktivieren
	dc.w HOME-CRS_UP		; H Home
	dc.w CRS_UP_SCR-CRS_UP	; I Cursor rauf (Scrollen, falls in Zeile 0) 
	dc.w DEL_FROM_CRS-CRS_UP ; J Löschen ab Cursor bis Bildschirm-Ende 
	dc.w L_TO_END-CRS_UP 	; K Löschen ab Cursor bis Zeilenende
	dc.w INS_SCRDN-CRS_UP 	; L Zeile einfĂŒgen. Rest runterschieben
	dc.w DEI_SCRUP-CRS_UP 	; M Zeile löschen, Rest hochscrollen
	dc.w DEF_TABS-CRS_UP 	; N Tabulatoren an jede 8. Spalte setzen 
	dc.w CLR_TAB5-CRS_UP 	; O alle Tabulatoren löschen (Tabula rasa) 
	dc.w SET_TAB-CRS_UP		; P	Tabulator setzen
	dc.w CLR_TAB-CRS_UP		; Q Tabulator loschen
	dc.w LIGHT_ON-CRS_UP	; R halbe Helligkeit einschalten
	dc.w LIGHT_OFF-CRS_UP	; S volle Helligkeit einschalten
	dc.w FSCR_LEFT-CRS_UP	; T akt. Zeile um 1 Pixel nach links scrollen 
	dc.w FSCR_RIGHT-CRS_UP	; U akt. Zeile in 1 Pixel nach rechts scrollen 
	dc.w FSCRON-CRS_UP		; V incl. akt. Zeile pixelweise n. unten scrollen 
	dc.w FDSCRUP-CRS_UP 	; W akt. Zeile löschen, Rest pixelweise hochscrollen 
	dc.w FSCRUP-CRS_UP		; X incl. akt Zeile pixelweise n. oben scrollen 
	dc.w DIR_CRS_CRS_UP		; Y	Cursor positionieren (Zeile, Spalte) 
	dc.w FDSCRDN_CRS_UP 	; Z akt. Zeile löschen, Rest pixelweise runter

E_SML:						; Offset-Tabelle fĂŒr ESC "Kleinbuchstabe" 
	dc.w CRS_ON_T-SET_COLOR ; a Cursor einschalten, falls ESC 'f'-ZĂ€hler=0 
	dc.w 0					; b	Vordergrundfarbe setzen
	dc.w 0					; c	Hintergrundfarbe setzen
	dc.w DEL_TO_CRS-SET_COLOR; d von Bildschirmanfang bis incl. Cursor löschen 
	dc.w CRS_ON-SET_COLOR	; e Cursor einschalten. ESC ’f'-ZĂ€hler löschen 
	dc.w CRS_OFF-SET_COLOR	; f Cursor ausschalten 
	dc.w GET_TCB-SET_COLOR	; g Pointer auf TerminalControlBlock holen 
	dc.w GET_CCB-SET_COLOR	; h Pointer auf CursorControlBlock holen 
	dc.w RESETCO-SET_COLOR	; i Terminal initialisieren 
	dc.w SAVE_CRS-SET_COLOR ; j Cursorposition speichern 
	dc.w REST_CRS-SET_COLOR ; k Cursor an zuletzt gemerkte Stelle
	dc.w DEL_LINE-SET_COLOR ; l Zeile löschen 
	dc.w WHITE_BLK-SET_COLOR ; m weiße Schrift auf schwarzem Grund 
	dc.w BLK_WHITE-SET_COLOR ; n schwarze Schrift auf weißem Grund
	dc.w L_TO_CRS-SET_COLOR ; o von Zeilenanfang bis incl. Cursor löschen 
	dc.w INV_ON-SET_COLOR	; p inverse Darstellung ein
	dc.w INV_OFF-SET_COLOR	; q inverse Darstellung aus
	dc.w GRAPHMODE-SET_COLOR ; r Grafikmodus 
	dc.w INS_SCRUP-SET_COLOR ; s Zeile einfĂŒgen, Rest hochschieben
	dc.w DEL_SCRDN-SET_COLOR ; t Zeile löschen. Rest runterschieben 
	dc.w SCR_LEFT-SET_COLOR ; u akt. Zelle um 1 Spalte nach links scrollen 
	dc.w WRAP_ON-SET_COLOR ; v automatischen ZeilenĂŒberlauf einschalten 
	dc.w WRAP_OFF-SET_COLOR ; h automatischen ZeilenĂŒberlauf ausschalten 
	dc.w SCR_RIGHT-SET_COLOR ; x akt. Zelle um 1 Spalte nach rechts scrollen 
	dc.w UNDER_ON-SET_COLOR ; y Unterstrich ein 
	dc.w UNDER_OFF-5ET_COLOR ; z Unterstrich aus

CRS_ON_T:					; ESC 'a'
	tst.b	1(a4)			; liegt kein ESC 'f'-Aufruf vor?
	beq.s	\ein			; nein, einschalten
	subq.b	#1,1(a4)		; sonst Anzahl der Aufrufe dekrementieren 
	bne.s	\zurĂŒck			; nur bei 0 einschalten
\ein:
	bset	#3,(a4)			; Cursor einschalten
\zurĂŒck:
	rts

SET_COLOR:					; ESC 'b'/'c' (nicht unterstĂŒtzt) 
	lea		VEC_BASE,a1		; Vektor
	lea		SET_COLOR2,a2	; auf Holen des Farbwertes
	move.l	a2,(a1)			; umschalten
	rts
SET_COLOR2:
	lea		VEC_BASE,a1		; Vektor wieder
	lea		STD_VEC,a2		; auf normale Ausgabe
	move.l	a2,(a1)			; umschalten
	rts

CRS_ON:						; ESC e'
	bset	#3,(a4)			; Cursor einschalten
	clr.b	1(a4)			; Anzahl CRS_0FF löschen
	rts

CRS_OFF:					; ESC 'f'
	bclr	#3,(a4)			; Cursor ausschalten
	addq.b	#1,1(a4)		; Anzahl der Aufrufe inkrementieren
	rts

INV_ON:						; ESC 'p'
	bset	#0,8(a0)		; inverse Darstellung ein
	rts

INV_OFF:					; ESC 'q'
	bclr	#0,8(a0)		; inverse Darstellung aus
	rts

WRAP_ON:					; ESC 'v'
	bset	#1,8(a0)		; Wrapping einschalten
	rts

WRAP_OFF:					; ESC 'w'
	bclr	#1,8(a0)		; Wrapping ausschalten
	rts

UNDER_ON:					; ESC 'y'
	bset	#2,8(a0)		; Unterstrich einschalten
	rts

UNDER_OFF:					; ESC 'z'
	bclr	#2,8(a0)		; Unterstrich ausschalten
	rts

INS_SCRUP:					; ESC 's'
	bsr		SCROLL_UP		; Zeile einfĂŒgen, Rest hochschieben
	bsr		DEL_LINE		; und neue Zeile löschen
	rts