scene.org File Archive

File download

<root>­/­mirrors­/­amigascne­/­Scrollers­/­I-Groupstext­/­ImmortalSouls/IS-CrBodyBlowsV2.txt

File size:
135 261 bytes (132.09K)
File date:
2013-04-01 23:04:22
Download count:
all-time: 197

Preview

9TH OF FEBRUAR 1994 - THE IMMORTAL SOULS ARE VERY PROUD TO PRESENT: BODY BLOWS VERSION 2 !!! 
TEAM 17 MADE THREE 983040 BYTES DISKS (USUALLY 901120 BYTES) BUT THAT DID'NT MATTER TO ME. I 
DECRUNCHED ALL THE DATAS AND RECRUNCHED THEM WITH THE UNBELIEVABLE CRUNCHMANIA AND NOW I HAVE
 380KB FREE DISK SPACE ! I WAS REALLY CLOSE TO PUT MILLENIUM (THE GAME!) ON THE EMPTY SPACE 
BUT THERE WERE 3 BLOCKS TO LESS EMPTY - BAD LUCK ! BUT NOW THE CREDITS: THE CRACK WAS DONE BY
 MYSELF (NAGILUM) OF COURSE, I ALSO CODED THE INTRO. THE LOGO AND THIS FONT WERE DRAWN - AS 
USUALLY - BY ASGARD AND THE NICE SOUND YOU SHOULD HEAR RIGHT NOW WAS DONE BY TRAY! I'M QUITE 
SURE THAT THIS INTRO HAS THE LONGEST SCROLLTEXT EVER WRITTEN (ABOUT 129KB)! THE TEXT IN THE 
MIDDLE OF YOUR SCREEN IS A COMPLETE BOOK CALLED `ASSEMBLER - PROFI TIPS'N TRICKS". YOU CAN 
STOP THE TEXT BY HOLDING THE RIGHT MOUSEBUTTON. THE SOURCEFILE FOR THIS INTRO TAKES 300KB BUT
 ONLY 500 OF 6000 LINES ARE REAL CODE THE REST ARE THE COPPERLIST, FONTS AND THE SCROLLTEXTS!
 SHORT AND PC-RELATIVE IS THE WAY I LIKE TO CODE !!! § ... ... ... WHAT THE HELL, TRAY IS 
HERE AGAIN TYPING SUM REALLY HOT INFORMATION FOR YA! YEAH, I KNOW THAT IT IS STANDING IN 
LITTLE (OR MAYBE BIG, EH?) INTROS FROM TIS BUT I THINK YOU SHOULD BETTER KNOW ABOUT IT ... IT
 IS THE SOUNDPACK WHICH I PRODUCED IN ONE AND A HALF YEAR. IF U WANT IT - CONTACT ME (TRAY) 
UNDER DA FOLLOWING ADDRESS ... IVO KUECHLER, PROF.-WILLKOMM-STR. 3A, 09212 LIMBACH-OBERFROHNA,
 GERMANY. IF YA WANNA GET SUM DEMOS U CAN CONTACT ME, TOO. IF YA WANT TO SEND ME SUM STUFF 
REMEMBER ... NO ILLEGAL STUFF!!! ... WELL THAT IS ALL I WANNA TELL YA! ... ... ... NAGILUM 
AGAIN TYPING HERE! SEE 'YA IN OUR NEXT PRODUCTION. I GOT A LOT OF STUFF RIGHT HERE (TURRICAN 3,
 ELITE 2, DISPOSABLE HERO, URIDIUM, ZOOL II,...) I THINK MORTAL KOMBAT'LL BE THE NEXT WHILE 
TRAY IS DOING `DIE SIEDLER". LET'S SEE WHAT'S NEXT! BYE ! §            TEXT WRAP                   



 Press right mousebutton to stop the text...
The following book was written by Manfred Leidorf
To order the book write to:

    media Verlagsgesellschaft
    Waldweg 5
    88175 Scheidegg
    Germany
or
    INTERCOMP A. Mayer G.m.b.H
    Heldendankstraße 24
    A-6900 Bregenz

It costs DM 39.90 or ÖS 329.- or SFR 39.90
and if you order it you'll get all sources on disk !
But let's start right now and here:

Wollten Sie schon immer mal erfahren, wie man eine Library selbst erstellt?
Möchten Sie endlich erfahren, wie das AA-Chipset direkt programmiert wird ?
Diese und viele andere Fragen beantwortet "Assembler Profitips & Tricks."
Wichtige Kniffe zum optimalen Programmieren gehören ebenso dazu wie eine
Einführung in die Prozessoren ab 68020 und dem mathematischen Koprozessor.
Eine "Frage- und Antwort-Runde" und die Grundlagen für einen absolut
perfekten Kopierschutz, der selbst Hardwarezusätzen standhält, runden das
Ganze angenehm ab.


* AA-Chipset-Programmierung

* Erweitere Grafikfähigkeiten unter OS 3.0

* Kopierschutz

* Optimierter Programmcode

* Fragen und Antworten

* Wie erstelle ich eine eigene Library ?

INHALT

Einführung................................xxxx

Kapitel 1: 

Die höheren Prozessoren...................xxxx

Kapitel 2: 

Grundlagen für optimiertes Programmieren..xxxx

Kapitel 3:

OS 2.0 kontra OS 3.0 (Grafik).............xxxx

Kapitel 4:

Blitter speziell und AA-Chipset...........xxxx

Kapitel 5:

Eine Library im Eigenbau..................xxxx

Kapitel 6:

Fragen und Antworten......................xxxx

Kapitel 7:

Der absolut sichere Kopierschutz..........xxxx

Anhang....................................xxxx


Einführung

Willkommen bei "Profi-Tips und Tricks in Assembler". Obwohl Hochsprachen
zur Zeit eine Hochkonjunktur erleben, ist Assembler immer noch eine sehr
beliebte Programmiersprache. Da es die Sprache des Prozessors selbst ist,
lassen sich - allen anderweitigen Behauptungen zum Trotz - nur so die
optimalen Möglichkeiten aus dem Rechner kitzeln. Dieses Büchlein soll
Ihnen helfen, ein paar Feinheiten der Maschinensprache kennenzulernen, und
setzt entsprechend vorraus, daß Sie bereits mindestens Grundkenntnisse
dieser Sprache besitzen.

Natürlich kann ein solch kompaktes Buch bei weitem nicht alles abdecken.
Deshalb haben wir ein paar ganz spezielle Themen ausgesucht, die oft in
Form von Leserfragen auftauchen. Diese Buchreihe könnte - ihren WÜnschen
und Vorschlägen entsprechend - durchaus fortgesetzt werden, auch mit
eventuell größerem Umfang oder ganz speziellen Themen. Wenn Sie Wünsche
oder Vorschläge haben: Schreiben Sie uns!
Die Welt des Programmierens ist unerschöpflich und selbst ein
1000-Seiten-Buch hätte nicht die Möglichkeit, alles zu berücksichtigen.
(Nichts destotrotz habe ich auch ein solches geplant....)

Aber ich habe mich umgehört. Was interessiert einen angehenden bzw.
gerade lernenden Assemblerprogrammierer speziell auf dem Amiga? Welche
Fragen tauchen am häufigsten auf? Folgendes kam dabei heraus:


Die zur Zeit aktuellsten Themen


- Prozessoren ab 68020

- Programmierung des AA-Chipsets

- Optimiertes Programmieren

- Erstellen einer eigenen Library


Dies ist quasi die "Top 4" der Wünsche, die deshalb in diesem Buch
besondere Beachtung finden. Dokumentationen der AA-Chips und OS  3.0 sind
zur Zeit noch sehr spärlich, von den "Autodocs" in Englisch einmal
abgesehen, und selbst dort gibt es einige Verwirrungen.
Zusätzlich gibt es ein kleines "Frage und Antwort" - Spiel und sonstige
Feinheiten.
Noch einige gute Ratschläge zu Beginn:

1.)

So schön Assembler auch ist, der Nachteil bleibt die große
Fehleranfälligkeit, wenn keine Programmierdisziplin gewahrt wird. Eine
Hochsprache zu beherrschen ist deshalb sicher nicht verkehrt. So kann das
Programmgerüst wesentlich schneller und komfortabler erstellt werden. Die
zeitkritischen Teile schreiben Sie dann einfach in Assembler. Moderne
Compiler ermöglichen das Einbinden von Assemblerteilen. Es ist also nicht
unbedingt notwendig, eine Dateiverwaltung in reinem Assembler zu schreiben.
Hier würde es beispielsweise genügen, das Hauptprogramm zum Beispiel in C
oder Modula II zu schreiben, während zeitkritische Sortierroutinen in
Assembler erstellt werden.

2.)

Besorgen Sie sich (bitte auf legale Weise) unbedingt die offiziellen
Includedateien zu OS 2.0 bzw. OS 3.0. Benutzen Sie einen Assembler, der mit
solchen Includes auch arbeiten kann. Auch wenn es am Anfang sehr
gewöhnungsbedürftig ist: Es ist doch sicher aussagekräftiger, "move.b"
bm_depth(a2),d0" zu schreiben als "move.b 5(a2),d0".
Ein weiterer Vorteil: Wenn sich irgendwelche Offsets in einer neuen
Betriebssystem-Version ändern, brauchen Sie nur Ihr Programm mit den neuen
Includes OHNE ÄNDERUNG zu assemblieren - schon ist ihr Werk angepaßt!

3.)

Das allseits beliebte "Hardwarehacken" ist sicher in einigen Situationen
sinnvoll. Aber überlegen Sie es sich bitte dreimal, ob es denn wirklich
sein muß. Ist es unbedingt notwendig, das Laden von Daten auf Diskette mit
einer eigenen Routine zu bewerkstelligen? Muß selbst Ihre eigene
Textverarbeitung ein reiner Hardwarehack sein? Schließlich soll doch der
Amiga möglichst bleiben, was er ist: Ein Multitasking-Rechner. Natürlich
gibt es auch genügend Situationen, bei denen es nicht vermeidbar ist.
Komplexe Spiele unter dem Betriebssystem sind kaum möglich. Aber selbst
dann muß nicht gleich das ganze System gekillt werden. Ein Programm muß es
mindestens ermöglichen, nach Verlassen des Programms dort zu landen, wo es
vor dem Start war (meistens CLI oder Workbench). Die Entschuldigung mit der
"Speicherknappheit" ist out: 98% aller Amigabesitzer haben mindestens 1 MB
Speicher. Die restlichen 2% werden sicher bald nachziehen, da
Speichererweitungen inzwischen sehr preiswert sind.

Wie dem auch sei: Ich wünsche Ihnen viel Erfolg bei Ihrer
Assemblerprogrammierung. Lassen Sie sich auch bei Mißerfolgen nicht
unterkriegen! Ausdauer und gute Nerven braucht jeder Programmierer, nicht
nur bei Assembler. 
Wenn Sie mich jetzt noch fragen, welcher Assembler denn nun der
beste ist, hier eine Liste der populärsten Assemblerpakete mit Vor- und
Nachteilen ohne Anspruch auf Vollständigkeit. Es werden nur Includefähige
Assembler berücksichtigt.

Programm:

----
A68k
----

Vorteile:

Hält sich an Standard. Public Domain. Zugehöriger Sourcecode(!) dabei!
Gilt als "Maßstab", so erzeugt z.B. Aztec C ein A68k-Kompatibles Listing.

Nachteil:

Keine komplette Entwicklungumgebung, Optimizer mit Bugs, nur 68000 Code
(zumindest die mir bekannte Version...)

-------
ASM One
-------

Vorteile:

Komplette Entwicklungsumgebung, mit schnellem Editor, Monitor, und 
leistungsfähigem Source-Debugger.

Nachteile:

Nur 68000-Code, nicht ganz bugfrei, zu viele Macken des veralteten Sekas
übernommen.

------
O.M.A.
------

Vorteile:

Unterstützt FPU, MMU, 68020/30/40, komplette Entwicklungsumgebung, sehr
guter Optimizer, leistungsfähiger Debugger. Schnell!

Nachteile:

Trotz seiner Qualitäten ist der Debugger sehr umständlich zu bedienen. 

--------
MaxonASM
--------

Vorteile:

Sehr gute (komplette) Entwicklungsumgebung, integrierter Reassembler, 
Code bis hin zum 68030, FPU und MMU.

Nachteile:

Relativ viele Bugs, besonders im Reassembler. Dieser beherrscht außerdem
nur die Librarys bis OS 1.3 und nur 68000-Code...

-------
DevPak:
-------

Hier gilt in etwa das gleiche wie bei O.M.A.


KAPITEL 1

Die höheren Prozessoren

Spätestens seit dem Amiga 1200 muß der Programmierer einsehen, daß es nicht
nur den 68000 gibt. Natürlich ist die Umstellung nicht so tragisch: Bis
auf klitzekleine Ausnahmen laufen (natürlich) 68000-Programme auch auf den
höheren Brüdern. Aber sie sind wesentlich schneller, besitzen erheblich
mehr Adressierungsarten und auch eine ganze Reihe zusätzlicher Befehle.

Die Vorteile: Sowohl Adress- als auch Datenbus sind voll auf 32 Bit
ausgelegt. Um dieses Plus auch nutzen zu können, brauchen wir spezielles
RAM, das auch 32Bit-breit ansprechbar ist. Man sagt: Der Prozessor braucht
32Bit-RAM. Ansonsten muß der 68020/30/40 seine Daten durch den 16Bit-Bus
quetschen und ein Großteil des Geschwindigkeitsgewinns ist dahin. Doch das
alles ist nicht die einzige Verbesserung: Der 68020 besitzt eine
dreistufige Prefetchtechnik. Wir erinnern uns: Wenn der 68000 einen Befehl
bearbeitet, hat er den nächsten Code schon eingelesen. Der 68020 geht noch
weiter, bei ihm sind es deren zwei.

Weitere Pluspunkte:

Der Prozessor besitzt ein eigenes internes RAM von 256 Bytes, den
sogenannten Cachespeicher. Das reicht für eine ganze Menge Befehle. Wenn es
gelingt, eine Unterroutine auf 256 Bytes Größe zu beschränken, so kann es
intern im Prozessor laufen, ohne daß er auf den Speicher zugreifen muß. Die
Adressierungszyklen spart er somit ein und beschleunigt wesentlich den
Programmablauf.

Beim 68030 kommt nochmal ein 256 Byte-Cache speziell für Daten hinzu.
Dieser Datencache hat einen sogenannten "Write-Through"-Effekt, d.h. beim
Schreiben wird sofort wieder in das "richtige" RAM geschrieben, während er
lesend brav im Cache bleiben kann. 
Der 68030 ist bei gleicher Taktfrequenz 50-70% schneller als der 68020.

Beim 68040 gibt es ebenfalls diese beiden Caches, jedoch jeweils satte 4
KB!!! Zusätzlich gibt es beim Datencache den Copyback-Modus, der den
Write-Through-Effekt nur zuläßt, wenn man es speziell befiehlt. Das macht
den Prozessor noch schneller. Allerdings ist dieser Copyback-Modus schuld
daran, daß so viele Programme mit dem 68040 nicht laufen.
Der 68040 ist bei gleichem Takt viermal schneller als der 68030.

Weitere Pluspunkte:

Während der 68000 noch mindestens 4 Taktzyklen für einen Buszugriff
benötigt, sind es beim 68020 nur noch 3, beim 68030 nur noch 2 und beim
68040 gar nur noch einer!

Die Prozessoren sind in der Regel auch höher taktbar als der 68000. Der
"kleinste" 68020 ist für 16 Mhz ausgelegt, das gleiche gilt für den 68030.
Beim 40er ist der Minimalwert 25 Mhz, wobei dieser Prozessor intern den
Takt sogar noch verdoppelt! Ein 25 Mhz-68040 läuft also eigentlich mit 50
Mhz, zumindest bei seiner internen Arbeit.

Sowohl der Befehls- als auch der Registersatz ist erheblich erweitert
worden. Außerdem besitzen diese CPUS mehr als dreimal so viel
Adressierungsarten. Die revolutionärste Neuerung ist übrigens beim 68040
der Befehl "move16".

Beispiel:	move16 (a0)+,(a1)+

Kopiert 16 Bytes mit einem einzigen Befehl. Bei waitstate-freiem RAM und
funktionierendem Burstmodus benötigt dieser Befehl lediglich 5 Taktzyklen!

Ebenfalls wichtig ist die Coprozessor-Schnittstelle, die eine einfache
Benutzung des Mathematik-Coprozessors 68881 und 68882 ermöglicht. Beim
68040 ist dieser Koprozessor schon integriert, allerdings mit einem
abgespeckten Befehlssatz. Auch eine MMU gibt es, wobei ab dem 68030 bereits
eine solche integriert ist.

Die MMU ermöglicht uns, bestimmte physikalische Adressen in andere logische
Adressen umzuwandeln. Beispiel: Wir könnten eine andere Kickstart an die
Adresse $200000 laden. Die MMU sorgt nun dafür, daß der 68020/30/40
"glaubt", diese Kickstart läge normal bei $f80000.

Zusätzliche Register

Der 68020/30/40 besitzt einige Register mehr. Ab dem 68020 gibt es z.B.
zwei Userstackpointer statt einem, und zusätzlich ein neuer
Supervisorstack, speziell für Interrupts, dem sogenannten Masterstack.
Erweitert wurde auch das Statusregister, das nun folgende Belegung hat:

Bit:			Funktion:

15			Tracebit 1  ; 00 Kein Trace, 01 Trace nur bei Sprüngen
14			Tracebit 0  ; 10 Trace nach jedem Befehl
13			Supervisorbit
12			Masterstack einschalten
11			unbenutzt
10			IRQ			; Diese drei Bits sind für die IRQ-Maske
9			IRQ
8			IRQ
7			unbenutzt
6			unbenutzt
5			unbenutzt
4			X
3			N
2			Z
1			V
0			C

Seit dem 68010 gibt es das sogenannte Vektorbasis-Register. Während die
Vektoren beim 68000 fix bei Adresse 0 beginnen, sind sie ab dem 68010
verschiebbar. Dieses 32Bit-Register gibt die Startadresse dieser Vektoren
an. Spezielle Functionscoderegister steuern den Cache, etc....

Hier nun eine Tabelle aller Adressierungsarten

(bd,Ax,Xx)

Das kennen wir eigentlich schon von bd(Ax,Xx). Der Unterschied: Der
konstante Offset (bd) darf hier eine Größe von 32 Bit haben oder gar
weggelassen werden.

(bd,PC,Xx)

Dies ist dasselbe, jedoch PC-Relativ.

([bd,Ax],Xx,Faktor)

Eigentlich wie oben, jedoch kann noch mit einem Faktor zusätzlich
multipliert werden, der 0, 1, 2, 4 oder 8 sein darf.

([bd,Ax,Xx],Faktor)

Sieht auf den ersten Blick genauso aus. Achten Sie aber auf die Klammerung!
Die Indirekte Adressierung erfolgt hier erst NACH der Adressierung, beim
vorherigen jedoch VORHER!

([bd,PC],Xx,Faktor)

(bd,PC,Xx],Faktor)

Hier wieder die gleichen nochmal, jedoch PC-Relativ.

Wenn Sie bei den PC-Relativen Addressierungen statt PC ZPC schreiben, wird
das Addieren des Programmcounters unterdrückt. Na ja, wers braucht....

Andere Adressierungsarten betreffen speziell die Sonderregister. Die
Beschreibung dieser würde aber völlig aus dem Rahmen fallen und selbst ein
ganzes Buch benötigen!

Erweiterte Befehle

Die Befehle zur Multiplikation und Division wurden erweitert. Jetzt können
auch zwei Langworte multipliert werden. Das Ergebnis wird dann eben in zwei
Register nach freier Wahl gelegt. Beispiel:

mulu.l	d0,d1,d4

Es wird d0 und d1 multipliziert und das Ergebnis landet in d1 und d4. So
ist eine neue Größe entstanden: Das Quadword (64 Bit). Entsprechendes gilt
umgekehrt für die Division.

Die Condition-Sprungbefehle (BEQ etc.) können nun auch durch den gesamten
Adressraum springen. Es gibt nun also gegenüber JMP keine Einschränkung
mehr und somit drei verschiedene Kürzen: .B, .W, und .L.

Systemsteuerung

MOVEC (privilegiert)

Ab dem 68010 ist die Vektorbasisadresse - wie bereits erwähnt - frei
verschiebbar. Beim 68000 beginnt diese Adresse fest bei Null,
beispielsweise wird der Interruptvektor Ebene 3 in $6c bestimmt. Diese
Tabelle nun läßt sich im gesamten Adressraum verschieben. Hierfür dient das
Steuerregister VBR. Deshalb hat Commodore auch immer wieder davor gewarnt,
die Interruptvektoren direkt zu verbiegen anstatt über das Betriebssystem!

Um nun die Vektorbasis nach $100000 zu verlegen, genügt der Befehl

move.l	#$100000,d0
movec	d0,VBR

Genausogut kann auch ein Adressregister herangezogen werden.

Außerdem lassen sich mit MOVEC auch noch die Register SFC (Source Function
Code Register) und DFC (Destination Function Code Register) beschreiben.
Beim 68020 sind die Cache-Controll-Register CACR und CAAR hinzugekommen.
Die Caches sollten Sie aber ab OS 2.0 über die exec.library manipulieren!

MOVES

Syntax:

MOVES <ea>,Rn
MOVES Rn,<ea>

Mit MOVES (ebenso privilegiert) werden Daten so im Speicher gelesen und
geschrieben, als ab eine andere Art von Adressraum vorliegen würde. Würde
beispielsweise im Supervisormodus der Befehl "MOVE $1000,d0" einer MMU über
die FC-Leitung signalisieren, daß auf Supervisordaten zugegriffen wurde, so
arbeitet MOVES anders: Enthält das SFC den Wert 2, und wird mit MOVES
$1000,d0 gelesen, so wird ein Zugriff auf ein Usermode-Programm
vorgetäuscht. Bei Lesezugriffen mit MOVES wird der Inhalt von SFC auf die
FC-Leitung gelegtm bei Schreibzugriffen entsprechend DFC.

SFC und DFC können folgende Werte enthalten:

1 =	 Usermode-Daten
2 =  Usermode-Programm
5 =  Supervisor-Daten
6 =  Supervisor-Programm
7 =  CPU (z.B. Coprozessorinterface)


Coprozessoren

Dieses Kapitel soll Sie ein wenig in die Programmierung des 68881/68882
einführen. Mit einem mathematischen Coprozessor steigern sich die
Möglichkeiten eines Programms erheblich. Zum einen unterstützt dieser
Coprozessor Fließkommaarithmetik, zum anderen sind sogar komplexe Befehle
vorhanden, um zum Beispiel einen Sinus zu errechnen. Dies geht wesentlich
schneller vonstatten, als wenn man diese Berechnungen softwaremäßig
emulieren müßte.

Die beiden Mathekünstler sind identisch. Der 68882 arbeitet aber nochmals
50% schneller. So benötigt der Befehl FASIN statt 564 Zyklen nur noch etwas
über 400. Diese Zyklenzahl erscheint auf den ersten Blick zwar erschreckend
hoch, aber die Programmierung "von Hand" mit der Haupt-CPU würde wesentlich
länger benötigen. Ein Programm, das viel mit Fließkommaarithmetik arbeitet,
läßt sich so locker 20fach beschleunigen.

Der 68881/82 besitzt acht 80Bit-Fließkommaregister mit den Namen FP0 bis
FP7, ein 32Bit-Statusregister, ein 16Bit-Kontrollregister sowie eine Art
Programmcounter.

Wie ist ein FP-Register nun aufgebaut? Das hängt von der gewählten
Genauigkeit ab. Es gibt einfache Genauigkeit (SINGLE), doppelte Genauigkeit
(DOUBLE) und eine erweiterte (DOUBLE EXTENDED) Genauigkeit.

Hier nun der Aufbau (Anzahl Bits):

				Vorzeichen	Exponent	Mantisse	Gesamt
Einfach:		1			8			23			32
Doppelt:		1			11			52			64
Erweitert:	1			15			64			80

Die Grundrechenarten

Hier stehen folgende Befehle zur Verfügung:

FADD entspricht einer Addition
FSUB ist die Subtraktion
FCOM vergleicht zwei Operanden (entspricht CMP)
FDIV Division
FMUL Multiplikation
FMOD Ermittelt Divisionsrest
FREM dto, nach IEEE Standard

Die Assemblersyntax ist ansonsten wie gewohnt, z.B.

FADD.E (a0),FP2

Die Syntax "E," schreit nach Erklärung:

.B	Wie Hauptprozessor
.W	"     "
.L	"     "
.S	Einfache Genauigkeit
.D  Doppelte Genauigkeit
.E  Erweiterte Genauigkeit
.P  gepackte Dezimalzahl


Höhere Mathematik

Besonders für Vektorgrafik und ähnliche Gemeinheiten bietet sich natürlich
die Benutzung dieser Superbefehle an:

FCOS,FSIN,FTAN,FCOSH,FSINH,FTANH stehen für die entsprechenden
trigonometrischen Funktionen. Zusätzlich existiert noch FCOSTAN zur
Berechnung von Sinus und Cosinus gleichzeitig. Auch die passenden
Umkehrfunktionen wie z.B. FACOS sind vorhanden.

Aber auch vor Logarithmen macht dieser Rechengigant nicht halt: 

FETOX 		berechnet die Funktion e hoch x
FETOXM1		wie vor, jedoch zusätzlich noch -1
FLOG10		Logarithmus zur Basis 10
FLOG2		Logarithmus zur Basis 2
FLOGn		läßt Sie die Basis gar frei wählen!
FLOGnp1		Ermittelt Logarithmus zur Basis e hoch x+1
FTENTOX		Umkehrung von FLOG10
FTWOTOX		Umkehrung von FLOG2
FSQR		Quadratwurzel

Hier nun ein Kernprogramm eines ganz bestimmten und sehr berühmten
Algorithmus. Das Programm ist so noch nicht lauffähig, es fehlt
beispielsweise "SetPixel". Es dient ja auch nur zur Demonstration der
FPU-Befehle.


	MC68882


	moveq #50,d0		; ZYKLEN   Z
	moveq #-2,d1		; X1
	moveq #2,d2			; X2
	moveq #-2,d3		; Y1
	moveq #2,d4			; Y2


	fmove d2,fp0		; XD=(X2-X1)/800
	fsub d1,fp0
	fdiv #800,fp0		; fp0 wird zu XD

	fmove d1,fp1		; X = x1-xd
	fsub fp0,fp1		; fp1 wird zu X

	fmove d4,fp2		; YD=(Y2-Y1)/600
	fsub d3,fp2
	fdiv #600,fp2		; fp2 wird zu YD

	moveq #0,d7		; Startwert Schleife for i=0 to 799

fori:
	fmove d3,fp3		; Y=Y1-YD
	fsub fp2,fp3		; fp3 wird zu Y

	fadd fp0,fp1		; X=X+XD
	
	moveq #0,d6		; Startwert Schleife for j=0 to 599

forj:
	fadd fp2,fp3		; Y=Y+YD
	fmove.x #0.5,fp4	; FP4 ist jetzt A
	fmove.x #0.5,fp5	; FP5 ist jetzt H
	fmove #0,fp6		; FP6 ist jetzt B

	moveq #1,d5		; Startwert Schleife for n=1 to z

forn:
	fmove fp4,fp7
	fmul fp7,fp7
	fmove fp3,-(a7)		; Y retten
	fmove fp6,fp3
	fmul fp3,fp3
	fsub fp3,fp7
	fsub fp1,fp7		; M=A*A-B*B-X
	fmove (a7)+,fp3		; Y restaurieren

	fmul fp4,fp6		; B=A*B
	fadd fp6,fp6
	fsub fp3,fp6		; B=B+B-Y

	fmove fp7,fp4		; A=M

	fmove fp3,-(a7)		; Y retten
	fmove fp2,-(a7)		; YD retten

	fabs fp4,fp2		; ABS (A)
	fabs fp6,fp3		; ABS (b)
	fadd fp2,fp3		; Addieren

	move.l d4,-(a7)
	fmove fp3,d4
	cmp #100,d4
	bhi.B ist_groesser
nextn:
	addq.w #1,d5
	cmp.w d5,d0
	bhi.W forn
	bra.B nextj	

ist_groesser:
	move.l (a7)+,d4
	fmove (a7)+,fp2		; YD restaurieren
	fmove (a7)+,fp3		; Y restaurieren

	btst #0,d5
	beq.B nextj

	jsr setpixel(pc)	; Pixel setzen
nextj:
	addq.w #1,d6
	cmp.w #512,d6
	bne.W forj
nexti:
	addq.w #1,d7
	cmp.w #640,d7
	bne.W fori


***********************************************

Na, haben Sie gemerkt, um was es geht? Hier als Bonus gleich noch ein
Beispiel:



*	x^2+y^2=radius
*	y=sqrt(radius-x^2)
*

	mc68882

radius  = 100
xmittel = 400
ymittel = 300

	move.l #radius,d2	; d2 ist konstanter radius
	mulu.w d2,d2		; radius im quadrat
	move.l #radius,d4	; d4 ist zählregister (max. radius)
	move.l d4,d0		; d0 ist laufende x-koordinate
loop:	move.l d2,d1
	mulu.w d0,d0		; x^2
	sub.l d0,d1			; radius^2-x^2
	fmove d1,fp1
	fsqrt fp1,fp0		; sqrt(radius^2-x^2)
	fmove fp0,d3		; ykoordinate zurück in ein datenregister
	add.w #ymittel,d3	; reale ykoordinate in d3
	move.w d4,d5
	add.w #xmittel,d5	; reale xkoordinate in d5
	subq.w #1,d4		; zähler um eins vermindern		
	bne.b loop			; zurück zum loop
	

*------------------------------------------------------------------
*	Und nochmal was zum Thema Kreise..
*------------------------------------------------------------------

	mc68882
start:
	OPEN_INT			; Intuition öffnen
	OPEN_GFX			; GFX öffnen
						; Bitte anpassen, dies ist nur eine
						; Abkürzung, um Platz zu sparen!

	lea neuscreen(pc),a0
	move.l intbase(pc),a6
	jsr	_LVOOpenScreen(a6)
	move.l d0,screenhandle
	move.l d0,a1
	lea 84(a1),a1			; RASTPORT

	move.w xmittel,d0
	move.w ymittel,d1
	move.w #100,yradius
	move.w #321,xradius
Radiusloop:
	sub.w #20,yradius
	sub.w #20,xradius
	bmi mausi
	move.l gfxbase(pc),a6
	bsr.B ellipse
	jmp radiusloop
mausi:
	btst #6,$bfe001
	bne.B mausi
	move.l intbase(pc),a6
	move.l screenhandle(pc),a0
	jsr _LVOCloseScreen(a6)

	CLOSE_INT					; INT & GFX schließen
	CLOSE_GFX
	rts
ellipse:
	move.w d0,d3
	move.w yradius,d4
	mulu.w d4,d4		; yradius2(d4)=yradius zum quadrat
	move.w xradius,d5	; d5=xradius
	add.w d5,d0			; d0=xmittel+xradius
	jsr _LVOMove(a6)
	move.w d3,d0
	move.w d5,d3		; d3=xradius
	mulu.w d5,d5		; xradius2(d5)=xradius zum quadrat
	fmove.l d4,fp0		; 
	fmove.l d5,fp1		; fp1=xradius2
	fdiv.x fp1,fp0		; Faktor(fp0)=yradius2/xradius2
	fmove fp0,fp4	
xloop:
	move.w d3,d2		; d2=Laufvariable x
	mulu.w d2,d2		; d2=x*x
	fmove.l d2,fp2		; fp2=x*x
	fmul.x fp4,fp2		; fp2=Faktor*x*x
	fmove.l d4,fp0		; fp0=yradius2
	fsub.x fp2,fp0		; fp0=yradius2-(Faktor*x*x)
	fmove.x fp0,fp3		; fp3=yradius2-(Faktor*x*x)
	fsqrt fp3			; fp3=sqrt(yradius2-(Faktor*x*))
	fadd.x #5.0E-01,fp3	; rundungsfehler ausgleichen
	move.l d0,d5		; d0 in d5 retten
	move.l d1,d6		; d1 in d6 retten
	add.w d3,d0			; offset in d0 zu x wert in d3
	fmove.l fp3,d7		; y wert in d7
	add.w d7,d1			; offset in d1 zu y wert in d7
	jsr _LVODraw(a6)	; Pixel d0,d1 setzen
	move.l d5,d0		; d0 wieder mit offset füllen	
	move.l d6,d1
	dbf d3,xloop		; x=x-1, Sprung zu xloop
ende:
	rts
	
xradius:
	dc.w 300
yradius:
	dc.w 200
xmittel:
	dc.w 320
ymittel:
	dc.w 256

screenhandle:
	dc.l 0
neuscreen:
	dc.w 0
	dc.w 0
	dc.w 640
	dc.w 512
	dc.w 1
	dc.b 0
	dc.b 1
	dc.w $8004
	dc.w 15
	dc.l 0
	dc.l titel
	dc.l 0
	dc.l 0
titel:
	dc.b "Ellipse",0
	cnop 0,4


KAPITEL 2 

Grundlagen für optimiertes Programmieren


Maschinencode ist rasend schnell, das steht außer Frage. Diese
Geschwindigkeit hat aber auch seine Nachteile. Besonders Einsteiger
verlassen sich allzusehr auf das "Wunder Assembler". Das geht so weit, daß
meiner Schätzung nach 50% aller Assemblerprogrammierer schlechteren Code
zustandebringen als der aktuellste C-Compiler. Schauen Sie sich mal ein
C-Compilat des SAS C in seiner aktuellsten Version an! Sie können sicher
gehen, daß größtenteils nichts mehr optimiert werden kann. Im Prinzip
verständlich: Wie die Entwickler des SAS C schon richtig bemerkten, kann
eine neu entdeckte Optimiermöglichkeit in den Compiler integriert und dann
schlicht und einfach vergessen werden. Da der Mensch selbst naturgemäß eben
nur ein Mensch ist, übersieht er schon mal einiges.


a) DER TURBOSHIFTER

Der beste Lerneffekt ergibt sich mit Hilfe von Beispielen. Deshalb wollen
wir uns auch nicht lange lumpen lassen und stellen uns vor folgende
Situation:

Sie möchten im FastRAM, aus welchen Grund auch immer, einen bestimmten
Bereich bitweise verschieben. Sagen wir, wir möchten den Inhalt 100
aneinanderliegende Worte um eine bestimmte Anzahl nach rechts verschieben.

Jedem Assemblerprogrammierer wird dabei bestimmt das Kommando ROXR
einfallen, mit dem mit Hilfe des X-Flags über mehrere Bereiche gerollt
werden kann. Da dies nur wortweise geschehen kann, würde eine "Shiftorgie"
über 100 Worte vielleicht folgendermaßen aussehen:
Ich habe übrigens bewußt möglichst "miese" Befehle ausgesucht. Denn
hinterher wollen wir diese Routine kräftig optimieren....
In unserem Beispiel shiften wir den Bereich um 13 Bits nach rechts.

	move.l	#99,d0				; Für 100 Shifts....
	move.l	#12,d1				; Für 13 Shifts.....
	movea.l	#Rollbereich,a0		; Adresse des Bereiches (willkürlich)
	move.l	#0,d2
	roxr.l	#1,d2				; X-Flag löschen

schleife:
	roxr.w	(a0)+
	sub.l	#1,d0
	bpl		schleife
	move.l	#Rollbereich,a0
	move.l	#99,d0
	sub.l	#1,d1
	bpl		schleife

Wenn Sie jetzt sagen, dieses kleine Programm ist miserabel erstellt, so
haben Sie vollkommen recht! Es gibt fast nichts, was nicht optimiert werden
könnte. Damit es Ihnen aber nicht all zu schlecht wird, verrate ich Ihnen
NICHT, wieviel Taktzyklen dieses Monstrum verschlingt. 
Beginnen wir zunächst mit den kleinen Optimiermöglichkeiten:

Wenn wir ein Datenregister komplett verschwenden dürfen und der zu movende
Bereich zwischen -128 und +127 liegt, so können wir die ersten beiden
Befehle folgendermaßen formulieren:

	moveq	#99,d0
	moveq	#12,d1

Der Nachteil von MOVEQ ist, daß der Bereich auf vorzeichenbehaftete
Bytegröße begrenzt ist und außerdem der gemovte Wert auf Langwortgröße
erweitert wird. Schreiben wir beispielsweise MOVEQ #-1,d0, so enthält
hinterher das Register D0 den Wert $FFFFFFFF. Wenn das nicht ins Gewicht
fällt, sollten Sie es anwenden, da es um ein Vielfaches schneller und
kürzer ist. So ganz nebenbei erwähnt löscht der Befehl MOVEQ #0,D0 das
Datenregister schneller (4 Zyklen 68000) als CLR.L D0 ( 6 Zyklen 68000).

Auch der nächste Befehl läßt sich verbessern, nämlich durch:

	lea	Rollbereich,a0

Ist der Datenbereich nicht weiter als 32767 Bytes entfernt, geht es noch
besser:

	lea Rollbereich(pc),a0

Den nächsten Befehl optimieren wir wie schon angedeutet mit einem MOVEQ
#0,D2 . Da der MOVE-Befehl das X-Flag nicht beeinflußt, müssen wir es
künstlich löschen, wozu der nachfolgende roxr dient. Aber ein roxr.w tuts
auch...

Die Sequenzen sub.l	#1,Datenregister und bpl können wir leicht mit der
DBF-Schleife ersetzen:

	dbf	d0,schleife

Sowie:

	dbf	d1,schleife

Nach dem Ende der ersten Schleife hatten wir das Adressregister wieder neu
auf den Anfangswert gesetzt, was unnötige Zeit verbrät. Eine vorherige
Rettung in ein anderes Register wirkt da wahre Wunder. Sollte wirklich kein
Register mehr frei sein, so ist wenigstens die Rettung auf den Stack noch
eine Alternative.

Hier also noch einmal die gleiche Routine (mit dem gleichen Algorithmus),
jedoch wesentlich effizienter:

	moveq	#99,d0				; Für 100 Shifts....
	moveq	#12,d1				; Für 13 Shifts.....
	lea	Rollbereich(pc),a0		; Adresse des Bereiches (willkürlich)
	moveq	#0,d2
	roxr.l	#1,d2				; X-Flag löschen
	move.l	a0,a1				; a0 retten
	move.l	d0,d7				; d0 retten
schleife:
	roxr.w	(a0)+
	dbf	d0,schleife
	move.l	a1,a0
	move.l	d7,d0
	dbf	d1,schleife

Diese Routine ist schon viel besser. Codemäßig ist sie schon sehr
akzeptabel, wenn auch immer noch nicht das optimale. Ihre Trainingsaufgabe:

Verbessern Sie die Routine mit dem gleichen Algorithmus noch weiter!


DER ALGORITHMUS IST ALLES.....

Auch das ist eine Schwäche vieler Programmierer: Es wird sich zwar
vielleicht bemüht, den Code zu optimieren, nicht aber den verwendeten
Algorithmus.
Sie wissen sicher, daß besonders Zugriffe auf den Speicher besonders
zeitintensiv sind. Die in unserem Beispiel verwendete Routine macht
insgesamt satte 1300 !!! Speicherzugriffe. Da kann es einem fast schon
schwindlig werden. Stellen Sie sich mal vor, Sie müßten einige Kilobytes an
Daten auf diese Weise shiften. Da muß sich selbst der Prozessor veräppelt
vorkommen. Doch wetten, daß wir es schaffen, unser Shiftbeispiel um den
Faktor 100 (!!) zu beschleunigen? Setzen wir doch einfach mal unser
logisch arbeitendes Gehirn ein.

Wir stellen fest:

Der Schwachpunkt der Routine ist der ROXR-Befehl, weil er in unserem
Beispiel leider immer nur um ein Bit verschieben kann. Da wir aber 13 mal
shiften wollen, explodieren unsere Zugriffe auf den Speicher.
Schneller wäre der Zugriff, wenn die Daten in Registern wären. Da wir aber
schlecht sämtliche 100 Worte in Prozessorregister legen können, brauchen
wir einen anderen Geistesblitz. Die logische Schlußfolgerung: Es muß eine
Möglichkeit gefunden werden, möglichst selten auf den Speicher zugreifen zu
müssen. Dazu müßten wir die 13 Shifts in einem Arbeitsgang erledigen.
Dummerweise gibt es kein ROXR #13, aber diesen Befehl können wir - jetzt
verrate ich es endlich - simulieren!

Überlegung 1: Die einzige Möglichkeit, 13 Shifts nach rechts auf einen
Schlag zu machen, wäre lsr innerhalb eines Datenregisters. Der einzige
Wermutstropfen: lsr darf in manchen Situationen (zum Glück nicht in allen) 
nur maximal 8 Shifts machen.  Deshalb muß in einem solchen Fall die Aufgabe
geteilt werden in lsr #8 und lsr #5 .

Überlegung 2: Da bei lsr die herausgeschobenen Bits verschwinden, müssen
sie irgendwie gerettet werden.

Überlegung 3: Die herausgeschobenen Bits müssen im nachfolgenden Wort bzw.
Langwort wieder hereingeschrieben werden.

Doch nun will ich sie nicht länger auf die Folter spannen. Allerdings
fehlt es nicht an einer weiteren Übungsaufgabe. Die hier gezeigte Variante
arbeitet wortweise, ist aber an sich bereits hochoptimiert.

Wenn Sie dieses Programm auf Anhieb verstehen, dann kann ich nur
gratulieren. Die Beschleunigung ist beträchtlich: Es wird auf jedes Wort
nur noch zweimal zugegriffen, einmal lesend und einmal schreibend.
Es lohnt sich also schon ab 3 Shifts! Alles andere findet innerhalb von
Registern statt. Doch von vorne:

Sie sehen sicher den Datenbereich "shiftlist", den ich als
"Shiftmaske" bezeichnet habe. Das hat folgende Bewandnis: Wenn ich zum
Beispiel drei Shifts nach rechts mache, muß ich die unteren 3 Bits retten,
und zwar vor der Shiftaktion mit einem AND. Bei drei Bits wird also ein
"AND #7" nötig, da 7 die unteren 3 Bits repräsentieren. Diese 7 wäre in
diesem Falle die Shiftmaske!

Die so geretteten Bits werden nun in ein anderes Register kopiert und an
den linken Rand geshiftet. Wie weit ist es bis zum linken Rand ? Bei 13
Shifts innerhalb eines Wortes exakt 3. (16 - Rechtsshifts) 
Prüfen Sie es nach! 

Das so gerettete und nach links an den Rand geschobene Wort merken wir uns
nun einfach für den nächsten Durchlauf in unserem Beispiel in d2. Deshalb
mußten wir anfangs d2 löschen, damit bei der ersten Aktion keine
Geisterdaten vorhanden ist. Im nächsten Durchgang schließlich wird das so
gemerkte Wort hineingeodert.

Schauen wir uns das Funktionsprinzip mal bildlich an: Der Einfachheit wegen
nehmen wir drei Beispielworte im Bitformat:

1010110010101111 1110100010001010 0101110101110110 0000000000000000

Wir brauchen am Ende ein "überschüssiges Wort", damit die Daten am Schluß
der Kette nicht verloren gehen.

Das erste Datenwort laden wir nach d0 und kopieren es nach d1. Dann wird
mit der schon erwähnten Shiftmaske die zu rettenden Bits mit AND gesichert,
deshalb auch die Kopie in D1. Bei 13 Shifts werden demnach die unteren 13
Bits gerettet. Ergebnis:

0000110010101111

Da wir diese Bits nun in d1 gerettet haben, können wir das Wort in d0 nun
sorgenfrei 13fach nach rechts schieben. Ergebnis:

0000000000000101

Entsprechend werden unsere 13 geretteten Bits um drei nach links geschoben,
sodaß d1 nun enthält:

0110010101111000

Jetzt odern wir den in d2 vorher gemerkten Wert in das Register d0. Da ganz
Anfang noch kein vorher gemerkter Wert existiert und d2 deshalb noch 0 ist,
bleibt das Ergebnis zunächst mal so...

Jetzt ist d2 frei, um den neuen nach links geshifteten Wert , der sich ja
noch in d1 befindet, zu vermerken:

move.l	d1,d2

JETZT schreiben wir das berechnete Wort, daß sich in d0 befindet, in das
zuvor gelesene Wort, beispielsweise mit move.w	d0,(a3)+

Momentan sehen unsere vier Worte also so aus:

0000000000000101 1110100010001010 0101110101110110 0000000000000000

Jetzt lesen wir das zweite Wort in das Register d0, und das ganze beginnt
von vorne. Beachten Sie, das die "verlorenen" Bits sich nun an der
richtigen Position im Register D2 befinden!

Wir kopieren also d0 wieder nach d1 und maskieren nun das zweite Wort mit
der Shiftmaske. Ergebnis:

0000100010001010

Jetzt wird in d0 wieder 13 mal geshiftet. Ergebnis diesmal:

0000000000000111

Die "verlorenen" Bits des vorherigen Wortes, die sich jetzt korrekterweise
linksbündig in D2 befinden, brauchen wir nur hinzuzuodern, sodaß wir als
Ergebnis haben:

0110010101111111

Hurra! Sie sehen, die verschollenen Bits sind nun an der richtige Stelle im
zweiten Wort gelandet!

Hier nun das Programm. Versuchen Sie, es genau zu analysieren.
Beispielsweise enthält es als Optimierung die "ROR"-Alternative. Um zwei
lsl-Kommandos (lsl #8 und lsl #5) zu verhindern, könnten wir in unserem
Fall stattdessen ein ror #3 machen. Das ist schneller und erfüllt in
unserer Situation den selben Zweck...


	moveq	#13,d0				; Für 13 Shifts
	move.w	d0,d6				; Anzahl Shifts merken
	moveq	#16,d5
	sub.w	d6,d5				; Anzahl nach links
	moveq	#8,d3				; zum Vergleichen......
	
	add.w	d0,d0				; Für Wort in Tabelle
	lea	shiftlist(pc),a5
	move.w	0(a5,d0.W),d7			; Shiftmaske
	moveq	#0,d2
	add.w	d4,d4
	addq.w	#1,d4				; Anzahl Worte......
weiter_shift:
	move.w	(a3),d0				; Datenwort holen
	move.w	d0,d1				; Nach d1 kopieren
	and.w	d7,d1				; mit der Shiftmaske anden...
	lsr.w	d6,d0				; Shiften......
	cmp.w	d3,d6
	bhi.B	besser_rohren
	lsl.w	d5,d1
	bra.B	aktion
besser_rohren:
	ror.w	d6,d1
aktion:
	or.w	d2,d0
	move.w	d1,d2
	move.w	d0,(a3)+
	dbf	d4,weiter_shift
	movem.l	(a7)+,d0-d7/a3/a5

*--------------------------------------------------------------------------

Allgemeine Optimierungen

Dieses Beispiel sollte eine Anregung dafür sein, nicht nur den Code an
sich, sondern auch das verwendete Verfahren zu optimieren.
Trotzdem läßt sich innerhalb des Prozessorbefehlssatzes einiges
herausholen. Gut optimierbar sind beispielsweise Rechnungen mit
Zweierpotenzen. 

Einige gesammelte Optimiermöglichkeiten...

1.) MULU

Ein MULU #2,d0 ist programmtechnisch eine größe Stünde. Das wird aber wohl
keiner machen. Besser wäre da schon lsl.l #1,d0, aber auch dies ist nicht
das beste. Am schnellsten wäre in diesem Falle:

add.l	d0,d0

Auch bei einer Multiplikation mit 4 ist

add.l	d0,d0
add.l	d0,d0

immer noch schneller als lsl.l #2,d0. Erst bei drei Verdoppelungen lohnt es
sich nicht mehr.

2.) QUICK

Benutzen Sie so oft wie nur möglich die Quickvarianten der verschiedenen
Befehle! Das funktioniert meist auch mit Adressregistern:

addq.l	#8,d0 statt add.l #8,d0

Bei einem Adressregister können Sie auch mit .W arbeiten, da das Ergebnis
dort auf Langwortgröße erweitert wird:

addq.w	#8,a0 wirkt wie addq.l	#8,a0

Wenn Sie höhere Werte addieren wollen, gibt es zumindest bei
Adressregistern eine interessante Alternative:

Statt add.l	#12000,a0 schreiben Sie lea 12000(a0),a0

Das klappt aber nur bis maximal 32767, ist aber dafür um einiges schneller!

Oft sieht man folgenden Leichtsinn:

lea	0(a0,d0.W),a0
move.l	(a0),d7

Warum nicht gleich: move.l	0(a0,d0.W),d7 ?


3.) PC-RELATIV IST NICHT GLEICH PC-RELATIV

Gewiß: Die PC-relativen Adressierungsarten sind schneller als die normalen
Varianten. Eine ähnliche Technik wird aber oft vergessen, die selbst
schlechten C-Compilern bekannt ist! Anstatt Variablen ständig direkt zu
benutzen, bietet sich ein Basis-Register an, über das Sie zugreifen.
Nehmen wir als Beispiel folgende Variablenliste:

label1:
	dc.w	0
label2:
	dc.w	0
label3:
	dc.w	0
label4:
	dc.w	0
label5:
	dc.w	0

	move.w	label1(pc),d0
	move.w	d0,label2

Hier stört ein Nachteil des PC-Relativen: Nur die Quelle kann so behandelt
werden, nicht aber das Ziel. Mit Hilfe eines Basisregisters läßt sich der
negative Effekt verhindern:

label1	=	0
label2	=	2
label3	=	4
label4	=	6
label5	=	8

variablen:
	dcb.w	10

Wenn Sie nun im Programmkopf einmal ein lea	variablen(pc),a4
initialisieren, so können Sie auf diese Variablen immer über a4 zugreifen,
sowohl mit der Quelle als auch beim Ziel:

	move.w	label1(a4),d0
	move.w	d0,label2(a4)

Dieser Fall ist natürlich nur ein Beispiel. Es ginge ja genausogut:

	move.w	label1(a4),label2(a4)

Oder noch besser:

	move.w	(a4),label2(a4)

Es soll damit nur gesagt werden, daß PC-Relativ allein nicht unbedingt ein
echtes PC-Relatives Programm sein muß.

4.) VERGLEICHEN

Ein weiterer Ansatz zum Optimieren bietet das Vergleichen. Ein cmp mit 0
ist nur bei Adressregistern notwendig, ansonsten ist die TST-Alternative
schneller.

Beispiel:

tst.w	d0 statt cmp.w	#0,d0

5.) Sprünge

Die meisten JMP und JSR-Kommandos in einem Programm lassen sich durch die
Alternativen BRA und BSR ersetzen. Bei ganz kurzen Distanzen (-128 bis
+127) sorgt das Kürzel .B für noch mehr Zeitersparnis.
Prüfen Sie auch, ob Sie nicht auch exotische Adressierungsarten sinnvoll
nutzen können, wie z.B.

JMP	0(a3,d7.W)


6.) Lokale Variablen: Optimierungen für reentranten Code

"Reentrant" heißt "wiedereintrittsfähig". Das bedeutet, daß ein Programm
von mehreren Tasks gleichzeitig durchlaufen werden kann, ohne daß es zu
Störungen kommt. Libraryfunktionen beispielsweise müssen diese Eigenschaft
aufweisen. Es sind also nur lokale Variablen erlaubt, nicht aber statische
Variablen. Daß man selbstmodifizierenden Code vermeiden soll, versteht sich
von selbst.....
Doch wie macht man ein Unterprogramm reentrant? 
Für lokale Variablen bietet sich der Stack an. Wenn Sie beispielsweise 100
Bytes für lokale Variablen benötigen, könnte der Kopf der Unterroutine so
starten:

	link	a5,#-100
	movem.l	d0-d7/a0-a4/a6,-(a7)	; nur wenn nötig...
	
Relativ über das Register a5 mit NEGATIVEM Offset können Sie nun Ihre
Variablen initialisieren und bearbeiten. Beispiel:

	move.w	#200,-100(a5)

Am Ende der Unterroutine schreiben Sie dann:

	movem.l	(a7)+,d0-d7/a0-a4/a6
	unlk	a5
	rts

Was macht der LINK-Befehl ? Er rettet das angegebene Register auf den Stack
und kopiert dann den Stapelzeiger in das angegebene Adressregister. Dann
wird der Stapelzeiger um den angegebenen Wert erniedrigt.
Somit können wir in unserem Beispiel mit negativem Offset 100 Bytes als
Variablenspeicher nutzen, der sich eigentlich im Stack befindet.

Beispiel:

a5 enthält null und der Stapelzeiger (a7) $309ff0.
Jetzt folgt der LINK a5,#-100.
Nach dem Retten von a5 auf den Stack steht dieser nun auf $309fec. Dieser
Wert wird nach a5 geschrieben und anschließend wird der Stackpointer noch
um 100 verringert. Die Differenz von a5 und dem Stapelspeicher dient uns
also nun als Variablenspeicher.
Ein "emuliertes" link a5,#100 sähe so aus:

	move.l	a5,-(a7)
	move.l	(a7),a5
	lea	-100(a7),a7

Nach unlk a5 enthält a5 wieder 0 und der Stapelzeiger wieder $309ff0.

Das emulierte UNLK:

	lea	100(a7),a7
	move.l	(a7)+,a5

Wenn Sie kein Register opfern können oder wollen, können Sie auch direkt
den Stapelzeiger manipulieren. Bei 100 Bytes Bedarf beispielsweise mit

	movem.l	d0-d7/a0-a6,-(a7)
	lea	-100(a7),a7

Jetzt können Sie relativ zum Register a7, diesmal aber mit POSITIVEN
Offsets, Ihre Variablen bearbeiten.
Am Schluß der Routine schreiben Sie entsprechend:

	lea	100(a7),a7
	movem.l	(a7)+,d0-d7/a0-a6


Der Vorteil: Wesentlich schneller als die Link-Variante.

Der Nachteil: Danach läßt sich nur umständlich noch was auf dem Stack
retten, da ja dann der Offset verfälscht würde.
Das Registerretten muß im Gegensatz zur LINK-Variante vorher, das
Restaurieren nachher stattfinden. Vorsicht!!


7.) BESSERES JSR/RTS

Kurz und Bündig: In fast allen Fällen kann beispielsweise ein

	jsr	unter
	rts

ersetzt werden durch

	jmp	unter

Aber verlieren Sie nicht die Übersicht!


8.) NIEDER MIT DEM POLLING

Als Polling wird eine Endlosschleife bezeichnet. Besonders in der ach so
geliebten Szene ist es beliebt, so auf die linke Maustaste zu warten:

waitmouse:
	btst	#6,$bfe001
	bne.B	waitmouse

Oder aber es gibt die Situation, daß eine Libraryfunktion nicht reentrant
ist oder sein darf, weil Sie eine Hardware benutzt (z.B. Drucker oder
Blitter.) Es muß also verhindert werden, daß diese Routine mehr als einmal
gleichzeitig durchlaufen wird. Manche "Spezialisten" behelfen sich so:

zaehler:
	dc.w	-1

routine:
	addq.w	#1,zaehler
	beq.B	benutzung_erlaubt
benutzung_verboten:
	tst.w	zaehler
	bpl.B	benutzung_verboten
	addq.w	#1,zaehler
benutzung_erlaubt:
	.
	.
	.
	.
	subq.w	#1,zaehler
	rts


Diese Methode hat einen erschreckenden Nachteil: Alle anderen Tasks, auch
der, welcher die betroffene Routine gerade benutzt, werden stark
ausgebremst. Ideal wäre, wenn der Task, der die Routine gerade
benutzen will, aber nicht darf, in den Waitstatus geht, bis er die Routine
wieder benutzen darf. Selbst für diese Situation hat das Betriebssystem
eine Lösung parat: Die Semaphoren. Beim Initalieren unseres Programms
müssen wir nur eine leere Semaphore-Struktur bereithalten und
initialisieren. Unser betroffenes Unterprogramm benutzt diese dann:

meine_semaphore:
	blk.b	SS_SIZE			; ohne Includes: blk.b 40,0
							; (Eigentlich wären weniger nötig, aber man
							; weiß ja nie).

	move.l	4.W,a6
	lea	meine_semaphore(pc),a0
	jsr	_LVOInitSemaphore(a6)

Unsere nur einmal gleichzeitig benutzbare Routine sähe dann so aus:

routine:
	move.l	4.W,a6
	lea	meine_semaphore(pc),a0
	jsr	_LVOObtainSemaphore(a6)
	.
	.
	.Unsere Routine
	.
	move.l	4.W,a6
	lea	meine_semaphore(pc),a0
	jsr	_LVOReleaseSemaphore(a6)

Intern passiert nun folgendes: "ObtainSemaphore" prüft, ab die Semaphore
noch frei ist. Wenn, ja, wird das danach folgende Programm ausgeführt.
Ist die Semaphore jedoch schon belegt, geht das Programm in den
Wartezustand - gibt also seine Rechenzeit frei -, bis die Semaphore wieder
frei ist. "ReleaseSemaphore" befreit die Semaphore wieder. Wird das
vergessen, warten andere Tasks auf Ewigkeit: Sie halten es dann wie
die verstorbene Putzfrau: Sie kehren nie wieder.......
Es handelt sich also quasi um ein "bedingtes Wait".


Wenn wir schon dabei sind: Hier eine Routine, die ein Warten auf die linke
Maustaste ohne Polling ermöglicht. Dabei sind keine Kenntnisse des
Input-Device etc. nötig:

	move.l	4.W,a6
	moveq	#-1,d0
	jsr	_LVOAllocSignal(a6)
	move.w	d0,sigbit
	moveq	#0,d1
	bset	d0,d1
	move.l	d1,sigmask

	moveq	#5,d0
	lea	vbiint(pc),a1
	move.l	#irqroutine,18(a1)
	jsr	_LVOAddIntServer(a6)

waitmouse:
	move.l	sigmask(pc),d0
	move.l	4.W,a6
	jsr	_LVOWait(a6)

	lea	vbiint(pc),a1
	moveq	#5,d0
	jsr	_LVORemIntServer(a6)

	move.w	sigbit(pc),d0
	jsr	_LVOFreeSignal(a6)



irqroutine:
	mvoem.l	d1-d7/a1-a6,-(a7)
	btst	#6,$bfe001
	bne.B	keine_maus
	move.l	mytask(pc),a1			; Hier muß noch IHR Task
									; eingetragen werden!
	move.l	sigmask(pc),d0
	move.l	4.W,a6
	jsr	_LVOSignal(a6)
keine_maus:
	movem.l	(a7)+,d1-d7/a1-a6
	moveq	#0,d0
	lea	$dff000,a0
	rts

mytask:
	dc.l	0
sigmask:
	dc.l	0
sigbit:
	dc.l	0
vbiint:
	dc.l	0,0
	dc.b	2,5
	dc.l	0,0,0
	cnop 0,4



Zum Schluß dieses Kapitels noch einen Tip:

Einige Assembler können Programmcode bereits automatisch optimieren.
Besonders der OMA und der MaxonASM beherrschen dies prächtig.
Algorithmen verbessern können sie aber (natürlich) nicht.


Er: "Mein Speicher wird knapp!"
Sie: "Macht nix, räumen wir den Mist eben in den Keller...."


KAPITEL 3

OS 2.0 kontra OS 3.0

Das Betriebssystem OS 2.0 war gegenüber den 1er-Versionen eine erhebliche
Steigerung in Punkto Vielseitigkeit, Komfort und Stabilität. Doch es wurde
leider NUR auf Funktionalität, nicht jedoch auf GESCHWINDIGKEIT Wert
gelegt, von einigen Ausnahmen abgesehen. (Das Diskettenhandling ist z.B.
erheblich schneller geworden). Frei von Fehlern war es auch nicht, aber
welches Programm ist das schon.

Bei OS 3.0 ging es einen kräftigen Schritt voran: Es wurden nochmals eine
ganze Menge Funktionen hinzugefügt, nicht nur wegen den neuen AA-Chips
(aber hauptsächlich). Diesmal hat sich das Entwicklerteam aber auch bemüht,
das System auf Geschwindigkeit hin zu optimieren. Die meisten
Grafikoperationen wurden erheblich beschleunigt, viele wirklich blödsinnige
Systembremsen eliminiert. Falls Sie OS 2.0 und OS 3.0 kennen, haben Sie
sich sicherlich über das wesentlich schnellere Windowhandling und
Textscrolling gefreut. Eine Optimierung habe ich als Beispiel einmal
genauer untersucht und stelle sie ihnen auch live vor:

WritePixel() der "graphics.library"

Der Programmteil, der für das Clipping zuständig ist, wurde weggelassen. Es
geht mal nur um das Zeichnen des Punktes selbst.

Unter OS 2.0 wird tatsächlich für das Setzen eines einzigen Pünktchens der
Blitter eingesetzt:

;
; WritePixel von OS 2.0, Programmteil "Punkt setzen"
;

	MOVE.L	$0004(A1),A0		; Bitmap des Rastports
LB_0D96	
	MULS.W	(A0),D1				; Bytes/Zeile * Zeile
	MOVE.W	D0,D3				; X-Koordinate retten
	EXT.L	D0					; Auf Langwort erweitern
	ASR.L	#3,D0				; /8 teilen
	ADD.L	D0,D1				; Zum Zeilenoffset addieren
	MOVEQ	#$0F,D0				; Bitmaske (0-15)
	AND.W	D0,D3				; Bitnummer
	SUB.W	D3,D0				; ermitteln
	MOVE.B	$0005(A0),D3		; Anzahl Planes
	LEA	$0008(A0),A0			; Start der Planeliste
	MOVEQ	#-$01,D2			; Für Blittermaske
	LEA	$00DFF000,A4			; Basis Customchips
	ADDQ.W	#1,$00AA(A6)		; Blitter-Counter
	BEQ.B	LB_0DC0				; leer, also frei
	BSR.W	$00FA0984			; Arbeitet der Blitter noch ?
LB_0DC0	TST.B	$0002(A4)		; Blitter-DMA
	BTST	#$06,$0002(A4)		; noch in Arbeit ?
	BEQ.B	LB_0DD0				; nein
	BSR.W	$00F9FDF0			; Warten auf Blitter
LB_0DD0	MOVE.L	D2,$0044(A4)	; Blittermaske setzen
	CLR.W	D2					; Unteres Wort löschen
	MOVE.W	D2,$0042(A4)		; CON1 löschen
	BSET	D0,D2				; Zu setzendes Bit eintragen
	MOVE.L	D2,$0072(A4)		; Quelldaten B und A
	CLR.W	D2					; wieder löschen
	MOVE.W	#$0300,D0			; Nur DMA A und D
LB_0DE6	BTST	D2,$0018(A1)	; Diese Bitplane maskiert ?
	BEQ.B	LB_0E16				; ja
	MOVE.B	$28(A1,D2.W),D0		; Minterm lesen
	TST.B	$0002(A4)			; DMA noch frei ?
	BTST	#$06,$0002(A4)		;
	BEQ.B	LB_0E00				; ja
	BSR.W	$00F9FDF0			; Warten auf Blitter
LB_0E00	MOVE.W	D0,$0040(A4)	; BLTCON0 setzen
	MOVE.L	(A0),A5				; Plane holen
	ADDA.L	D1,A5				; Offset dazu
	MOVE.L	A5,$0048(A4)		; Quelle C
	MOVE.L	A5,$0054(A4)		; Ziel D
	MOVE.W	#$0041,$0058(A4)	; Blitter starten
LB_0E16	ADDQ.L	#4,A0			; nächste Plane
	ADDQ.W	#1,D2				; 
	CMP.B	D2,D3				; noch ein Bit ?
	BNE.B	LB_0DE6				; ja



Allein das Warten auf den Blitter und das Initialisieren desselben braucht
schon soviel Zeit, daß man vier Pixel mit dem Prozessor setzen könnte. Das
hat auch Commodore erkannt. Nun wurde bei OS 3.0 die Routine "ReadPixel"
quasi ins Umgekehrte gewandelt. Der Punkt wird nun mit der CPU gezeichnet.
Ergebnis: Bis zu 6 mal schneller als unter OS 2.0:

;
; WritePixel von OS 3.0, Programmteil "Punkt setzen"
;

LB_6CB6	
	MOVE.L	$0004(A1),A0		; BitMap des Rastports
LB_6CBA
	MULS.W	(A0),D1				; Bytes pro Zeile*Zeilenstart
	MOVE.W	D0,D3				; X-Pos retten
	EXT.L	D0					; Auf Langwort bringen
	ASR.L	#3,D0				; /8 für Byteoffset
	ADD.L	D0,D1				; merken...
	MOVE.L	D1,A5				; Als Offset nach a5
	MOVEQ	#$07,D0				; Bitmaske 0-7
	AND.W	D0,D3				; Betroffene Bitnummer
	SUB.W	D3,D0				; ermitteln
	MOVE.B	$0005(A0),D3		; Anzahl Planes
	SUBQ.W	#1,D3				; -1 für schleife
	ADDQ.L	#8,A0				; Start der Planeliste
	MOVE.B	$0018(A1),D1		; Maske
	LEA	$00DFF000,A4
	ADDQ.W	#1,$00AA(A6)		; Blitterzähler erhöhen
	BEQ.B	LB_6CE8				; (Muß sein, wegen fremder Blitts!)
	BSR.W	$002275A0			; Warten
LB_6CE8	TST.B	$0002(A4)		; DMA beendet ?
	BTST	#$06,$0002(A4)
	BEQ.B	LB_6CF8				; ja
	BSR.W	$00227FD4			; Warten
LB_6CF8	LEA	$0028(A1),A3		; Adresse der Minterme
LB_6CFC	MOVE.B	(A3)+,D2		; Minterm lesen
	MOVE.L	(A0)+,A4			; Erste Plane
	LSR.B	#1,D1				; Maskeninhalt prüfen
	BCC.B	LB_6D28				; Nicht zeichnen!
	TST.B	D2					; Minterm positiv ?
	BPL.B	LB_6D16				; ja
	ADD.B	D2,D2				; Minterm *2
	BPL.B	LB_6D10				; immer noch positiv...
	BSET	D0,$00(A4,A5.L)		; Bit setzen
LB_6D10	DBF	D3,LB_6CFC			; Nächste Plane
	BRA.B	LB_6D2C				; Ende
LB_6D16	ADD.B	D2,D2			; Minterm *2
	BMI.B	LB_6D24				; Negativ...
	BCLR	D0,$00(A4,A5.L)		; Bit löschen
	DBF	D3,LB_6CFC				; Nächste Plane
	BRA.B	LB_6D2C				; Ende
LB_6D24	BCHG	D0,$00(A4,A5.L)	; Bit invertieren
LB_6D28	DBF	D3,LB_6CFC			; Nächste Plane....
LB_6D2C


Wahrhaftig ist die Routine erheblich schneller, es lohnt sich also!
Da die Routine OS2.0-Kompatibel ist, könnte man praktisch diese Routine in
OS2.0 reinpatchen!

NEUE BITMAP-FORMATE

Ganz wichtig zu wissen: Nur wer wirklich systemkonform programmiert hat,
kann sichergehen, daß sein Programm auch auf OS 3.0 läuft. Das neue
Kickstart-Flaggschiff straft schlechtes Programmieren noch mehr als OS 2.0!

Oft wird nämlich "BytesPerRow" innerhalb der Bitmap-Struktur als die Breite
der Bitplane interpretiert. Das stimmt aber schon bei Interlacebildern
nicht mehr. Sehen wir uns aber mal die Struktur der Bitmap unter OS 3.0 an:

;
;	BitMap
;
    dc.w	XXXX		;	bm_BytesPerRow/Bytes pro Zeile
    dc.w	XXXX		;   bm_Rows	/ Zeilen
    dc.b    XX			;   bm_Flags
    dc.b    XX			;   bm_Depth/ Anzahl Planes
    dc.w	XXXX		;   Noch(!) unbenutzt
    blk.l	8,0			;   Acht Zeiger auf Bitplanes

Die Flags bei bm_Flags haben folgenden Aufbau:

CLEAR			Bit 0
DISPLAYABLE		Bit 1
INTERLEAVED		Bit 2
STANDARD		Bit 3
MINPLANES		Bit 4

Neu ist beispielsweise bei OS 3.0 "Interleaved". Das bedeutet, die Planes
sind folgendermaßen aufgebaut:

Zu erst alle Zeilen 0 ALLER Bitplanes
Dann alle Zeilen 1 ALLER Bitplanes
usw....

In diesem Zusammenhang ist der richtige Umgang mit "BytesPerRow" noch
wichtiger wie bei OS 2.0. Die offizielle Definition lautet nicht, wie der
Name eigentlich sagt, "Bytes pro Zeile", sondern:

BytesPerRow ist die Anzahl Bytes, die zum aktuellen Zeiger hinzuaddiert
werden muß, um exakt eine Zeile weiter an dem selben X-Offset zu landen.

Bei 5 Bitplanes Lowres (320 Puntke) INTERLEAVE werden Sie also als
BytesPerRow nicht 40, sondern 200 finden.......

Seit OS 2.0 muß außerdem ein Bitplanepointer auch nicht unbedingt auf eine
echte Bitplane zeigen. Denn: Eine 0 bedeutet, daß von einer komplett mit
Nullen gefüllten Plane ausgegangen werden muß. Ein -1 ($FFFFFFFF) bedeutet,
daß von einer komplett gefüllten Plane ausgegangen werden muß. Das spart in
vielen Situationen sehr wertvolles ChipRAM!

Für BltBitmap() (Offset -30) gilt also:

a0	= Zeiger auf Quellbitmap-Struktur
a1	= Zeiger auf Zielbitmap-Struktur
d0	= X-Offset Quellbitmap
d1	= Y-Offset Quellbitmap
d2	= X-Offset Zielbitmap
d3	= Y-Offset ZielBitmap
d4	= Breite des Blits in Pixel
d5	= Höhe des Blits in Zeilen
d6	= Minterm
d7	= Maske

Ergebnis: Anzahl geblitteter Planes (d0)

Dabei gilt wie schon gesagt: Enthält ein Bitplanezeiger eine 0, so wird das
behandelt, als handelt es sich um eine leere Plane, bei -1 jedoch um eine
volle Plane. Dieses Feature gibt es erst seit OS 2.0 !!


Um eine BitMap "mit allen Schikanen" zu kreieren, muß man unter OS 2.0 noch
folgenden Weg gehen:

1. Mit "InitBitMap" eine BitMapstruktur initialisieren
2. Mit AllocRaster die Planes anfordern und in die
   Struktur schreiben.

Seit OS 3.0 gibts eine Abkürzung:

AllocBitMap

d0	Breite
d1  Höhe
d2  Anzahl Planes
d3  Bitmap-Flags
a0  "Friend_Bitmap"

Dabei bedeutet die "Friend_Bitmap" ein Zeiger auf eine andere Bitmap. Das
System versucht dann die Bitplane so zu konstruieren, daß ein eventuelles
Blitten zu dieser "Friend_BitMap" möglichst effizient ist.

Kommen wir nun zu den wichtigsten Neuerungen der "graphics.library". Leider
reicht in unserem Büchlein nicht der Platz für alles, aber das wesentliche
fehlt nicht!


FreeBitMap

a0 = BitMap

Entfernt eine BitMap, die mit AllocBitMap angefordert wurde und gibt auch
das Chipram der Planes wieder frei. Vorsicht: Erst prüfen, ob der Blitter
nicht gerade in diese BitMap blittet!


GetAPen

a0	= Rastport

Ergebnis: d0 = Vordergrund-Farbstift

Ermittelt den Vordergrundfarbstift des Rastports. Das könnte zwar auch
dadurch geschehen, daß man das entsprechnde Byte im Rastport ausliest, aber
in Zukunft wird das Byte für den Vordergrundfarbstift nicht mehr reichen
und ein größerer Wert wird existieren. Diese Routine liest den APen also
zukunftskompatibel!


GetBPen

a0 = Rastport

Ergebnis: d0 = Hintergrundfarbstift.

Die GetAPen, jedoch für den Hintergrundfarbstift "BPen". Es gilt das
entsprechend gesagte...



GetDrMd

a0 = Rastport

Ergebnis: d0 = Zeichenmodus

Ermittelt den Zeichenmodus des Rastports. Auch hier gilt das gleiche wie
bei den beiden letzten Routinen.



GetRGB32

a0 = Colormap
d0 = Erste Farbnummer
d1 = Anzahl Farben
a1 = zu füllende Tabelle

Diese Routine ähnelt GetRGB4, ist jedoch für die neuen Farbmodi des
AA-Chipsets gedacht und besitzt auch erweiterte Features. Für jeden
Farbanteil (!) ist dabei ein Langwort vorgesehen! Volles Gelb wird vom
Betriebssystem also als $FFFFFFFF $FFFFFFFF $00000000 verwaltet!

Nach A0 kommt der Zeiger auf die entsprechende Colormap-Structur.
d0 entspricht der ersten Farbnummer, zur Zeit also 0-255 bei AA.
d1 wiederum möchte die Anzahl der Farben wissen. Sie müssen Platz oder
Speicher für eine Tabelle beschaffen, die von dieser Routine aufgefüllt
wird. Pro Farbe müssen Sie demnach 3 Langworte freihalten. Funktioniert
auch ohne AA-Chips!


LoadRGB32

A0 = Viewport
A1 = Farbtabelle

Setzt mehrere Farben im RGB32-Format auf einmal. Die Tabelle muß dabei
folgendes Format haben:

1 Wort mit der Anzahl der Farben
1 Wort mit der Nummer der ersten Farbe
Pro Farbe wie gehabt drei Langworte
0 für das Ende der Liste oder:
1 Wort für eine weitere Anzahl Farben
1 Wort für eine neue Startnummer
Pro Farbe wieder drei Langworte
0 für das Ende der Liste oder.....

Sie können also im Prinzip das ganze Farbspektrum setzen und dazwischen
sogar ein paar Farben auslassen bzw. überspringen. Wenn das kein Service
ist!


SetRGB32

a0 = ViewPort
d0 = Farbnummer
d1 = Rot
d2 = Grün
d3 = Blau

Setzt eine einzelne Farbe im RGB32 Format. Nicht vergessen: Für Weiß
beispielsweise : $FFFFFFFF,$FFFFFFFF,$FFFFFFFF


SetWriteMask

a0 = Rastport
d0 = Maske

Ergebnis: 0, wenn nicht möglich, bzw. wenn das Grafikdevice dies nicht
unterstützt (zukunftskompatibel zu RTG!).

Setzt eine neue Schreibmaske im Rastport. In etwa gilt wieder das bei
SetAPen etc. gesagte...




Dies war nur ein kleiner Auszug der neuen Routinen der "graphics.library".
Sie funktionieren prinzipiell auch ohne AA-Chips. Bei den Farben genügen
dann aber natürlich auch weiterhin die RGB4-Varianten.


KAPITEL 4

Blitter speziell und AA-Chipset

Die Grafik des Amiga ist intern nach dem Bitplane-Verfahren aufgebaut, was
eigentlich eine Besonderheit des Amigas ist. Wie kam es dazu? Nun, zur Zeit
der Entwicklung des Amiga war RAM-Speicher noch relativ teuer. Sie wissen
sicher, daß das erste Amigamodell (die erste Serie des Amiga 1000) nur
256KB Speicher hatte.

Wegen dieser Knappheit mußte eine Grafikdarstellungsart entwickelt werden,
die möglichst wenig Speicher verbrauchte. Man suchte ein Verfahren, daß bei
5 Bit Farbtiefe auch wirklich nur 5 Bits pro Farbe braucht, bei 3 Bit
Farbtiefe nur 3 Bits und so weiter. Die einzige Möglichkeit, dies zu
realisieren, sind die Bitplanes. Wie sind diese nun aufgebaut? Eigentlich
ganz einfach: Stellen Sie sich eine Grafik mit einer Auflösung von 320*256
Punkten vor. Da jeder Punkt einem Bit entspricht, benötigen wir hierfür
40*256 Bytes, das sind extakt 10 KB. Der Haken: Da jedes Bit nur den
Zustand 0 und 1 annehmen kann, haben wir auch entsprechend nur zwei Farben
zur Verfügung. Um nun 4 Farben darzustellen, werden nun einfach quasi zwei
Grafiken zu 320*256 Punkten erzeugt: Dadurch haben wir nun zwei Bits pro
Farbe zur Verfügung. Jede solche Teilgrafik wird "Bitplane" genannt. Bei
den bisherigen Amigas waren maximal 6 Bitplanes möglich, was theoretisch 64
Farben ergab. Spezielle Hardwaretricks ermöglichten aber mehr: Der
HAM-Modus läßt 4096 in einem Bild zu. Da nur 32 Farbregister existieren,
werden 6 Bitplanes je nach Modus in zwei verschiedenen Arten interpretiert.

Beim sogenannten Extra Halfbright Modus (Besonderer Halbhell-Modus)
bestimmt die 6.Bitplane die Helligkeit: Während die ersten 5 Planes auf
eines der 32 Farbregister zeigen, schaltet die 6.Bitplane die betroffene
Farbe auf halbe Helligkeit. Beispiel: Würde die Farbe 16 als volles Rot
($FFF) dargestellt, so würde die 6.Bitplane diesen Farbton auf $777
reduzieren (es wird abgerundet!), sofern das entsprechende Bit dort gesetzt
ist.

Beim HAM-Modus wird es um einiges komplizierter. Schließlich "muß" es die
Hardware hier schaffen, theoretisch sämtliche 4096 Farben in einem Bild
unterzubringen, bei nur 6 Bitplanes. Wo ist der Haken? Der HAM-Modus
arbeitet folgendermaßen:

Die ersten 4 Planes dienen wie bisher als Zeiger auf ein Farbregister von
0-15, wenn das entsprechende Bit der Bitplanes 5 und 6 auf 0 ist. Wir haben
in diesem Fall also 16 feste Farben.

Ist bei einem Pixel jedoch das Bit in Plane 5 gesetzt, geschieht folgendes:

Der Rote und Grüne Anteil des LINKEN BENACHBARTEN Pixels wird unverändert
übernommen. Blau wird durch den Wert ersetzt, den die ersten vier Planes
angeben. Würden diese beispielsweise zusammen den Wert 0 enthalten, so
würde BLAU einzeln auf 0 gesetzt.

Ist bei einem Pixel das Bit in Plane 6 gesetzt, geschieht das gleiche mit
Rotanzeil. Grün und Blau werden also unverändert übernommen und der rote
Anzeil modifiziert.

Sind die Bits in Plane 5 UND 6 gesetzt, wird der Grünwert verändert,
während Rot und Blau unverändert bleiben. 

Von diesem "Halten und Modifizieren" der Farben hat der Modus seinen Namen:
HAM bedeutet "Hold And Modify".
Übrigens: Wenn versucht wird, den ersten Pixel einer Zeile zu manipulieren,
ist ja eigentlich gar kein linker benachbarter Pixel da. In diesem Falle
wird die Hintergrundfarbe als "linker Pixel" interpretiert.

Sie haben sicher die Einschränkung des HAM-Modus bemerkt: Wir können zwar
schön viel Farben darstellen, aber um einen kompletten Farbwert zu ändern,
benötigen wir 3 Pixel. Die "Farbwechselauflösung" beträgt demnach 3 Punkte.
Sicher fällt auch auf, daß es gar nicht so einfach ist, ein Malprogramm für
diesen Modus zu schreiben.

Der HAM- und EHB Modus funktioniert leider nur im Lowres-Modus, also bei
der niedrigsten Auflösung, da bei Hires nur maximal 4 Bitplanes möglich
sind.

AA-CHIPSET

Sind Sie Besitzer eines Amiga 1200 oder Amiga 4000, haben Sie da schon
erheblich mehr Glück. Die maximale Anzahl der Bitplanes beträgt hier 8,
d.h. in den Normalmodi sind bis zu 256 Farben möglich. Das besondere: Die
bisherigen Einschränkungen fallen weg: Egal, welche Auflösung Sie wählen,
auch bei Hires und Superhires dürfen Sie bis zu 8 Bitplanes benutzen. Aber
seien Sie gewarnt: 8 Bitplanes in Superhires belasten die DMA sehr stark!

In diesem Falle gleich ein wichtiger Tip für Programmierer: Gewöhnen Sie
sich einfach (pauschal) an, daß alle Bitplanes an einer durch 8 teilbaren
Adresse beginnen!!! Nur dann ist sichergestellt, daß alle Grafikmodi
einwandfrei funktionieren.

Doch das AA-Chipset begnügt sich nicht nur mit mehr Bitplanes: Auch die
Farb- und Spritefähigkeiten haben sich kräftig gesteigert. Die
Gesamtpalette beträgt nun nicht mehr 4096, sondern über 16 Millionen
Farben. Exakt ausgedrückt: Statt bisher 4 Bits pro Farbanteil stehen nun 8
Bits pro Farbanzeil zur Verfügung. Und diese 256*256*256 Kombinationen
ergeben diese 16 Millionen.

Das AA-Chipset bietet auch einen neuen HAM-Modus an, der statt mit den
bisherigen 6 Planes mit 8 Bitplanes arbeitet. 
Die ersten 6 Planes dienen wieder als Zeiger auf die Stammpalette, sie hat
sich also auf 64 erhöht. Statt 16 aus 4096 stehen demnach 64 aus 16
Millionen Stammfarben zur Verfügung. Dieser neue HAM-Modus wird auch HAM8
genannt.
Die Planes 7 und 8 steuern wieder wie gehabt das Halten und Modifizieren
der Farben. Doch halt! Wenn zur Manipulation der Farbe nur 6 Bits zur
Verfügung stehen, wie soll dann auf 16 Millionen Farben zugegriffen werden?
Im Prinzip einfach: Es geht eben nur in Viererschritten. Wir müssen also
"geistig" die Farbe mit 4 multiplizieren, um den echten Farbwert zu
erhalten. Bei optimaler Auswahl der Stammpalette lassen sich so bis zu
1280000 Farben in einem Bild darstellen, je nach Auflösung. Einleuchtend:
Je höher die Auflösung, desto mehr Pixelmanipulationen sind möglich. Und
HAM8 ist ja ebenfalls in allen Auflösungen erlaubt!

Kommen wir nun zu den benötigten Registern (für AA).

BPLCON0	$DFF100

Bit		Funktion

15		Normaler Hires-Modus
14-12	Anzahl Bitplanes		(7 nur bei AA-CHIPS)
11		HAM-Modus				( Bei 8 Planes = HAM8 bei AA-Chips)
10		Dualplayfield
9		Colorburst
8		Genlock-Audio
7		-
6		Superhires				Ab ECS und natürlich AA
5		-
4		1 = 8 Bitplanes (14-12 dann unbenutzt) Nur AA-Chips!
3		Lightpen-Eingang
2		Interlace
1		Externes Sync (z.B. Genlock)
0		Einschalten von BPLCON3	(ab ECS)


DIE FARBEN

Die Farbregister des Amiga liegen ab $DFF180 und sind amigatypisch ein Wort
groß. Die oberen 4 Bits sind hierbei unbenutzt und die unteren 12 Bits
geben die jeweilige Farbe im RGB-Format an. Der Gag an der Sache: Auch beim
AA-Chipset ist das nicht anders. Wie aber um alles in der Welt sind
trotzdem 256 Farben möglich, noch dazu mit 8 Bits pro Farbanteil ?
Jetzt lüften wir das Geheimnis:

Die AA-Farbmodis

Um in ein Farbregister, z.B. $DFF180, eine 24Bit-Farbe zu schreiben,
benutzen diese Register eine Art "Bankswitching". Man schreibt also zuerst
die 4 oberen Bits pro Farbanteil, schaltet dann die Bank um
und schreibt nun die unteren vier Bits. Dabei muß die entsprechende
Reihenfolge eingehalten werden.
Welche Bank wir nun beschreiben, bestimmt das BPLCON3-Register, welches wir
durch Setzen von Bit 0 im BPLCON0-Register eingeschaltet haben.
Zuständig ist das Bit 9 dieses Registers:

Beispiel: Farbe $FD1455 im Register $DFF180

move.w	#$0000,$dff106		; Schreiben der oberen 4 Bits
move.w	#$0F15,$dff180		; Obere 4 Bits der Farbe
move.w	#$0200,$dff106		; Schreiben der unteren 4 Bits
move.w	#$0D45,$dff180		; Untere 4 Bits der Farbe

Ist das Bit also 0, werden die "Highbits", ist es 1, die "Lowbits"
geschrieben.

Natürlich wird in der Regel hierfür der Copper verwendet, und nicht der
Prozessor. Sie merken sicher auch, daß der Copper hier wesentlich mehr zu
tun hat. Das ist auch der Grund, warum beim Ziehen von hochauflösenden
AA-Screens unter Intuition der berüchtigte schwarze "Trennbalken" zwischen
den Screen viel dicker ist als bei den "alten" Amigas gewohnt!

Wichtig: Jedesmal, wenn die Copperliste neu startet (COPJMP), wird das Bit
automatisch auf Null gesetzt! Beachten!

Die zweite Frage, die offen bleibt: Wenn ich nur 32 Farbregister habe, wie
soll ich dann 256 Farben setzen?
Auch dies wird wieder durch Bankswitching gelöst. Sie sehen: Das war die
rettende Idee, zu den alten Farbregistern kompatibel zu bleiben!

Es werden 8 Bänke mit je 32 Farbregistern verwaltet, was die gewünschten
256 Farben ergibt. Auch hier ist wieder BPLCON3 (DFF106) zuständig, diesmal
die Bits 12 bis 14. Eine Tabelle erklärt es am besten!

Bit 14	13	12	Palette:

	0	0	0	Farben 0-31
	0	0	1	Farben 32-63
	0	1	0	Farben 64-95
	0	1	1	Farben 96-125
	1	0	0	Farben 126-159
	1	0	1	Farben 160-191
	1	1	0	Farben 192-223
	1	1	1	Farben 224-255


NEUE SPRITEMODI

Sprites sind bei den AA-Chips nicht mehr auf Lores beschränkt. Bit 7 und 6
des BPLCON3-Registers (dff106) bestimmen die verwendete Auflösung:

Bit	7	6	Auflösung

	0	0	Lowres
	1	0	Hires
	0	1	Lowres
	1	1	SuperHires


Neue Spritegrößen:

Nur Hires- und Superhires-Sprites alleine wären nicht reizvoll, schließlich
sind die Sprites eh schon klein genug. Zuständig ist das Register $DFF1FC,
und dort wiederum die Bits 2 und 3.

Bit	3	2	Breite

	0	0	16
	1	0	32
	0	1	32
	1	1	64


Beachten Sie: Entsprechend ändert sich auch die Handhabung der Datenliste.
Bei 16Pixel-Sprites bleibt alles beim Alten.

Datenformat 32Pixel-Sprites:

2 Langworte als Controlldaten, statt bisher Worte
Spritedaten im Langwortformat.

Das erste Langwort:

Das erste Wort ist wie zu erwarten das Highword. Das zweite Wort enthält
das entsprechende zweite Kontrollwort.

Das zweite Langwort:

Das Highword entspricht dem zweiten Kontrollwort des ersten Langwortes. Das
Lowword wird auf 0 gesetzt.

Datenformat 64Pixel-Sprites:

Diesmal sind es gleich zwei Doppel-Langworte, die als Controllwerte dienen.
Danach folgen die Doppellangworte für die Daten.

Das erste Doppellangwort:

Erstes Kontrollwort,Zweites Kontrollwort, Zweites Kontrollwort, 0

Das zweite Doppellangwort:

Zweites Kontrollwort,Erstes Kontrollwort,Erstes Kontrollwort,0 


Die Spritefarben

Bisher begann die Spritefarbtabelle fix ab Farbe 16. Bei den AA-Chips
dagegen ist sie frei wählbar. Hierzu dienen die Bits 4-7 des Registers
$DFF10C. Bei der Datenmenge hilft natürlich wieder am besten eine Tabelle:

Bit	7	6	5	4	Spritefarben ab...

	0	0	0	0	$DFF180	Farbbank 0
	0	0	0	1	$DFF1A0	Farbbank 0
	0	0	1	0	$DFF180	Farbbank 1
	0	0	1	1	$DFF1A0	Farbbank 1
	0	1	0	0	$DFF180	Farbbank 2
	0	1	0	1	$DFF1A0	Farbbank 2
	0	1	1	0	$DFF180	Farbbank 3
	0	1	1	1	$DFF1A0	Farbbank 3
	1	0	0	0	$DFF180	Farbbank 4
	1	0	0	1	$DFF1A0	Farbbank 4
	1	0	1	0	$DFF180	Farbbank 5
	1	0	1	1	$DFF1A0	Farbbank 5
	1	1	0	0	$DFF180	Farbbank 6
	1	1	0	1	$DFF1A0	Farbbank 6
	1	1	1	0	$DFF180	Farbbank 7
	1	1	1	1	$DFF1A0	Farbbank 7


4.1.2.) Der Blitter

Zunächst zu einer neuen Registerbelegung: Ab dem ECS-Chipsatz lassen sich
Bereiche bis zu 32768*32768 blitten. Da das alte BLTSIZE-Register hierfür
aber nicht mehr ausreicht, ist ein neues hinzugekommen, daß diesmal zwei
Register belegt:

BLTSIZEV	$DFF05C	Anzahl Zeilen
BLTSIZEH	$DFF05E	Anzahl Worte/Zeile (11Bit)

Exakt diese Reihenfolge muß eingehalten werden, da BLTSIZEH den Blitter
startet. Doch nun zum Blitter selbst. Viele Programmierer haben erhebliche
Probleme mit den sogenannten Mintermen. Diese Boolschen Gleichungen
bestimmen die Blitteroperation. Sehen wir uns der Vollständigkeit halber
diese Terme und das entsprechende Register nochmal an:

BLTCON0 $DFF040, Bits 7 bis 0

7	6	5	4	3	2	1	0
ABC	ABc	AbC	Abc	aBC	aBc	abC	abc

Als Beispiel sei gegeben:

Quelle A: Maske, welche festlegt, was geblittet werden soll.
Quelle B: Die zu blittenden Daten
Quelle C: Das Ziel selbst.

Ergebnis ist wiederum Ziel, auch D genannt.

Bei dieser Kombination bewirkt der Minterm $C0 ein einfaches Kopieren.
$30	invertiert unsere Daten erst, bevor sie ins Ziel gelangen. $50
schließlich bewirkt mit der Quelle gar nichts. Lediglich das Ziel wird
invertiert. (Sie vermuten richtig, daß in diesem Falle der Inhalt von
Quelle B egal ist).

Ich stelle Ihnen nun ein Assemblerprogramm vor, daß einen Blitt anhand
dieser Minterme simuliert. Dabei enthält das Register D0 die Maske, D1 die
Quelle und D2 das Ziel. Das Blittergebnis landet schließlich im Register
D7. Das Mintermbyte übergeben wir in D6. Experimentieren Sie mit diesem
Progrämmchen, dann werden Sie diese Terme sicher schnell verstehen:

;
; Minterm-Simulation in Assembler
;

	moveq	#-1,d0			; Maske
	move.l	#$89abcdef,d1	; Quelle als Beispiel
	move.l	#$55555555,d2	; Noch-Inhalt des Ziels
	move.b	#$c0,d6			; Minterm
	moveq	#0,d7			; Hier kommt das Ergebnis hin
	lsr.b	#1,d6			; Mintermbit 0 ?
	bcc.B	nicht0			; nein

;
;	Minterm abc berechnen
;

	move.l	d0,d3
	move.l	d1,d5
	move.l	d2,d6
	and.l	d3,d5
	and.l	d5,d6
	not.l	d6
	or.l	d6,d7
nicht0:
	lsr.b	#1,d6			; Mintermbit 1 ?
	bcc.B	nicht1			; Nein

;
;	Minterm abC	berechnen
;

	move.l	d0,d3
	move.l	d1,d5
	move.l	d2,d6
	not.l	d3
	not.l	d5
	and.l	d3,d5
	and.l	d5,d6
	or.l	d6,d7
nicht1:
	lsr.b	#1,d6			; Mintermbit 2 ?
	bcc.B	nicht2			; Nein
;
;	Minterm aBc berechnen
;

	move.l	d0,d3
	move.l	d1,d5
	move.l	d2,d6
	not.l	d3
	not.l	d6
	and.l	d3,d5
	and.l	d5,d6
	or.l	d6,d7
nicht2:
	lsr.b	#1,d6			; Mintermbit 3 ?
	bcc.B	nicht3
;
;	Minterm aBC berechnen
;

	move.l	d0,d3
	move.l	d1,d5
	move.l	d2,d6
	not.l	d3
	and.l	d3,d5
	and.l	d5,d6
	or.l	d6,d7
nicht3:
	lsr.b	#1,d6			; Mintermbit 4 ?
	bcc.B	nicht4
;
;	Minterm Abc berechnen
;

	move.l	d0,d3
	move.l	d1,d5
	move.l	d2,d6
	not.l	d5
	not.l	d6
	and.l	d3,d5
	and.l	d5,d6
	or.l	d6,d7
nicht4:
	lsr.b	#1,d6			; Mintermbit 5 ?
	bcc.B	nicht5

;
;	Minterm AbC berechnen
;

	move.l	d0,d3
	move.l	d1,d5
	move.l	d2,d6
	not.l	d5
	and.l	d3,d5
	and.l	d5,d6
	or.l	d6,d7
nicht5:
	lsr.b	#1,d6			; Mintermbit 6 ?
	bcc.B	nicht6
;
;	Mintermbit ABc berechnen
;
	move.l	d0,d3
	move.l	d1,d5
	move.l	d2,d6
	not.l	d6
	and.l	d3,d5
	and.l	d5,d6
	or.l	d6,d7
nicht6:
	lsr.b	#1,d6			; Mintermbit 7 ?
	bcc.B	nicht7
;
;	Mintermbit ABC	berechnen
;
	move.l	d0,d3
	move.l	d1,d5
	move.l	d2,d6
	and.l	d3,d5
	and.l	d5,d6
	or.l	d6,d7
nicht7:

Damit haben Sie in d7 das Ergebnis, das auch der Blitter gehabt hätte.


DAS LINIENZIEHEN

Der Blitter benutzt zum Zeichnen einer Linie den Bresenham-Algorithmus,
einer der schnellsten Verfahren der Welt. Unverständlicherweise haben viele
Programmierer Probleme, dieses Prinzip zu verstehen. Deshalb zeige ich
Ihnen hier einmal eine Variante mit dem Prozessor. Ich habe sie absichtlich
etwas umständlich programmiert, um das Verständnis zu erhöhen. In
Wirklichkeit läßt sich der Bresenham-Algorithmus noch viel kompakter und
effizienter schreiben. Optimieren Sie doch mal dieses Programm!
Streng genommen paßt es hier zwar nicht zum Thema Blitter, aber die
Verständlichkeit des Blitter-Linien-Ziehens wird erhöht!

Der Bresenham-Algorithmus vermeidet zeitraubende Orgien mit Sinus/Cosinus
und ähnlichem Schnickschnack. Noch heute werden oft Linien auf lahme Weise
programmiert. Eigentlich unverständlich. Dabei ist "Bresenham" so einfach:
Es kommt nur mit Auf- bzw. Abwärtszählen aus. Das hier vorgestellte
Programm benutzt der Einfachheit halber zum Zeichnen der jeweiligen Pixel
die Routine "WritePixel" der ",graphics.library", die als Ziel nicht die
Bitplanes, sondern den Rastport erwartet. Vorteil: Selbst Clipping wird
berücksichtigt. Ist der Rastport also z.B. teilweise von einem anderen
Rastport (z.B. Window) überdeckt, wird dort nicht hineingezeichnet, sondern
in den gepufferten verdeckten Bereich.
Als Bonus unterstützt die Routine sogar die Linienmuster. Aber wie gesagt:
Natürlich gibt es Unmengen schnellerer Varianten.

* 
* Linien mit Bresenham, unterstützt Linepatterns
*
* Code: Manfred Leidorf
* Hinweis: Umständlich programmiert zwecks 
* besserem Verständnis
*
* Die Routine holt sich die Startkoordinaten aus
* dem Rastport und ist fast 100% funktionskompatibel
* zur Betriebssystem-Routine "Draw"
*
* a1: Zielrastport
* d0: ZielX
* d1: ZielY
*
* Falls Sie keine Includes haben:
*

_LVOOpenLibrary	=	-552
_LVOMove		=	-240
_LVOWritePixel	=	-324

	move.l	4.W,a6
	lea	gfxname(pc),a1
	moveq	#0,d0
	jsr	_LVOOpenLibrary(a6)
	move.l	d0,gfxbase

	movem.l	d2-d7/a0-a6,-(a7)	; Register retten
	move.l	gfxbase(pc),a6
	move.w	d0,d2			; ZielX
	move.w	d1,d3			; ZielY
	move.w	36(a1),d0		; StartX (Start X im Rastport)
	move.w	38(a1),d1		; StartY (Start Y im Rastport)

	movem.l	d0-d1/a1,-(a7)
	move.w	d2,d0
	move.w	d3,d1
	jsr	_LVOMove(a6)		; Grafikcursor setzen
	movem.l	(a7)+,d0-d1/a1
	
*--------------------------------------------------------------------------
*	DeltaX und DeltaY bilden
*--------------------------------------------------------------------------
	move.w	d3,d7			; ZielY nach d7
	move.w	d2,d6			; ZielX nach d6
	sub.w	d0,d6			; DeltaX
	bpl.B	posi1
	neg.w	d6			; Eventuell positiv machen
posi1:
	sub.w	d1,d7			; DeltaY
	bpl.B	posi2
	neg.w	d7			; Eventuell positiv machen
posi2:
*--------------------------------------------------------------------------
	cmp.w	d0,d2			; X1 < X2 ?
	bgt.B	oktant0167		; ja
*--------------------------------------------------------------------------
*	X1 war größer als X2
*--------------------------------------------------------------------------
oktant2345:
	cmp.w	d1,d3			; Y1 < Y2 ?
	bgt.B	oktant23		; Dann Oktand 2 oder 3
oktant45:
	cmp.w	d6,d7			; d6:   DeltaX       d7:   DeltaY
	bgt.B	oktant5
	bra.B	oktant4
oktant23:
	cmp.w	d6,d7			; d6:   DeltaX       d7:   DeltaY
	bgt.B	oktant2
	bra.B	oktant3
*--------------------------------------------------------------------------
*	X2 war größer als X1
*--------------------------------------------------------------------------
oktant0167:
	cmp.w	d1,d3
	bgt.B	oktant01		; Dann Oktand 0 oder 1
oktant67:
	cmp.w	d6,d7			; d6:   DeltaX       d7:   DeltaY
	bgt.B	oktant6
	bra.B	oktant7
oktant01:
	cmp.w	d6,d7			; d6:   DeltaX       d7:   DeltaY
	bgt.B	oktant1
	bra.W	oktant0
*----------------------------------------------------------------------------
*	Einsprünge eintragen
*----------------------------------------------------------------------------
oktant0:
	lea	oktant0routine(pc),a3
	bra.B	variablen
oktant1:
	lea	oktant1routine(pc),a3
	bra.B	variablen
oktant2:
	lea	oktant2routine(pc),a3
	bra.B	variablen
oktant3:
	lea	oktant3routine(pc),a3
	bra.B	variablen
oktant4:
	lea	oktant4routine(pc),a3
	bra.B	variablen
oktant5:
	lea	oktant5routine(pc),a3
	bra.B	variablen
oktant6:
	lea	oktant6routine(pc),a3
	bra.B	variablen
oktant7:
	lea	oktant7routine(pc),a3
*----------------------------------------------------------------------------
*	Sondervariablen berechnen
*----------------------------------------------------------------------------
variablen:
	cmp.w	d6,d7
	bgt.B	kleindelta6			; X-Differenz ist Kleindelta
	move.w	d7,d5				; Kleindelta nach d5
	move.w	d6,d4				; Großdelta nach d4
	bra.B	arithmetisch		; Zu den Berechnungen....
kleindelta6:
	move.w	d6,d5				; Kleindelta nach d5
	move.w	d7,d4				; Großdelta nach d4
*----------------------------------------------------------------------------
*	Arithmetische Berechnungen
*----------------------------------------------------------------------------
arithmetisch:
	add.w	d5,d5				; 2 x Kdelta
	move.w	d4,d6				; GDelta für Schleife nach d6
	move.w	d4,d7				; GDelta nochmal nach d7
	add.w	d4,d4				; 2 x GDelta
*----------------------------------------------------------------------------
*	Jetzt kann es eigentlich losgehen........
*----------------------------------------------------------------------------
	move.w	34(a1),d2			; Linienmuster aus Rastport holen
	
	jmp	(a3)					; Einspringen...

	nop
	
oktant0routine:
	rol.w	#1,d2
	bcc.B	muster0
	movem.l	d0-d1,-(a7)
	jsr	_LVOWritePixel(a6)
	movem.l	(a7)+,d0-d1
	
muster0:
	addq.w	#1,d0			; X+1
	sub.w	d5,d7
	bpl.B	noch_positiv
	addq.w	#1,d1			; Y+1
	add.w	d4,d7
noch_positiv:
	dbf	d6,oktant0routine
	bra.W	line_finito
	
oktant1routine:
	rol.w	d2
	bcc.B	muster1
	movem.l	d0-d1,-(a7)
	jsr	_LVOWritePixel(a6)
	movem.l	(a7)+,d0-d1
muster1:
	addq.w	#1,d1			; Y+1
	sub.w	d5,d7
	bpl.B	noch_positiv3
	addq.w	#1,d0			; X+1
	add.w	d4,d7
noch_positiv3:
	dbf	d6,oktant1routine
	bra.W	line_finito	
oktant2routine:
	rol.w	#1,d2
	bcc.B	muster2
	movem.l	d0-d1,-(a7)
	jsr	_LVOWritePixel(a6)
	movem.l	(a7)+,d0-d1
muster2:
	addq.w	#1,d1			; Y+1
	sub.w	d5,d7
	bpl.B	noch_positiv6
	subq.w	#1,d0			; X-1
	add.w	d4,d7
noch_positiv6:
	dbf	d6,oktant2routine
	bra.W	line_finito

oktant3routine:
	rol.w	#1,d2
	bcc.B	muster3
	movem.l	d0-d1,-(a7)
	jsr	_LVOWritePixel(a6)
	movem.l	(a7)+,d0-d1
muster3:
	subq.w	#1,d0			; X-1
	sub.w	d5,d7
	bpl.B	noch_positiv8
	addq.w	#1,d1			; Y+1
	add.w	d4,d7
noch_positiv8:
	dbf	d6,oktant3routine
	bra.B	line_finito


oktant4routine:
	rol.w	#1,d2
	bcc.B	muster4
	movem.l	d0-d1,-(a7)
	jsr	_LVOWritePixel(a6)
	movem.l	(a7)+,d0-d1
muster4:
	subq.w	#1,d0			; X-1
	sub.w	d5,d7
	bpl.B	noch_positiv2
	subq.w	#1,d1			; Y-1
	add.w	d4,d7
noch_positiv2:
	dbf	d6,oktant4routine
	bra.B	line_finito
	
oktant5routine:
	rol.w	#1,d2
	bcc.B	muster5
	movem.l	d0-d1,-(a7)
	jsr	_LVOWritePixel(a6)
	movem.l	(a7)+,d0-d1
muster5:
	subq.w	#1,d1			; Y-1
	sub.w	d5,d7
	bpl.B	noch_positiv4
	subq.w	#1,d0			; X-1
	add.w	d4,d7
noch_positiv4:
	dbf	d6,oktant5routine
	bra.B	line_finito
	
oktant6routine:
	rol.w	#1,d2
	bcc.B	muster6
	movem.l	d0-d1,-(a7)
	jsr	_LVOWritePixel(a6)
	movem.l	(a7)+,d0-d1
muster6:
	subq.w	#1,d1			; Y-1
	sub.w	d5,d7
	bpl.B	noch_positiv5
	addq.w	#1,d0			; X+1
	add.w	d4,d7
noch_positiv5:
	dbf	d6,oktant6routine
	bra.B	line_finito
	
oktant7routine:
	rol.w	#1,d2
	bcc.B	muster7
	movem.l	d0-d1,-(a7)
	jsr	_LVOWritePixel(a6)
	movem.l	(a7)+,d0-d1
muster7:
	addq.w	#1,d0			; X+1
	sub.w	d5,d7
	bpl.B	noch_positiv7
	subq.w	#1,d1			; Y-1
	add.w	d4,d7
noch_positiv7:
	dbf	d6,oktant7routine

******************************************
*	Hier wären wir fertig...
******************************************
line_finito:


gfxname:
	dc.b	"graphics.library",0
	cnop	0,4
gfxbase:
	dc.l	0


Wie gehts beim Blitter ?

In einem recht populären Buch zum Amiga wird bei der Beschreibung des
Linemodus ein Fehler gemacht, der zu Linien führt, die einen Pixel zu kurz
sind. Dieser Patzer entstand, weil der betroffene Autor die Differenz von
Start und Ziel mit der Größe verwechselt hat. Um die richtige Größe zu
erhalten, muß man nach der Ermittlung der Differenz noch 1 addieren.
Logisch, oder ?

Wie schon die Prozessorversion, arbeitet auch der Blitter mit dem
Oktantensysem, da ja auch er den Bresenham-Algorithmus benutzt. Die
Oktanten sind beim Blitter so aufgeteilt:

	2	1
3			0

4			7
	5	6


Nennen wir die Koordinaten nun StartX, StartY, ZielX und ZielY.
Wie bestimmen wir nun den richtigen Oktanten? Relativ leicht (So macht es
auch das obige Programm!)

Gehen wir stufenweise vor:

1. Ist StartX kleiner als ZielX? Dann kommen nur die Oktanten 0,1,6,7 in
Frage, ansonsten 2,3,4,5. Sind StartX und ZielX gleich, sind wir an dieser
Stelle nicht schlauer, dann könnten es nämlich alle acht Oktanten sein.

2. Ist StartY kleiner als ZielY? Dann kommen nur die Oktanten 0,1,2,3 in
Frage, ansonsten 4,5,6,7. Bei Gleichheit gilt obiges Gesagte. Wir folgern
logisch: Sind StartX/ZielX  und StartY/ZielY gleich, so liegen wir exakt in
der Mitte und der Oktant ist egal. Die Linie wäre dann auch nur 1 Punkt!

3. Jetzt müssen wir ein wenig rechnen: Bilden Sie die Differenz der
X-Koordinaten. Das Ergebnis nennen wir DELTAX. Machen Sie das gleiche mit
den Y-Koordinaten. Das Ergebnis nennen wir DELTAY.

4. Ist DeltaX größer als DeltaY, so kommen nur die Oktanten 0,3,4,7 in
Frage, andernfalls nur 1,2,5,6.

Aus diesen bisherigen Erkenntnissen läßt sich der passende Oktant ermitteln.
Ungerechterweise müssen wir nun noch den Oktanten einen Code zuordnen.

Oktant:	Code:
0		6
1		1
2		3
3		7
4		5
5		2
6		0
7		4

Der Blitter hat die Möglichkeit, Linien mit einem 16Bit-großen Muster zu
zeichnen. Außerdem gibt es einen Modus der verhindert, daß in einer Zeile
mehr als 1 Pixel vorkommt. Für bestimmte Fälle ist das notwendig,
beispielsweise beim korrekten Flächenfüllen mit dem Blitter.

Bevor wir nun zu den Registern kommen, müssen wir aber noch ein wenig
rechnen! Wir haben vorhin zwei Werte berechnet, nämlich DeltaX und DeltaY.
Nehmen Sie das Delta mit dem kleineren Wert, das wir nun KleinDelta nennen
wollen. Das größere nennen wir GroßDelta.

1.) Merken Sie sich KleinDelta und verdoppeln Sie den Wert.

2.) Merken Sie sich auch dieses Ergebnis. Ziehen Sie nun Großdelta von diesem
Ergebnis ab.  Ist es negativ, merken wir uns, daß wir das SIGN-Flag auf 1
setzen müssen.

3.) Nehmen Sie wieder das verdoppelte KleinDelta und subtrahieren Sie davon
ein verdoppeltes GroßDelta.


a) Das Ergebnis der Rechnung bei 2.) kommt in das Register BLTAPTL.

b)In BLTCPT und BLTDPT kommt die Startadresse der Linie, genauer gesagt des
Wortes, in dem die Linie beginnt.

c) Das Ergebnis der Rechnung bei 3.) kommt in das Register BLTAMOD.

d) Das verdoppelte KleinDelta muß nach BLTBMOD

e) BLTCMOD und BLTDMOD müssen mit der Breite der Grafik in Bytes gefüttert
werden, in die die Linie gezeichnet wird.

f) BLTADAT bekommt den Wert $8000

g) BLTBDAT bekommt das Linienmuster!

h) BLTAFWM wird mit $FFFF initialisiert.

BLTSIZE startet den Blitter. Die Bits 0-5 werden fix mit 2 initialisiert,
während die Bits 6-15 die Länge der Linie enthalten müssen. Die Länge
entspricht dem Wert "GroßDelta"+1 !!

Register BLTCON0:

Beim normalen Linienziehen wird der Minterm $CA eingesetzt. Die benötigten
DMA-Bits sind 1011, und die oberen 4 Bits kennzeichnen den Startpunkt
innerhalb des ersten Wortes.

Register BLTCON1:

Die ersten vier Bit sind identisch zu BLTCON 0. Bits 11-7 werden gelöscht.
Bit 6 ist das SIGN-Flag. Wie Sie es behandeln müssen, habe ich bereits
erwähnt. Die Bits 4-2 enthalten den vorhin besprochenen Oktantencode.
Bit 1 bestimmt, ob nur 1 Pixel pro Zeile gezeichnet werden darf. Bit 0
aktiviert den Linienmodus, muß also gesetzt werden.


KAPITEL 5

Wir entwickeln eine eigene Library

Oft neigt man als Programmierer ganz schnell dazu, ständig Programmteile
immer wieder neu zu entwickeln, die eigentlich schon x-mal eingetippt
wurden. Ein guter Weg, von dieser Schwäche abzukehren, ist das Sammeln der
wichtigsten und immer wieder gebrauchten Unterroutinen. Hat man jedoch gar
mehrere Routinen zu einem bestimmten Thema angesammelt, so bietet sich eine
Library an. So braucht in Zukunft nur noch diese Library geöffnet zu
werden, und die Routinen lassen sich dann auch von mehreren Programmen
gleichzeitig benutzen, ohne diese ständig neu im Speicher zu haben. Sie
stellen sich das nun besonders schwierig vor? Keine Panik auf der Titanic,
es ist sogar sehr einfach.

An dieser Stelle sei nochmals betont, daß Sie sich die INCLUDE-Files für
Assembler besorgen sollten. Viele Händler bieten diese zum günstigen Preis
an, außerdem sind bei vielen kommerziellen Assemblern (z.B. MaxonASM,
O.M.A., ASMOne) diese Includes dabei. Ohne diese Includes ist das
Entwickeln einer eigenen Library wahrhaft schwierig.


Damit es nicht allzu kompliziert wird, wollen wir zur Probe einmal eine
kleine Library entwickeln. Diese soll folgende Funktionen haben:

1. Die wichtigsten Librarys sollen auf einen Schlag geöffnet und die
Zeiger darauf in einem Langwort-Array eingetragen werden.

2. Die entsprechende Routine, um diese Librarys wieder zu schließen.

3. Es soll ein Requester erscheinen, der "Sind Sie sich da sicher?"
abfragt. Dieser Requester soll automatisch auf dem vordersten Screen
erscheinen.

4. Es soll die Größe einer Datei festgestellt werden. Als Parameter
erwartet die Routine den Filenamen samt Pfad.


Grundregeln einer Library

1.)

Halten Sie die Register-Konventionen ein! Das heißt konkret: Die Register
d2-d7 und a2-a6 müssen nach Rückkehr wieder den alten Wert besitzen.

2.)

Ergebnisse sollten möglichst dem Register d0 übergeben werden, auch Zeiger!
Seltene Ausnahmen sind erlaubt.

3.) 

Bei Übergabeparametern sollten Sie möglichst die Register d0-d1 und
a0-a1 benutzen. Mehr Parameter sind aber im Notfall erlaubt.

4.) 

Die Routinen müssen re-entrant sein. Das bedeutet: Mehrere Programme
müssen die Routinen gleichzeitig durchlaufen können, ohne sich gegenseitig
dabei zu stören. Verboten sind demnach globale Variablen!

5.) War eine Aktion nicht erfolgreich, müssen alle verbrateten
Speicherbereiche und Librarys wieder freigegeben werden. Außerdem wird dann
0 dem Register d0 übergeben. (0=FALSE)

Die Librarybase

Nennen wir unsere kleine Library doch "spezial.library". Die "Mutter" einer
Library ist die Librarybase. Diese müssen wir erstellen und in der
Include-Schublade speichern. In unserem Beispiel taufen wir sie
"SpezialBase.i". Dabei steht das "i" für Include.


;
;  SpezialBase.i
;
;  Include-File für Leidorfs Spezial-Library
;  

        IFND    SPEZIAL_BASE_I
SPEZIAL_BASE_I   SET     1

        IFND EXEC_TYPES_I
        INCLUDE "exec/types.i"
        ENDC

        IFND EXEC_LISTS_I
        INCLUDE "exec/lists.i"
        ENDC

        IFND EXEC_LIBRARIES_I
        INCLUDE "exec/libraries.i"
        ENDC

        STRUCTURE SpezialBase,LIB_SIZE
        UBYTE   sb_Flags
        UBYTE   sb_pad
        ULONG   sb_SysLib
        ULONG   sb_DosLib
		ULONG	sb_IntuiLib
        ULONG   sb_SegList
        LABEL   SpezialBase_SIZEOF

SPEZIALNAME      MACRO
        DC.B    'spezial.library',0
        ENDM
        ENDC

;
;
;


Die hier gezeigte Librarybase ist nur die Mindestvorraussetzung. Vor dem
"LABEL "SpezialBase_SIZEOF" " können Sie noch Ihre eigenen Variablen und
Zeiger einsetzen, sofern das in Ihrem Falle nötig sein sollte. Die meisten
Librarys haben ja viel größere Basisstrukturen als diese hier. Dabei
bedeuten:

UBYTE	Vorzeichenloses Byte
UWORD	Vorzeichenloses Wort
ULONG	Vorzeichenloses Langwort
BYTE	Vorzeichenbehaftetes Byte
WORD	Vorzeichenbehaftetes Wort
LONG	Vorzeichenbehaftetes Langwort
APTR	Zeiger

Das Kommando "LABEL SpezialBase_SIZEOF" läßt den Assembler das Ende der
Struktur erkennen.

Nachdem wir diese "SpezialBase.i" in der Include-Schublade gespeichert
haben, sollten wir uns noch auf die Funktionsoffsets einigen.
Übrigens können Sie auch noch andere eigene Strukturen Ihrer eigenen
Library in der gleichen Datei definieren. Schauen Sie sich ruhig einmal
einige Commodore-Strukturen an, z.B. "exec.i", oder "gfx.i". Der Lerneffekt
ist gewaltig!

Die Offsets -6,-12,-18, und -24 sind dem System vorbehalten. Deshalb müssen
unsere Funktionen mit dem Offset -30 beginnen. Auch hier sollten wir eine
Includedatei generieren, die unsere Einsprünge beinhalten. Diese taufen wir
"spezial_lib.i":

;
; Spezial_lib.i Einsprünge
;

_LVOOpenAllLibs			EQU	-30		; Öffnen der wichtigsten Librarys
_LVOCloseAllLibs		EQU -36		; Schließen derselben
_LVOAutoRequestFront	EQU	-42		; Autorequest auf dem vordersten Screen
_LVOCheckFileSize		EQU	-48		; Um Dateigröße festzustellen


Nun können wir uns an unser eigentliches Programm machen. Der Programmkopf
unserer eigenen Library sieht mindestens so aus:
(Aber nicht die beiden eben besprochenen Dateien dazu, die haben Sie ja
hoffentlich gespeichert!)
Es folgt nun alles in der echten Reihenfolge, so daß Sie es mit Ihrem
Assembler gleich ausprobieren können. Bedenken Sie bitte, daß bei Ihnen
selbst die Dateiverzeichnisse natürlich auch einen anderen Namen haben
können!


*********************************************************
*														*
*	spezial.library										*
*														*
*	Assembler Profi-Tips und Tricks						*
*	von Manfred Leidorf									*
*********************************************************

	INCDIR	INCLUDES:				; Assigned die Includes. Kann bei
									; Ihnen anders heißen!! Hier
									; bedeutet es, daß die Includes im
									; im logischen Gerät "INCLUDES:" zu
									; finden sind!

	INCLUDE	lvo/exec.i				; Oder Gleichwertiges mit den LVOS!
	INCLUDE	lvo/intuition.i
	INCLUDE	lvo/dos.i
	INCLUDE	exec/types.i
	INCLUDE	exec/libraries.i
	INCLUDE	exec/lists.i
	INCLUDE	exec/alerts.i
	INCLUDE	exec/initializers.i
	INCLUDE	exec/resident.i
	INCLUDE	libraries/dos.i
	INCLUDE	intuition/intuitionbase.i
	INCLUDE	SpezialBase.i
*--------------------------------------------------------------------


Das sind so die Grund-Includes für die eigene Library. Nun geht es schon um
den eigentlichen Programmcode. Eine Library sollten Sie wie folgt beginnen,
damit es keinen Absturz gibt, wenn diese versehentlich als normales
Programm gestartet wird:


start:
	moveq	#0,d0
	rts


Nun folgt die sogenannte Resistent-Struktur, die in etwa immer so aussehen
muß. Die Erläuterung erfolgt in den Kommentaren:


RTSTRUC:
	dc.w	RTC_MATCHWORD			; Resistent-Kennung
	dc.l	RTSTRUC					; Zur Kontrolle Zeiger auf Beginn
	dc.l	EndLib					; Zeiger auf Ende der Library
	dc.b	RTF_AUTOINIT			; Flag für automatisches Initialisieren
	dc.b	SPEZIALLIBVERSION		; Versionsnummer
	dc.b	NT_LIBRARY				; Kennung "Library"
	dc.b	MYLIBPRI				; Priorität
	dc.l	MyLibName				; Zeiger auf Libraryname
	dc.l	LibId					; Zeiger auf Versionsstring
	dc.l	InitLib					; Zeiger auf Library-Initstruktur
MyLibName:
	dc.b	"confusion.library",0	; Der Name
	cnop	0,4						; Adresse begradigen
SPEZIALLIBVERSION	=	34			; Ihre Versionsnummer
SPEZIALLIBREVISION	=	1			; Ihre Revisionsnummer
MYLIBPRI		=	0				; Die Prioriät
LibId:
	dc.b	"spezial.library 34.1 (12. Mai 93) "	; Der IDSTRING
	dc.b	"© Manfred Leidorf 1993",13,10,0
	cnop	0,4
	dc.w	0


Wenn Sie sich über "Kennung Library" gewundert haben: Auch Devices
beispielsweise haben diesen Kopf. Deshalb muß das Betriebssystem die
Möglichkeit zur Unterscheidung haben!
Es folgt nun die Initialisierungsstruktur. Diese beginnt mit der Größe der
Librarybase-Struktur, dem Zeiger auf die Funktionstabelle und der
Datentabelle. Danach folgt noch der Zeiger der Initialisierungsroutine.
Dieses Zeigerkonzept mag anfangs verwirrend wirken, hat aber den Vorteil,
daß nicht mit aller Gewalt jeder Programmteil an einer bestimmten Stelle
beginnen muß!

InitLib:
	dc.l	SpezialBase_SIZEOF		; Größe der LibraryBase
	dc.l	Functions				; Zeiger auf die Funktionstabelle
	dc.l	Datas					; Zeiger auf eventuelle Daten
	dc.l	LibInitRoutine			; Zeiger auf Initialisierungsroutine
Functions:
	dc.l	Open
	dc.l	Close
	dc.l	Expunge
	dc.l	ExtFunc
	dc.l	OpenAllLibs			; Öffnet GFX/INT/DOS/LAYERS/ICON
	dc.l	CloseAllLibs		; Schließt diese Libs wieder
	dc.l	AutoRequestFront	; Unser "Frontrequester"
	dc.l	CheckFileSize		; Filegrößenchecker
	dc.l	-1					; Ende


Die Routinen "Open, Close, Expunge und ExtFunc" sind für das System
reserviert. Trotzdem müssen Sie diese Routinen selbst schreiben, wobei es
bestimmte Grundvorschriften gibt. Aber auch eigene Routinen können dort
eingebaut werden. Zum Beispiel könnten Sie veranlassen, daß beim Öffnen
Ihrer Library automatisch das "audio.device" geöffnet wird und so
weiter....

Das grundlegende der Routine Open ist das Öffnen der Library bzw. das
Zählen, wie oft die Library schon geöffnet wurde. Denn erst wenn dieser
Zähler wieder auf Null ist, darf die Library aus dem System entfernt
werden.

Close ist das entsprechende Gegenstück. Es überwacht die Anzahl der
Library-Schließungen.

Expunge ist schließlich dafür zuständig, nun tatsächlich die Library aus
dem System zu entfernen.

ExtFunc ist noch unbenutzt und mit einer Dummy-Funktion belegt. Damit wurde
noch Reserve geschaffen für eventuelle Erweiterungen.

Es folgen nun noch die Daten. Dabei beginnen erst die vom System
vorgeschriebenen Daten, danach folgen Ihre eigenen. Da wir in unserer
Library eine Funktion haben, welche die Librarys "graphics,intuition, dos,
layers und icon" öffnet, brauchen wir natürlich die Namen dieser Librarys.

Datas:
	dc.b	LN_TYPE,NT_LIBRARY
	dc.l	LN_NAME,MyLibName
	dc.b	LIB_FLAGS,LIBF_SUMUSED!LIBF_CHANGED
	dc.w	LIB_VERSION,SPEZIALLIBVERSION
	dc.w	LIB_REVISION,SPEZIALLIBREVISION
	dc.l	LIB_IDSTRING,LibId
	dc.l	0

c_gfxname:
	dc.b	"graphics.library",0
	cnop	0,4
c_intname:
	dc.b	"intuition.library",0
	cnop	0,4
c_dosname:
	dc.b	"dos.library",0
	cnop	0,4
c_layersname:
	dc.b	"layers.library",0
	cnop	0,4
c_iconname:
	dc.b	"icon.library",0
	cnop	0,4
c_gfxbase:
	dc.l	0			; brauchen wir ab und zu!
	dc.w	0
Nun folgt die Routine, die von der Initialisierungsroutine des Systems
zusätzlich aufgrufen wird.

*****************************************************************
*																*
*	Initialisierungsroutine der Library							*
*																*
*****************************************************************

LibInitRoutine:
	move.l	a5,-(a7)
	move.l	d0,a5
	move.l	a6,sb_SysLib(a5)
	move.l	a0,sb_SegList(a5)
	lea	c_dosname(pc),a1
	moveq	#0,d0
	jsr	_LVOOpenLibrary(a6)
	move.l	d0,sb_DosLib(a5)
	bne.B	dos_ok
	ALERT	AG_OpenLib!AO_DOSLib
dos_ok:
	lea	c_intname(pc),a1
	moveq	#37,d0
	jsr	_LVOOpenLibrary(a6)
	move.l	d0,sb_IntuiLib(a5)
	bne.B	int_ok
	ALERT	AG_OpenLib!AO_Intuition
int_ok:
	move.l	a5,d0
	move.l	(a7)+,a5
	rts



Sie sehen nun die libraryinterne Routine Open: Zuerst wird die Anzahl der
Öffnungsaktionen hochgezählt und das Flag gelöscht, das ein Entfernen der
Library zuläßt.

*********************************
*								*
*	Libinterne Routine OPEN		*
*								*
*********************************

Open:
	addq.w	#1,LIB_OPENCNT(a6)
	bclr	#LIBB_DELEXP,sb_Flags(a6)
	move.l	a6,d0
	rts

Es folgt nun das Gegenstück CLOSE. Der Zähler wird um 1 verringert.
Ist der Zähler auf Null, wird geprüft, ob das DELEXP-Flag erlaubt, die
Library zu entfernen. Wenn ja, wird zu "Expunge" gesprungen und die Library
aus dem Speicher entfernt.

*********************************
*								*
*	Libinterne Routine CLOSE	*
*								*
*********************************

Close:
	moveq	#0,d0
	subq.w	#1,LIB_OPENCNT(a6)
	bne.B	endclose
	btst	#LIBB_DELEXP,sb_Flags(a6)
	beq.B	endclose
	bra.B	Expunge
endclose:
	rts

Expunge prüft auf Erlaubnis, die Library zu entfernen. Dann werden die
geöffneten Librarys geschlossen und die Library mittels Remove entfernt.

*********************************
*								*
*	Libinterne Routine EXPUNGE	*
*								*
*********************************

Expunge:
	movem.l	d2/a5/a6,-(a7)
	move.l	a6,a5
	move.l	sb_SysLib(a5),a6
	tst.w	LIB_OPENCNT(a5)
	beq.B	noopen
	bset	#LIBB_DELEXP,sb_Flags(a5)
	moveq	#0,d0
	bra.B	ExpungeEnd
noopen:
	move.l	sb_SegList(a5),d2
	move.l	a5,a1
	jsr	_LVORemove(a6)
	move.l	sb_DosLib(a5),a1
	jsr	_LVOCloseLib(a6)
	move.l	sb_IntuiLib(a5),a1
	jsr	_LVOCloseLib(a6)
	moveq	#0,d0
	move.l	a5,a1
	move.w	LIB_NEGSIZE(a5),d0
	sub.l	d0,a1
	add.w	LIB_POSSIZE(a5),d0
	jsr	_LVOFreeMem(a6)
	move.l	d2,d0
ExpungeEnd:
	movem.l	(a7)+,d2/a5/a6
	rts	


ExtFunc ist noch unbenutzt und wird mit einer sinnlosen Funktion belegt.

*********************************
*								*
*	Libinterne reservierte Function
*								*
*********************************

ExtFunc:
	moveq	#0,d0
	rts


Nun folgt unsere erste Routine, welche die erwähnten Librarys öffnet.
Als Ergebnis erhalten wir einen Zeiger auf die Librarybases in der
Reihenfolge exec,graphics,intuition,layers,dos und icon.
Warum Exec? Nun, zwar ist die Execbase in der Adresse 4 schon zu finden,
aber auf eine Kopie im FastRAM haben wir schnelleren Zugriff!
Parameter werden keine benötigt. Wegen "AllocVec" wird OS 2.0 hier
vorrausgesetzt. Wenn Sie kein OS 2.0 haben, so dürfen Sie Ihr Können gleich
üben: Versuchen Sie, AllocVec mit einer AllocMem-Sequenz zu ersetzen!
AllocMem und AllocVec sind im Prinzip gleich. Nur müssen Sie sich bei
AllocVec die Größe nicht merken. Beim Aufruf von FreeVec genügt die
Adresse, während Sie bei FreeMem auch noch die Größe wissen müssen, die der
Speicherbereich hatte!

********************************
*
*	Routine OpenAllLibs
*	Öffnet GFX,INT,LAYERS,DOS und ICON
*
*
*	Parameter: Keine
*	Rückgabe: in D0	Zeiger auf Liste der Bases
*	in der Reihenfolge exec,gfx,int,layers,dos und icon
*	Ein leerer Zeiger heisst: Lib konnte nicht
*	geöffnet werden.
*	Eine Null in d0: Wegen Speichermangel ganz unmöglich!
*	Es werden mindestens V37-Librarys verlangt
*	(Kann von Ihnen geändert werden)
*   Die Execbase selbst wird auf übergeben, da das
*	dieser aus dem FastMem schneller ist als von
*	Adresse 4
*
*********************************

OpenAllLibs:
	movem.l	a4/a5/a6,-(a7)		; Benutzte Register retten
	move.l	4.w,a6				; Execbase
	moveq	#24,d0				; Speicher holen für 6 Zeiger
	move.l	#$30001,d1			; Gelöschtes FastMem am Stück
	jsr	_LVOAllocVec(a6)		; Speicher holen
	move.l	d0,a5				; Adresse merken
	move.l	d0,a4				; Kopie...
	move.l	a5,d0				; Testen, ob erfolglos
	beq.B	fehler				; ging schief, also Null nach d0
	move.l	4.W,(a5)+			; Execbase als erstes in die Liste
	lea	c_gfxname(pc),a1		; Name Graphicslibrary
	moveq	#37,d0
	jsr	_LVOOpenLibrary(a6)		; GFXLIBRARY öffnen
	move.l	d0,(a5)+			; GFXBase
	lea	c_intname(pc),a1
	moveq	#37,d0
	jsr	_LVOOpenLibrary(a6)		; INTUITIONLIBRARY öffnen
	move.l	d0,(a5)+			; INTBASE
	lea	c_layersname(pc),a1
	moveq	#37,d0
	jsr	_LVOOpenLibrary(a6)		; LAYERSLIBRARY öffnen
	move.l	d0,(a5)+			; LAYERSBASE
	lea	c_dosname(pc),a1
	moveq	#37,d0
	jsr	_LVOOpenLibrary(a6)		; DOSLIBRARY öffnen
	move.l	d0,(a5)+			; DOSBASE
	lea	c_iconname(pc),a1
	moveq	#37,d0
	jsr	_LVOOpenLibrary(a6)		; ICONLIBRARY öffnen
	move.l	d0,(a5)				; ICONBASE
	move.l	a4,d0				; Adresse der Liste als Ergebnis
fehler:
	movem.l	(a7)+,a4/a5/a6		; Zurück
	rts							; Das wars!






Unsere nächste Routine soll die geöffneten Librarys wieder schließen. Als
Parameter übergeben wir den gleichen Zeiger, den wir bei OpenAllLibs
erhalten haben nach a1. Die Routine schließt dann die Library wieder und
gibt den Speicher für die Liste wieder frei.
Beachten Sie, wie peinlich genau die Routine jeden Zeiger erst prüft!
Schließlich soll ja kein CloseLibrary aufgerufen werden, wenn im
Librarybase-Zeiger gar nichts steht!

*********************************
*
*  Alle mit OpenLibAll geöffneten Librarys wieder schließen	*
*  Übergabe des von OpenAllLibs geöffneten Zeigers nach a1!	*
*
*********************************

CloseAllLibs:
	movem.l	d7/a2/a3/a6,-(a7)	; Register retten
	move.l	a1,a2				; Parameter retten 
	move.l	a2,a3				; dto.
	move.l	(a2)+,a6			; execbase braucht kein CloseLib
	move.l	(a2)+,d7			; GFXBASE
	beq.B	keine_gfx			; keine da...
	move.l	d7,a1				; Nach a1
	jsr	_LVOCloseLibrary(a6)	; und schließen
keine_gfx:
	move.l	(a2)+,d7			; INTBASE
	beq.B	keine_int			; keine da...
	move.l	d7,a1				; Nach a1
	jsr	_LVOCloseLibrary(a6)	; und schließen
keine_int:
	move.l	(a2)+,d7			; LAYERSBASE
	beq.B	keine_layers		; keine da...
	move.l	d7,a1				; Nach a1
	jsr	_LVOCloseLibrary(a6)	; und schließen
keine_layers:
	move.l	(a2)+,d7			; DOSBASE
	beq.B	keine_dos			; keine da...
	move.l	d7,a1				; Nach a1
	jsr	_LVOCloseLibrary(a6)	; und schließen
keine_dos:
	move.l	(a2),d7				; ICONBASE
	beq.B	keine_icon			; keine da...
	move.l	d7,a1				; Nach a1
	jsr	_LVOCloseLibrary(a6)	; und Schließen
keine_icon:
	move.l	a3,a1				; Speicher der Liste
	jsr	_LVOFreeVec(a6)			; freigeben

	movem.l	(a7)+,d7/a2/a3/a6	; Ende
	rts


Bei der nächsten Routine schlägt wieder ein Lerneffekt zu. Denn bei unserem
"AutoRequestFront" müssen viele Dinge beachtet werden.

Unsere Routine soll den vordersten Screen ermitteln. Nach dem dies
geschehen ist, wird verhindert, daß während unser Programm so schön
arbeitet, der vorderste Screen wechselt. Anschließend holen wir uns die
Struktur des ersten zugehörigen Windows. Ist kein Window vorhanden, wird
unsere Routine mit einer Fehlerrückgabe (0 in D0) verlassen. Ansonsten
startet nun der AutoRequest. Hier einigen wir uns auf folgendes:
Bei "Cancel" übergeben wir eine -1, bei Okay eine +1. Das ist zwar anders
als beim echten "AutoRequest", aber wir brauchen die Null ja für unsere
Fehlermeldung!
Die IntuitionLibrary müssen wir nicht extra öffnen, die hat unsere Library
schon beim Initialisieren geöffnet und die Basis in unsere Libbase mit dem
Namen "sb_IntuiLib" abgelegt!

******************************************
*	Wie AutoRequest, jedoch automatisch
*	auf vordersten Screen!
******************************************


AutoRequestFront:
	movem.l	d2-d7/a2-a6,-(a7)	; Register retten
	move.l	sb_IntuiLib(a6),a6	; IntuitionBase
	jsr	_LVOLockIBase(a6)		; IntuitionBasis locken
	move.l	d0,d7				; Lock merken
	move.l	60(a6),a5			; Der vorderste Screen
	move.l	4(a5),d6			; Das erste Window
	beq.B	kein_fenster		; Fehler!
	move.l	d7,a0				; IntLock
	jsr	_LVOUnlockIBase(a6)		; Lock befreien
	move.l	d6,a0				; Nach a0
	lea	bodytext(pc),a1			; Bodytext Requester
	lea	lefttext(pc),a2			; Linker Text
	lea	righttext(pc),a3		; Rechter Text
	moveq	#0,d0
	moveq	#0,d1
	move.w	#320,d2				; Nur bis Kick 1.3
	move.w	#160,d3				; Nur bis Kick 1.3
	jsr	_LVOAutoRequest(a6)
	tst.l	d0
	beq.B	war_rechts			; Rechts angeklickt
	moveq	#1,d0
	bra.B	vorbei				; Das war es
war_rechts:
	moveq	#-1,d0
	bra.B	vorbei
*----------------------
kein_fenster:
	moveq	#0,d0
vorbei:
	movem.l	(a7)+,d2-d7/a2-a6
	rts
*----------------------
bodytext:
	dc.b	0,1					; Farben
	dc.b	0					; Modus
	dc.b	0					; Füller
	dc.w	10,10				; Textposition
	dc.l	0					; Standardfont
	dc.l	bodystring			; Der Text
	dc.l	0					; Kein weiterer Text
bodystring:
	dc.b	"Sind Sie sich da sicher ?",0
	cnop	0,4

lefttext:
	dc.b	0,1
	dc.b	0
	dc.b	0
	dc.w	5,3
	dc.l	0
	dc.l	leftstring
	dc.l	0
leftstring:
	dc.b	"Ja!"
	cnop	0,4

righttext:
	dc.b	0,1
	dc.b	0
	dc.b	0
	dc.w	5,3
	dc.l	0
	dc.l	rightstring
	dc.l	0
rightstring:
	dc.b	"Nein!"
	cnop	0,4


Unsere nächste Routine verrät Ihnen zugleich, wie Sie die Größe einer Datei
ermitteln können. Ab dann können Sie es ja bequem per Library
automatisieren. Diesen Part erklären wir nun etwas ausführlicher. Zunächst
beginnen wir der Ordnung halber so:

****************************************************
*	
*	CheckFileSize ermittelt Größe einer Datei
*
*	Parameter: In a0 Zeiger auf Pfad
*	Ergebnis: In d0 Größe oder 0
*
****************************************************

CheckFileSize:
	link	a5,#-32					; Für lokale Variablen!
	movem.l	d2-d7/a2-a4/a6,-(a7)	; Register retten

Unsere Librarybase brauchen wir noch, A6 aber auch. Deshalb retten wir sie
auf unserem lokalen Stack. Wir müssen nämlich Speicher holen für den
FileInfoBlock!

	move.l	a6,-4(a5)

Jetzt holen wir die benötigten 260 Bytes für den Infoblock:

	move.l	4.W,a6
	move.l	#260,d0
	moveq	#1,d1
	jsr	_LVOAllocVec(a6)
	move.l	d0,-8(a5)				; Merken
	beq.B	error

Zunächst muß unsere Routine den Lock der Datei besorgen, dessen Zeiger auf
den Pfad wir nach a0 übergeben haben. Die Dosbase befindet sich schon
innerhalb unserer eigenen Librarybase. Holen wir sie uns also:

	move.l	-4(a5),a6				; Unsere Libbase
	move.l	sb_DosLib(a6),a6		; Dosbase
	move.l	a0,d1					; Der Pfad
	moveq	#-2,d2					; Lock zum Lesen
	jsr	_LVOLock(a6)				; Lock holen
	move.l	d0,-12(a5)				; Unser lokaler Stack
	beq.B	fehler2					; Das ging wohl schief....

Jetzt füllen wir den Fileinfoblock mit unseren gewünschten Daten.

	move.l	-8(a5),d2				; Zeiger Infoblock
	move.l	d0,d1					; Lock
	jsr	_LVOExamine(a6)				; Infoblock füllen
	
Der FileInfoBlock ist nun richtig mit Daten gefüttert oder im Fehlerfalle
leer. So oder so müssen wir den Lock jetzt wieder freigeben. Wir haben im
Prinzip ja, was wir wollen!

	move.l	-12(a5),d1
	jsr	_LVOUnLock(a6)

Jetzt testen wir unseren FileInfoBlock auf Erfolg:

	move.l	-8(a5),a3
	move.l	124(a3),d6				; Dateigröße vorläufig mal nach d6
	beq.B	fehler2					; Das ging schlief

Den Speicher für den Infoblock können wir nun wieder freigeben:

	move.l	4.W,a6
	move.l	-8(a5),a1
	jsr	_LVOFreeVec(a6)
	move.l	d6,d0
	bra.B	kein_fehler				; d0 enthält nun Dateigröße

***************************************************
*	Fehlerbehandlungen
***************************************************
fehler2:
	move.l	4.W,a6
	move.l	-8(a5),a1
	jsr	_LVOFreeVec(a6)
error:
	moveq	#0,d0
kein_fehler:
	movem.l	(a7)+,d2-d7/a2-a4/a6
	unlk	a5
	rts
*************************************************************
*
*	Kennung Libraryende
*
*************************************************************
EndLib:
	END
	


Es folgt nur noch die Kennung für das Libraryende, und unsere Library ist
somit fertig. Wir können Sie assemblieren, "spezial.library" taufen und in
das Libs-Verzeichnis kopieren.
Wenn Sie beim Assemblieren Fehlermeldungen bekommen, so sollten Sie prüfen,
ob Ihr Assembler mit dem Code was anfangen kann und ob Sie wirklich alle
Includes richtig eingebunden haben. Ich habe dieses Programm erprobt und
als Beweis ist genau diese Library auf der beiliegenden Diskette, fix und
fertig.

Ich hoffe, daß Sie nun eine gute Anregung für Ihre eigene Library gefunden
haben und wünsche Ihnen viel Spaß dabei! Besonders nützliche und gute
Librarys sollten Sie dem Public-Domain - oder Shareware-Pool zur Verfügung
stellen!


KAPITEL 6

Fragen und Antworten

Immer wieder tauchen Fragen auf, und was ist die logische Konsequenz, wenn
man keinen Experten zur Hand hat? Man versucht sich durch Zeitschriften zu
wühlen, um die Antwort zu finden. Aus diesem Grunde habe ich einige häufige
Fragen zusammengetragen, um diese hier zusammenfassend zu beantworten. Die
Schwierigkeitsgrade sind dabei sehr unterschiedlich. Legen wir also los:

FRAGE:

Bei manchen Spielen oder Demos legt man die Diskette ein, und blitzschnell
erscheint ein Bild auf dem Monitor. Wie ist das möglich?

ANTWORT:

Der Geschwindigkeitskniff liegt darin, daß das Bild mit Hilfe einer Routine
innerhalb des Bootblock der Diskette geladen wird. Das Bild wird dann ab
Sektor 2 direkt - oft auf ohne Umweg über eine Directory - mit Hilfe des
Trackdisk-Devices geladen. Oft paßt ein solches Bild auf den Rest des
Zylinders 0, manchmal wird noch Zylinder 1 gebraucht. Jedenfalls ist
ruckzuck und ohne Stepperorgien des Schreib/Lese-Kopfes das Bild geladen.
Sie müssen also Aufpassen: Das Spielchen ist nur sicher bei eh
directorylosen Disketten. 

Sie speichern nun Ihr Bild ab Block 2 ab, also direkt nach dem Bootblock,
und lesen es mit DoIO wieder ein. Für den Normalanwender erscheint es dann
so, als wäre das ganze Bild in den Bootblock gequetscht worden.
Die Checksumme des Bootblocks berechnet sich übrigens so:

	lea	Bootblock,a0
	lea 4(a0),a1
	clr.l	(a1)
	moveq	#255,d1
	moveq	#0,d0
rechne:
	add.l	(a0)+,d0
	bcc.b	schleife
	addq.l	#1,d0
schleife:
	dbf	d1,rechne
	not.l	d0
	move.l	d0,(a1)


FRAGE:

Bei meinem selbstgeschriebenen IFF-Lader wird bei einigen Bildern nur
Salat angezeigt.

ANTWORT:

Das kann natürlich an vielem liegen. Beispielsweise an einer falsch
berechneten Bytebreite. Nehmen wir an, das wir die Breite in Pixeln im
Register d0 haben, dann errechnet sich die Bytebreite wie folgt:

	add.w	#15,d0
	lsr.w	#4,d0
	add.w	d0,d0

Genausogut können Sie aber auch im Falle eines Hardwarehacks die Register
falsch initialisiert haben!


FRAGE:

Wie muß mein Programm beginnen, damit ich es sowohl von der Workbench als
auch vom CLI aus starten kann? Wenn ich mein Programm von der Workbench aus
starten will, erscheint ein GURU!

ANTWORT:

Es muß im Falle eines Workbenchstartes die WB-Message abgeholt und
beantwortet werden, sonst führt der Messagestau zum Guru. Der Programmkopf
sieht (mindestens) so aus:

start:
	move.l	4.w,a6
	suba.l	a1,a1
	jsr	_LVOFindTask(a6)			; Eigenen Task suchen
	move.l	d0,a4					; Taskstruktur merken
	tst.l	172(a4)					; Start vom CLI ?
	bne.B	vom_cli					; Ja
	lea	92(a4),a0					; Auf Startup-Message warten
	jsr	_LVOWaitPort(a6)			; Warten...
	lea	92(a4),a0					; Startup-Message
	jsr	_LVOGetMsg(a6)				; Message lesen
	move.l	d0,wb_message			; Startup-Message merken
vom_cli:
	.
	.
	.

Die Message sollte natürlich noch replyt werden, spätestens kurz vor
Verlassen des Programmes!


FRAGE:

Wie kann man per Programm feststellen, ob sich der AA-Chipsatz im Rechner
befindet?

ANTWORT:

Des Rätsels Lösung finden Sie in der GfxBase. Dort befindet sich an Offset
236 ein Byte namens "ChipRevBits". Den AA-Chipsatz erkennen Sie daran, daß
sich dort $1f befindet, sofern der Chipsatz aktiviert ist.


FRAGE:

Wie kann ich am einfachsten einem selbstgeschriebenen CLI-Befehl Parameter
übergeben ?

ANTWORT:

Im Register d0 steht automatisch die Größe in Bytes Ihrer Parameter und im
Register A0 die Adresse. Nehmen wir an, es handelt sich bei dem Parameter
um einen Dateipfad. Dann könnte die Routine folgendermaßen aussehen:

start:
	subq.w	#1,d0
	bne.B	argumente
sense:
	rts						; Keine Argumente
argumente:
	cmp.b	#$20,(a0)+
	bne.B	hier
	dbf	d0,argumente
	bra.B	sense
hier:
	subq.l	#1,a0
	lea	pfad(pc),a4
pfadschreiber:
	move.b	(a0)+,(a4)+
	dbf	d0,pfadschreiber
	clr.b	-1(a4)
	

FRAGE:

Ich benötige einen einigermaßen guten Requester, der aber nicht nur 2 Werte
abfragt wie zum Beispiel "AutoRequest", sondern quasi beliebig viele!

ANTWORT:

Hierfür ist die Routine "EasyRequestArgs" der "intuition.library" mit dem
Offset -588 gedacht.
Der Aufruf:

Window:			a0
EasyStruktur:	a1
IDCMP:			a2
Argumente:		a3

In A0 muß die Adresse des betroffenen Windows. Nach a1 muß der Zeiger einer
Easy-Requester-Struktur.

Der Zeiger auf eventuelle zusätzliche IDCMP-Flags darf auch leer sein.
A3 schließlich sollte auf einen leeren Datenbereich für z.B. einen
Dateinamen zeigen.
Wichtig: Das Gagdet ganz rechts entspricht 0, also dem CANCEL wie bei
AutoRequest!


	move.l	intbase(pc),a6
	move.l	60(a6),a5					; Erster Screen
	move.l	sc_FirstWindow(a5),a0		; Erstes Fenster
	lea	ChangeReq(pc),a1				; Fragen stellen
	lea	IDCMP(pc),a2					; IDCMP-Zeiger
	lea	Parameter(pc),a3
	jsr	_LVOEasyRequestArgs(a6)
	cmp.w	#1,d0
	beq.B	aber_ja					; 1. Gadget
	cmp.w	#2,d0
	beq.B	warum					; 2. Gadget
	cmp.w	#3,d0
	beq.W	heute_nicht				; 3. Gadget
	cmp.w	#4,d0
	beq.B	vielleicht				; 4. Gadget
	cmp.w	#5,d0
	beq.B	haeeh					; 5. Gadget
	bra.W	nein					; Muß in die Datei !!
*-------------------------------------------------------------------------
IDCMP:
	dc.l	0
ChangeReq:
	dc.l	es_SIZEOF,0,_Title,_Fmt,_Buttons
_Title:
	dc.b	"Witziger Laderequester ",0
	cnop	0,4
_Fmt:
	dc.b	"Soll ich die Datei einladen ?",0
	cnop	0,4
_Buttons:
	dc.b	"ABER JA! |WARUM? | HEUTE NICHT | VIELLEICHT | HAEH? | NEIN",0
	cnop	0,4
Parameter:
	blk.b	256,0		; Eventueller Dateiname



FRAGE:

Wenn ich aus einer Screenstruktur die Mauskoordinaten abfrage, bekomme ich
völlig falsche Werte!

ANTWORT:

Sie haben bestimmt die Koordinaten verwechselt. Man geht nämlich
normalerweise davon aus, daß zuerst die X-Koordinate und dann die
Y-Koordinate folgt. In der Screenstruktur ist es aber umgekehrt:
Beim Offset 16 steht die Y-Koordinate, beim Offset 18 die X-Koordinate.
Der Hitpoint wird aber in der Screenstruktur NICHT berücksichtigt! Hier
müssen Sie die Mauskoordinaten im gerade aktiven Window benutzen!
Dort steht bei Offset 12 die relative Y-Position zum Window, und bei 14 die
relative X-Position. Der Hitpoint wird durch zwei BYTE-Werte dargestellt,
nämlich für X der Offset 80 und für Y der Offset 81 (Hoppla, da stimmt die
Reihenfolge plötzlich wieder!)


FRAGE:

Was ist der Unterschied zwischen AllocMem und AllocVec? Die zu übergebenden
Parameter sind doch gleich!

ANTWORT:

Richtig. Aber Sie müssen sich nur noch noch die Adresse merken, nicht aber
die Größe. Um den mit AllocVec geforderten Speicher freizugeben, genügt die
Adresse in A1 und FreeVec, während Sie bei FreeMem noch die Größe wissen
mußten. Hier übrigens nochmal ein kleines Progrämmchen, daß die Benutzung von
AllocVec() demonstriert!

	incdir	"include:"
	include	"exec/exec.i"
	include	"lvo/exec.i"
	include	"dos/dos.i"
	include	"lvo/dos.i"

AllocVec1:
	move.l	4,a6
	move.l	#MEMF_FAST,d1
	move.l	#480000,d0
	jsr	_LVOAllocVec(a6)
	tst.l	d0
	beq.w	errende1
	move.l	d0,MemBase1
	move.l	4,a6
	move.l	#MEMF_FAST,d1
AllocVec2:
	move.l	#480000,d0
	jsr	_LVOAllocVec(a6)
	tst.l	d0
	beq.w	errende1
	move.l	d0,MemBase2
	
	move.l	#DosName,a1
	move.l	#37,d0
	jsr	_LVOOpenLibrary(a6)
	tst.l	d0
	beq.w	errende1
	move.l	d0,DosBase
PutText2:
	move.l	DosBase,a6
	move.l	#FuellText,d1
	jsr	_LVOPutStr(a6)
Fuellen1:
	move.l	#10,d1		; Erstes Bild mit 10 füllen
	move.l	#479999,d2
	move.l	MemBase1,a0
loop1:
	move.b	d1,(a0)+
	dbf	d2,loop1
Fuellen2:
	move.l	#1,d1		; Zweites Bild mit 1 füllen
	move.l	#479999,d2
	move.l	MemBase2,a0
loop2:
	move.b	d1,(a0)+
	dbf	d2,loop2
	move.l	#25,d3		; 25 Bilder pro Sekunde :)
PutText3:
	move.l	DosBase,a6
	move.l	#AddText,d1
	jsr	_LVOPutStr(a6)
Addieren:
	move.l	#479999,d2
	move.l	MemBase1,a0
	move.l	MemBase2,a1
loop3:
	move.b	(a1)+,d1	; Bildveränderung in d1
	add.b	d1,(a0)+	; zum vorherigen Bild dazu addieren
	dbf	d2,loop3
	dbf	d3,Addieren	; nächstes Bild
NormalesEnde:
	move.l	DosBase,a6
	move.l	#Text4,d1
	jsr	_LVOPutStr(a6)
	move.l	4,a6
	move.l	DosBase,a1
	jsr	_LVOCloseLibrary(a6)
	move.l	MemBase1,a1
	jsr	_LVOFreeVec(a6)
	move.l	MemBase2,a1
	jsr	_LVOFreeVec(a6)
	move.l	#0,d0
	rts	
errende1:
	move.l	DosBase,a6
	move.l	#ErrorText1,d1
	jsr	_LVOPutStr(a6)
	move.l	4,a6
	move.l	DosBase,a1
	jsr	_LVOCloseLibrary(a6)
	rts

MemBase1:
	dc.l	0
MemBase2:
	dc.l	0
DosName:
	DOSNAME
	cnop	0,4
DosBase:
	dc.l	0
ErrorText1:
	dc.b	"Konnte Speicher nicht allocieren oder Dos nicht öffnen!",0
	cnop	0,4
FuellText:
	dc.b	"Fülle 480000 Bytes mit 10 !",$0A,0
	cnop	0,4
AddText:
	dc.b	"Addiere 25*480000mal 20 dazu !",$0A,0
	cnop	0,4
Text4:
	dc.b	"Fertig !",$0A,0
	cnop	0,4

	END




FRAGE:

Wie stelle ich fest, welcher Prozessor im Rechner ist?

ANTWORT:

In der Execbase bei Offset 296 (AttnFlags) steht ein Wort, das Ihnen die
gewünschte Auskunft gibt.

Kein Bit gesetzt: 68000 und sonst nichts.

Bit 0	gesetzt:  68010 (auch bei 68020 gesetzt)
Bit 1   gesetzt:  68020 (auch bei 68030 gesetzt)
Bit 2   gesetzt:  68030 (auch bei 68040 gesetzt)
Bit 3   gesetzt:  68040
Bit 4   gesetzt:  68881-Coprozessor (auch bei 68882 gesetzt)
Bit 5   gesetzt:  68882

Warum zwischen 68881 und 68882 unterschieden werden muß, weiß ich selbst
leider nicht. Meines Wissens nach sind beide funktionell identisch.
Kurioserweise läuft IMAGINEFP nur mit dem 68882 korrekt und schnell (und
natürlich mit dem 68040).


FRAGE: 

Was sind eigentlich TAGS, speziell bei Intuition ?

ANTWORT:

Tag heißt zu deutsch "Anhängsel". Bisher wurden große Parameter in Form von
Strukturen übergeben die meist eine starre Größe hatten und nur schwer
erweiterbar sind. Anders bei den Tags: Hier werden nur die Parameter
übergeben, die auch wirklich gebraucht werden, alle anderen sind mit einem
Defaultwert vorbelegt. Selbst die Reihenfolge der Parameter ist egal
geworden. Um das zu ermöglichen, wird zuerst die betroffene Tag-Kennung und
dann der Parameter geschrieben. "OpenScreenTagList" beispielsweise ist ein
Ersatz für OpenScreen und darf theoretisch sogar völlig ohne Parameter
aufgerufen werden. In diesem Fall wird ein Lores-Screen mit 2 Farben
geöffnet. Leider würde dieses Buch den Rahmen sprengen, um genauer auf die
Tags einzugehen. Weiterführende Literatur ist vonnöten. Aber trotzdem hier
ein Beispiel für OpenScreenTagList und OpenWindowTaglist!
Das Listing ist erst lauffähig, wenn Sie es auf Ihre Verhältnisse (Includes
etc.) angepaßt haben!

	incdir	"include:"
	incdir	"include:lvo"
	include	"exec/exec.i"
	include	"exec.i"
	include	"intuition/intuition.i"
	include	"intuition/intuition_lib.i"
	include	"intuition/screens.i"
	include	"graphics/displayinfo.i"
	include	"intuition.i"

	Section	Programm,Code

	move.l	4,a6
OpenIntui:
	move.l	#37,d0
	move.l	#IntuiName,a1
	jsr	_LVOOpenLibrary(a6)
	tst.l	d0
	beq	errende
	move.l	d0,IntuiBase
OpenTagScreen:
	move.l	#0,a0
	move.l	#ScreenTagList,a1
	move.l	IntuiBase,a6
	jsr	_LVOOpenScreenTagList(a6)
	move.l	d0,ScreenHandle
OpenTagWindow:
	move.l	IntuiBase,a6
	move.l	#0,a0
	move.l	#WindowTagList,a1
	move.l	#ScreenHandle,Custom
	jsr	_LVOOpenWindowTagList(a6)
	tst.l	d0
	beq	ScreenClose
	move.l	d0,WindowHandle
CloseTagWindow:
	move.l	WindowHandle,a0
	jsr	_LVOCloseWindow(a6)
ScreenClose:
	move.l	ScreenHandle,a0
	jsr	_LVOCloseScreen(a6)
		
errende:
	move.l	-5,d0
	rts

	Section	Daten,Data

IntuiName:
	INTNAME
IntuiBase:
	dc.l	0
ScreenHandle:
	dc.l	0
ScreenTagList:
	dc.l	SA_Width,640
	dc.l	SA_Height,512
	dc.l	SA_Depth,4
	dc.l	SA_DisplayID,HIRESLACE_KEY
	dc.l	SA_Title,ScreenTitle
	dc.l	TAG_DONE,0	
ScreenTitle:
	dc.b	"Mein erster Screen",0
	cnop	0,4
WindowHandle:
	dc.l	0
WindowTagList:
	dc.l	WA_ScreenTitle,ScreenTitle
Custom:	dc.l	WA_CustomScreen,ScreenHandle
	dc.l	WA_Left,10
	dc.l	WA_Top,100
	dc.l	WA_Width,200
	dc.l	WA_Height,300
	dc.l	WA_Title,WindowTitle
	dc.l	TAG_DONE,0

WindowTitle:
	dc.b	"Mein Window",0
	cnop	0,4
	END


KAPITEL 7

DER ABSOLUT SICHERE KOPIERSCHUTZ

Über den Sinn und Unsinn eines Kopierschutzes mag man streiten.
Nichtsdestotrotz macht es Spaß, sich mit dieser Materie zu befassen. Warum
also nicht?

Die Überschrift täuscht natürlich ein wenig. Zwar läßt sich ein
Kopierschutz entwickeln, den kein Kopierprogramm der Welt überwinden kann,
aber deshalb ist der Schutz nicht absolut sicher. Schließlich gibt es ja da
noch die heißgeliebten Cracker, die nur darauf warten, diesen wieder zu
entfernen. Aber es gibt Schutzmethoden, die selbst heutige Hardwarezusätze
nicht überwinden können, weil es rein physikalisch einfach unmöglich ist!

Die Grundlagen

Das erste für uns interessante Register ist das Drivestatus-Register
$BFE001, das den meisten bekannt ist, weil dort z.B. auch die Feuerknöpfe
bzw. die linke Maustaste abgefragt wird. Für den Diskettenbetrieb sind die
Bits 2-5 von Bedeutung: (Vergessen Sie nicht, daß die Bits ab 0 gezählt
werden. Bit 2 ist demnach das dritte Bit.)

Bit 2

Ist dieses Bit gesetzt, so ist eine Diskette im Laufwerk. Das Bit wird aber
erst aktualisiert, wenn der Steppermotor bewegt wird.

Bit 3

Wenn die Diskette schreibgeschützt ist, so ist das Bit gelöscht.

Bit 4

Befindet sich der Schreib/Lesekopf auf Zylinder 0, so ist dieses Bit
gelöscht.

Bit 5

Man kann das Diskettenlaufwerk nicht mit Kommandos "Überschütten": Erst
wenn Bit 5 auf 0 ist, kann das angewählte Laufwerk Befehle annehmen.


DRIVE SELECT

Um nun den Kontroller zu bedienen, benutzen wir das CIA B - Register
$BFD100, welches einige wichtige Funktionen beinhaltet. Wie alle
CIA-Register hat auch dieses eine Größe von 8 Bit. Erstens müssen wir eine
Möglichkeit haben, das Laufwerk auszuwählen, denn bis zu vier Floppys
können angeschlossen sein. Zweitens brauchen wir einen "Schalter" für den
Laufwerksmotor und eine Steuerung des Steppermotors. Doch nun die Belegung
im einzelnen:

Bit 7: Motorbit

Um gezielt den Motor eines der vier Laufwerke einzuschalten, müssen wir
folgendermaßen vorgehen: Zunächst deselektieren wir alle vier Laufwerke und
setzen dieses Bit auf 0. Nun selektieren wir ein Laufwerk. Dadurch schaltet
sich der Motor des gewünschten Drives ein. Bei gesetztem Bit wird dagegen
bei dieser Prozedur der betreffende Motor AUS-Geschaltet.

Bit 6: Laufwerk 3

Bit 5: Laufwerk 2

Bit 4: Laufwerk 1

Bit 3: Laufwerk 0


Bit 2:

Ist dieses Bit auf 0, wird die obere Spur gelesen/geschrieben, bei 1 die
untere.

Bit 1:

Wenn der Schreibkopf bewegt wird, so bestimmt dieses Bit die Richtung. Bei
0 wird der Kopf nach innen bewegt, bei 1 nach außen.

Bit 0:

Dieses Bit sendet die Stepimpulse, löst also die Bewegung des Schreibkopfes
aus. Dies geschieht durch Löschen des Bits. Nach dem Löschen sollte es
sofort wieder gesetzt werden, schon alleine deshalb, um weitersteppen zu
können.

ACHTUNG: Die Stepgeschwindigkeit sollte nicht übertrieben werden, sonst
kommt das Laufwerk nicht mehr mit. Benutzen Sie aber auf keinen Fall
DBF-Schleifen oder ähnliches für die Pause, denn sonst versagt Ihre
Steproutine auf Turbokarten.

Um ein wenig in Übung zu kommen: Hier nun ein kleiner Hardwarehack, der im
Laufwerk DF0: zuerst das Vorhandensein einer Disk prüft, dann den Kopf zu
Zylinder 0 fährt und anschließend zu Cylinder 79. Danach fährt der Kopf
wieder zum ursprünglichen Ausgangspunkt.

Stepübung:



drive	equ	$bfd100
status	equ	$bfe001

start:
	lea	$dff000,a5
	move.w	#$4000,$9a(a5)		; IRQ aus
	move.b	#$ff,drive			, Alles auf High

;
; Auf eingelegte Diskette warten
;

wait_of_disk:
	bsr.B	innenstep			; 1 Spur nach innen
	move.l	#500,d5				; etwas warten
	bsr.W	pause
	bsr.B	aussenstep			; 1 Spur nach aussen
	move.l	#500,d5
	bsr.W	pause
	btst	#2,status			; Diskette drin ?
	beq.B	wait_of_disk

;
; Auf Spur 0 fahren und Steps mitzählen
;

	moveq	#0,d4
steppo:
	btst	#4,status			; Spur 0 ?
	beq.B	am_ziel				; ja
	bsr.W	aussenstep			; nein
	addq.w	#1,d4				; Mitzählen
	moveq	#2,d5				; Ganz kurz warten
	bsr.W	pause
	bra.B	steppo

am_ziel:
	moveq	#78,d2				; Zähler für 79 Zylinder
loop:
	bsr.B	innenstep			; 1 Spur nach innen
	moveq	#2,d5
	bsr.W	pause
	dbf	d2,loop

	moveq	#79,d2
	sub.w	d4,d2				; wieder zur Normalspur

loop2:
	bsr.B	aussenstep
	moveq	#2,d5
	bsr.W	pause
	dbf	d2,loop2

aus:
	move.w	#$c000,$9a(a5)
	rts

innenstep:
	bclr	#1,drive
	bclr	#0,drive
	bset	#0,drive
	rts

aussenstep:
	bset	#1,drive
	bclr	#0,drive
	bset	#0,drive
	rts

pause:
	move.l	$dff004,d7
	and.l	#$0001ff00,d7
	cmp.l	#$0001f000,d7
	bhi.B	pause
	lsr.l	#8,d7
	move.b	d7,d6
	add.b	#15,d6
sleep:
	cmp.b	$dff006,d6
	bne.B	sleep
	dbf	d5,pause
	rts


Soweit, so gut. Aber wir wollen ja nicht nur mit dem Steppermotor spielen,
sondern Daten lesen und schreiben. Auch der Diskettenbetrieb arbeitet
mittels DMA, und zwar völlig prozessorunabhängig, da nur gerade Zyklen
benutzt werden. Der Prozessor muß nur die Register initialisieren.
Natürlich müssen die Daten beim Schreiben noch MFM-Codiert werden (mit
Blitter möglich!) oder beim Lesen decodiert. Aber der Datentransfer läuft
"wie von selbst!"

Die Daten, die von der Disk-DMA gehandhabt werden, müssen im CHIP-Memory
liegen. Die Adresse, ab der geschrieben bzw. wohin gelesen wird, kommt in
das DSKPT-Register $DFF020 und hat im Prinzip Langwortformat. Das nächste
zu besprechende Register ist DSKLEN mit der Adresse $DFF024 und hat
folgende Bedeutung:

Bit:

15	1 = DMA ist möglich
14	1 = schreiben 0 = lesen
13-0 = Anzahl der zu schreibenden Worte.

WICHTIG: Das Register wird als LETZTES beschrieben, um die Disk-DMA zu
starten. Als weitere Besonderheit gilt: Das Register muß zweimal
beschrieben werden, um die Aktion wirksam werden zu lassen. Grund: Man
wollte vermeiden, daß durch einen Programmfehler versehentlich wichtige
Daten durch Überschreiben zerstört werden. Bevor also DSKLEN beschrieben
wird, muß noch ein anderes Register initialisiert werden, und zwar ADKCON,
mit der Adresse $DFF09E. Dieses Register ist auch lesbar, aber an einer
anderen Adresse, nämlich $DFF010.

Die Belegung:

Bit		Funktion

15		1 = Die anderen gesetzten Bits werden auf 1 gesetzt
		0 = Die anderen gesetzten Bits werden auf 0 gesetzt

14/13	Precomp-Zeit. 00 = Keine, 01 = 140 Ns, 10 = 280 ns
		11 = 560 ns

12		1 = MFM-Format 0 = GCR-Format

11		1 = Daten werden zu Paula geschickt, 0 = Daten
			werden zu Diskette geschickt.

10		1 = Ein bestimmtes Wort wird als Syncmarkierung benutzt.

9		1 = GCR-Sync soll benutzt werden

8		1 = MFM-Geschwindigkeit 0 = GCR-Geschwindigkeit

Das erwähnte Syncwort (signalisiert mit Bit 10) wird in das Register
$DFF07E geschrieben. Sinnvollerweise muß es ein Wort sein, daß durch
normale MFM-Codierung nicht entstehen kann, aber dennoch legal ist.
Es hat sich hier der Wert $4489 durchgesetzt.

Der Kopierschutz

Unser Kopierschutz, der selbst von einem Hardwarezusatz nicht kopiert
werden kann, beruht auf folgendem Prinzip: Kein Diskettenlaufwerk hat die
exakt gleiche Umdrehungsgeschwindigkeit wie ein anderes. Außerdem ist der
Motor noch Gleichlaufschwankungen unterworfen. Die Idee: Wir schreiben eine
Spur randvoll mit Daten und zählen diese. Diesen Wert merken wir uns und
benutzen ihn später, um die geschriebene Datenmenge abzufragen. Weicht die
Datenmenge ab, liegt eine Kopie vor!
Beim Lesen einer Spur stört die Gleichlaufschwankung nicht, aber wenn ein
Programm versucht, die exakt gleiche Datenmenge zu schreiben, so ist die
Chance sehr gering. Eine lauffähige Kopie wird somit zum reinen
Glückstreffer, und daran ändert auch ein Hardwarezusatz nichts!

Damit wir aber einen definierten Anfang auf unserer Schutzspur haben,
wollen wir auch die Syncmarkierung einsetzen. Es gibt zwei Möglichkeiten,
zu synchronisieren. Entweder mit dem erwähnten Wort $4489, das wir
sicherheitshalber zweimal hintereinander schreiben sollten, da der
Kontroller manchmal das erste verschluckt. Oder wir benutzen die
Indexmarkierung. Bei jeder Umdrehung signalisiert der Motor mit Hilfe einer
Lichtschranke, daß eine Umdrehung vollendet ist. Dieses Signal läßt sich
abfragen. Allerdings ist das Indexverfahren relativ ungenau. Ein bitgenaues
Synchronisieren ist so nicht möglich. Bytegenau reicht aber wohl aus.
Die Indexmarkierung erkennen Sie durch Bit 4 im Register $BFDD00. Ist das
Bit gesetzt, wurde die Markierung erreicht. Am sichersten: Sie warten bis
die Markierung auftaucht, und dann, bis sie wieder beendet ist. Zu guter
Letzt müssen wir noch auf einen DMA-Fehler hinweisen: Beim Schreiben wird
das letzte Wort nicht mehr komplett geschrieben, beim Lesen wird das letzte
Wort verschluckt. Um nun Daten zu zählen, können wir auf den Index warten,
und dann solange Bytes zählen, bis diese wieder auftaucht. Die gelesenen
Bytes können wir beispielsweise zusammenaddieren, um eine Checksumme zu
bilden. Das haarscharfe Messen der Tracklänge ist ein vorbildlicher
Kopierschutz.

Hier nun die nötige Routine, welche die Bytes auf Spur 0 mit Hilfe der
Indexmarkierung zählt:

;
; Trackcount
;

drive	equ	$bfd199
status	equ $bfe001

start:
	lea	$dff000,a5
	move.w	#$4000,$9a(a5)		; IRQ aus
	move.b	#$ff,drive			; Alles auf high
	bclr	#7,drive
	bclr	#3,drive			; Laufwerk 0

;
; Auf eingelegte Diskette warten
;

wait_of_disk:
	bsr	innenstep
	move.l	#500,d5
	bsr	pause
	bsr	aussenstep
	move.l	#500,d5
	bsr	pause
	btst #2,status
	beq.B	wait_of_disk

;
; Auf Spur 0 fahren
;

steppo:
	btst	#4,status
	beq	am_ziel
	bsr	aussenstep
	moveq	#2,d5
	bsr	pause
	bra.B	steppo

am_ziel:
	move.w	#$7fff,$9e(a5)
	move.w	#%1001000100000000,$9e(a5)	; ADKCON
	move.w	#0,$24(a5)					; Keine DMA
	moveq	#0,d7
	move.b	$bfdd00,d0
index:
	move.b	$bfdd00,d0					; Warten auf Index
	btst	#4,d0
	beq.B	index
index2:
	move.b	$bfdd00,d0
	btst	#4,d0
	bne.B	index2
lesetest:
	move.w	$dff01a,d6
	btst	#15,d6
	beq.B	lesetest
	addq.l	#1,d7

	move.b	$bfdd00,d0
	btst	#4,d0
	beq.B	lesetest			; weiter, noch kein Index!

aus:
	move.w	#$c000,$9a(a5)
	rts
;
;	Anzahl Bytes auf Spur nun in d7
;

innenstep:
	bclr	#1,drive
	bclr	#0,drive
	bset	#0,drive
	rts

aussenstep:
	bset	#1,drive
	bclr	#0,drive
	bset	#0,drive
	rts

kopfoben:
	bclr	#2,drive
	rts

kopfunten:
	bclr	#2,drive
	rts

pause:
	move.l	$dff004,d7
	and.l	#$0001ff00,d7
	cmp.l	#$0001f000,d7
	bhi.B	pause
	lsr.l	#8,d7
	move.b	d7,d6
	add.b	#15,d6
sleep:
	cmp.b	$dff006,d6
	bne.B	sleep
	dbf	d5,pause
	rts


Nach Beendigung dieses Programms erhalten Sie die Anzahl der Bytes (noch im
MFM-Code) auf der Spur. Das müssen also etwas über 12000 Bytes sein.
Supergenau ist dieses Indexverfahren nicht. Eine bessere Methode ist das
Einlesen einer Spur mit der $4489-Synchronisation. Wir lesen dann mit
Absicht zuviel, nämlich $3800 Bytes. Die Daten, die direkt am Anfang
stehen, wiederholen sich dadurch zwangsweise. Aus diesem Grund läßt sich
die richtige Datenmenge leicht ermitteln. Wir suchen einfach nach der
ersten Wiederholung und berechnen dann den Abstand zum Anfang. Hier nun das
Listing zum Einlesen einer MFM-Codierten Spur mit Hilfe der
$4489-Synchronisation:

;
; Lesen einer MFM-Spur
;

drive	equ	$BFD100
status	equ	$BFE001

start:
	lea	$dff000,a5
	move.w	#$4000,$9a(a5)	; IRQ aus

	move.b	#$ff,drive
	bclr	#7,drive
	bclr	#3,drive		; Laufwerk 0
	move.l	#500,d5
	bsr pause

;
; Auf eingelegte Diskette warten
;

wait_of_disk:
	bsr	innenstep
	move.l	#500,d5
	bsr.W	pause
	bsr	aussenstep
	move.l	#500,d5
	bsr	pause
	btst	#2,status
	beq.B	wait_of_disk

am_ziel:
	move.w	#$7fff,$9e(a5)
	move.w	#%1001010100000000,$9e(a5)	;ADKCON
	move.w	#$4489,$7e(a5)		; Sync
	move.l	#daten,$20(a5)		; Zeiger auf Daten
	move.w	#$9c00,$24(a5)		; Starten
	move.w	#$9c00,$24(a5)		; (zweimal!)

warten:
	btst	#1,$dff01f
	beq.B	warten				; Warten, bis fertig
;
;
; Hier müßte nun die Auswertung folgen
;
;

aus:
	move.w	#$c000,$9a(a5)
	rts

innenstep:
	bclr	#1,drive
	bclr	#0,drive
	bset	#0,drive
	rts

aussenstep:
	bset	#1,drive
	bclr	#0,drive
	bset	#0,drive
	rts

kopfoben:
	bclr	#2,drive
	rts

kopfunten:
	bclr	#2,drive
	rts

pause:
	move.l	$dff004,d7
	and.l	#$0001ff00,d7
	cmp.l	#$0001f000,d7
	bhi.B	pause
	lsr.l	#8,d7
	move.b	d7,d6
	add.b	#15,d6
sleep:
	cmp.b	$dff006,d6
	bne.B	sleep
	dbf	d5,pause
	rts

	section mfm,data_c

daten:
	dcb.b	$3800,0



Bei diesem Progrämmchen spielt das Format an sich absolut keine Rolle,
solange es $4489-synchronisiert ist. Deshalb können wir nur durch Ausmessen
der exakten Spurkapazität einen DOS-Kompatiblen und sehr wirksamen
Kopierschutz realisieren, denn kaum ein Laufwerk schreibt exakt die gleiche
Datenmenge. Um den Schutz "aufzutragen", würde es also lustigerweise
genügen, die betroffene Diskette zu formatieren.


ANHANG

;
;  SpezialBase.i
;
;  Include-File für Leidorfs Spezial-Library
;  

        IFND    SPEZIAL_BASE_I
SPEZIAL_BASE_I   SET     1

        IFND EXEC_TYPES_I
        INCLUDE "exec/types.i"
        ENDC

        IFND EXEC_LISTS_I
        INCLUDE "exec/lists.i"
        ENDC

        IFND EXEC_LIBRARIES_I
        INCLUDE "exec/libraries.i"
        ENDC

        STRUCTURE SpezialBase,LIB_SIZE
        UBYTE   sb_Flags
        UBYTE   sb_pad
        ULONG   sb_SysLib
        ULONG   sb_DosLib
		ULONG	sb_IntuiLib
        ULONG   sb_SegList
        LABEL   SpezialBase_SIZEOF

SPEZIALNAME      MACRO
        DC.B    'spezial.library',0
        ENDM
        ENDC

;
;
;


;
; Spezial_lib.i Einsprünge
;

_LVOOpenAllLibs			EQU	-30		; Öffnen der wichtigsten Librarys
_LVOCloseAllLibs		EQU -36		; Schließen derselben
_LVOAutoRequestFront	EQU	-42		; Autorequest auf dem vordersten Screen
_LVOCheckFileSize		EQU	-48	; Um Dateigröße festzustellen








TTTTTTTTTTTTTTTTTTTTTTTTT        IIIIIIIIIIIIII     SSSSSSSSSSSSSSSSSSSSSSSSSSS
TTTTTTTTTTTTTTTTTTTTTTTTT        IIIIIIIIIIIIII     SSSSSSSSSSSSSSSSSSSSSSSSSSS
TTTTTTTTTTTTTTTTTTTTTTTTT        IIIIIIIIIIIIII     SSSSSSSSSSSSSSSSSSSSSSSSSSS
        TTTTTTTTT                  IIIIIIIIII       SSSSSSSSSS
        TTTTTTTTT                  IIIIIIIIII       SSSSSSSSSS
        TTTTTTTTT                  IIIIIIIIII       SSSSSSSSSS
        TTTTTTTTT                  IIIIIIIIII       SSSSSSSSSSSSSSSSSSSSSSSSSSS
        TTTTTTTTT                  IIIIIIIIII       SSSSSSSSSSSSSSSSSSSSSSSSSSS
        TTTTTTTTT                  IIIIIIIIII       SSSSSSSSSSSSSSSSSSSSSSSSSSS
        TTTTTTTTT                  IIIIIIIIII                        SSSSSSSSSS
        TTTTTTTTT                  IIIIIIIIII                        SSSSSSSSSS
        TTTTTTTTT                  IIIIIIIIII                        SSSSSSSSSS
        TTTTTTTTT                IIIIIIIIIIIIII     SSSSSSSSSSSSSSSSSSSSSSSSSSS
        TTTTTTTTT                IIIIIIIIIIIIII     SSSSSSSSSSSSSSSSSSSSSSSSSSS
        TTTTTTTTT                IIIIIIIIIIIIII     SSSSSSSSSSSSSSSSSSSSSSSSSSS