[comp.sys.amiga] BlitLab 1.2

rokicki@rocky.STANFORD.EDU (Tomas Rokicki) (06/23/87)

This and the next four messages contain BlitLab 1.2.  If you
are using or have used BlitLab 1.1, you need to look at this
update.  In this update, the line drawing algorithm is done
correctly, from information gleaned from a disassembly of the
ROMs.  Included in this shar archive is a uuencoded binary
and TeX-style documentation.

---start---
#! /bin/sh
: This is a shar archive.  Extract with sh, not csh.
echo x - README
cat > README << '20488!Funky!Stuff!'
BlitLab 1.2						22 July 1987

	This is release 1.2, which adds access to two additional blitter
flags, and corrects the line drawing algorithm.  It also includes the
blit.tex documentation.  If you are interested in drawing lines, this
program and its documentation now contains information which cannot be
found in any of the published documentation on the blitter.

	BlitLab is a program which lets you experiment with the blitter
to your hearts content, in relative safety.  It opens up a workbench
window with gadgets for all of the registers of the blitter, and allows
you to manipulate individual registers and perform blits on a magnified
bitmap.  This documentation is sketchy, but it probably won't get much
better.

	This program was written for a blitter presentation at the
BADGE meeting of 16 April 1987, and it met with such approval that I
decided to make it available over the net.  I wrote it for Manx C using
16 bit ints.  It should port to Lattice with a minimum of effort, but
it doesn't.

	The code itself may turn out to be interesting.  I wrote the
entire program in two days, but I tried to keep things clean and
modular.  Any comments on the code, either positive or constructive
criticism, is welcome.

	On to the program.  I'm going to describe operation of the
program starting in the upper left hand corner and proceeding across
and down.  I will not describe the operation of the blitter.

	The large dark area in the upper left is the bit map we are
going to play with.  It has a width of 96 bits, or 12 bytes, or 6
words.  It has a height of 32 rows.  Remember these numbers well.
You can draw in this area with the mouse and the two gadgets in the
upper right.  The topmost gadget is the Point/Box gadget.  When set
to point, you can draw individual pixels on the screen by clicking on
the pixels with the select button.  When set to box, you can draw a
filled rectangle by clicking on the upper left hand pixel of the box you
wish to draw with the select button, dragging the mouse and releasing
the select button over the lower left hand pixel.  The next gadget is
the Clear/Set gadget, allowing you to clear or set points.  Note that
this is not a paint program, and the interaction speed may not be as
fast as you are used to; no apologies.  It was only intended to draw
bits for the blitter.

	Below these two gadgets are two numbers.  The adrs number
holds the address of the word the cursor is currently pointing at,
relative to the beginning of the bit array.  (It is written as, for
instance, M+382, which means the address of the array plus 382 bytes.)
The shift value is the pixel, numbered from 0 at the left to 15 at the
right.  With these values, you can point at a pixel to get its address
to enter into a string gadget below.

	The Calc gadget looks at the values you have entered for the
blitter, and determines if any memory other than the bit array will be
modified.  If it will, it prints `Blit unsafe' on the title bar.  This
is a sanity check to keep you from crashing the system.  Carefully check
your values if this flag is on.  The fact that this gadget writes `Blit
unsafe' does not necessarily mean that the blit will destroy memory
outside of the array; careful use of a source operand and mask bits
might not pass this test but would be perfectly okay.  I have
allocated several extra rows above and below the bit array so minor
errors shouldn't crash the system.  Currently line mode is not checked,
so use it with care.

	The GO gadget performs the blit you have indicated.  After the
blit, the results are updated in the magnified bit array rather slowly.
It performs the same check performed by Calc before executing, and
complains similarly.  You can override the complaint by clicking on the
GO gadget again.

	Underneath the magnified bit map, on the left, are four gadgets
labeled SX, SY, EX, and EY.  These bits get the starting and ending
points for the line you wish to draw.  The X values should range from
0 to 95; the Y values from 0 to 31.  These are not checked.  Be aware
that these are not blitter registers; they are simply values that the
program will use in calculating the actual blitter register contents
necessary for the line draw.

	The (line)/LINE gadget turns on line mode.  The Setup gadget
takes the SX/SY/EX/EY values, and sets up most of the blitter registers
to draw the line automatically.  It does not set up the function
register, because there are different ways to draw lines.  For a solid
line, use ~AC+A; for a textured line, use ~AC+AB; for an XOR line, use
~AC+A~C, for instance.  It does not perform the blit, however.  Note
that if you want to draw a new line, you need to change the appropriate
SX/SY/EX/EY values, then click Setup, then click GO.

	The next two gadgets, W and H, hold the size of the current
blit.  The W value is in words, and the H value is in bytes.  Legal
values range from 1-64 for W, and 1-1024 for H.  (Of course, the useful
range within this program is much smaller.)

	Next to the H gadget are gadgets for the DESC, FCI, IFE, and
EFE bits of the gadget.  These can get set or cleared by selecting them.
Underneath these gadgets is the Function gadget.  In this gadget you
enter the function you want to perform.  0 is clear all, 255 is set all,
and other combinations can be entered directly as minterms.  For
instance, the A~C+~CA presented earlier means exclusive or the A and C
bits for the destination.

	The lower right hand corner contains the actual blitter registers
and their values in hexadecimal.  These are the values that are fed to
the blitter when you select GO.  As you enter new numbers into the string
gadgets corresponding to blitter registers, these values will change to
reflect the new values.  (Actually, they will only change if you hit
carriage return after entering the numbers into the string gadget; if you
simply select another gadget after entering data into a string gadget, the
old value will stay displayed until you select CALC or GO.)  This can be
used to calculate minterms, for instance.  Entering ABC+A~B~C+~AB~C+~A~BC
into the Function gadget hitting return will put 96 in the least
significant eight bits of CON0; this is the hexadecimal value reflecting
the above minterms.

	At the lower right is a table of string gadgets.  Each row is for
a single DMA channel.  The first column is the USE bit; is that channel
on or off?  Y means on, N means off; selecting the gadget toggles it.  The
next column contains the start address for DMA.  These numbers (as in the
remaining string gadgets) can be entered in decimal, hexadecimal (preceded
by a $), binary (preceded by a %), or as an offset from the start of the bit
array (preceeded by M+).  M alone represents the beginning of the blit
array.  The third column is the modulo value which is added to the DMA
channel pointer at the end of each row; this value is in bytes and can be
negative.  The fourth column is the data register; if a channel is turned
off, its data register can be preloaded with a value, and then it functions
as a constant.  For instance, to fill a block of memory with the value
$E931, simply preload the A data register with this value, turn on only the
D channel, and blit with the function A.  Finally, the last column is the
amount to shift the A and B operands, to the right, before using them.
Finally, in the lower left corner of the DMA channel box are string gadgets
for AFWM and ALWM.

	This program has been tested fairly carefully.  Therefore, if the
blitter appears to do something it shouldn't, you probably have set up the
registers wrong.  Remember, the Amiga blitter is a word blitter, not a bit
blitter.  It can be made to look like a bit blitter with some work.  It is
possible, however, that errant blits can muck up some system memory, or
some internal variable memory, so if things really seem awry, reboot and
go back into the program.  Try the same values again, and see if they work
this time.

	Blitter documentation is not included here currently.  Sometime
in the next week I will finish my blitter documentation; that documentation
will tell you more than you ever wanted to know about the blitter.  So,
until then, I remain:

Tomas Rokicki
Box 2081
Stanford, CA  94305
723-1646 (office), 326-5312, -5681 (home)
rokicki@sushi.stanford.edu
...lll-crg!decwrl!sushi.stanford.edu!rokicki
20488!Funky!Stuff!
echo x - bits.c
cat > bits.c << '20488!Funky!Stuff!'
/*
 *   This routine handles the display of the bit array in BlitLab.
 */
#include "structures.h"
/*
 *   This is the address of the real bits we operate on.
 */
extern short int *realbits ;
static int safearr[192] ;
allocbitmem() {
   extern void *allocmem() ;

   realbits = (short *)(allocmem(1000L, MEMF_CHIP | MEMF_CLEAR)) + 150 ;
}
pdot(x, y, on)
int x, y, on ;
{
   int off = (x >> 4) + y * 6 ;
   int bit = 1 << (15 - (x & 15)) ;

   if (on) {
      if ((realbits[off] & bit) == 0) {
         realbits[off] |= bit ;
         safearr[off] |= bit ;
         color(WHITE) ;
         fbox(x * 6 + HBITSTART, y * 3 + VBITSTART + 1, 4, 2) ;
      }
   } else {
      if (realbits[off] & bit) {
         realbits[off] &= ~bit ;
         safearr[off] &= ~bit ;
         color(BLACK) ;
         fbox(x * 6 + HBITSTART, y * 3 + VBITSTART + 1, 4, 2) ;
      }
   }
}
preg(x1, y1, x2, y2, on)
int x1, y1, x2, y2, on ;
{
   int i, j ;

   for (i=x1; i<=x2; i++)
      for (j=y1; j<=y2; j++)
         pdot(i, j, on) ;
}
/*
 *   This routine writes out the new position to the screen.
 */
updatepos(x1, y1)
int x1, y1 ;
{
   char outbuf[4] ;

   sprintf(outbuf, "%3d", ((x1 >> 3) & ~1) + y1 * 12) ;
   drawtext(HLMGSTART+28, VLMG3+6, outbuf) ;
   sprintf(outbuf, "%2d", x1 & 15) ;
   drawtext(HLMGSTART+12, VLMG4+6, outbuf) ;
}
updatebits() {
   int x=HBITSTART, y=VBITSTART+1 ;
   register short *p1 = realbits, *p2 = safearr ;
   int i = 192 ;
   int rc = 6 ;
   int bit ;

   while (i-- > 0) {
      if (*p1 == *p2) {
         p1++ ;
         p2++ ;
         x += 6 * 16 ;
      } else {
         bit = 0x8000 ;
         while (bit != 0) {
            if ((*p2 & bit) != (*p1 & bit)) {
               color((*p1 & bit) ? WHITE : BLACK) ;
               fbox(x, y, 4, 2) ;
            }
            bit = (bit >> 1) & 0x7fff ;
            x += 6 ;
         }
         *p2++ = *p1++ ;
      }
      if (--rc == 0) {
         rc = 6 ;
         x = HBITSTART ;
         y += 3 ;
      }
   }
}
20488!Funky!Stuff!
echo x - blit.tex
cat > blit.tex << '20488!Funky!Stuff!'
%
%   Blitter manual
%
%\magnification=\magstep1
%
%   We need a macro to do double column output.  These are lifted
%   from the TeXbook, with only minor modifications.
%
\newdimen\fullsize\fullsize=7.2truein\hoffset=-0.35truein
\hsize=3.45truein  % this is awfully narrow; will it work?
\vsize=9truein
\def\makefootline{\baselineskip24pt\hbox to\fullsize{\the\footline}}
\let\lr=L \newbox\leftcolumn
\output={\if L\lr
  \global\setbox\leftcolumn=\columnbox \global\let\lr=R
 \else\doubleformat\global\let\lr=L\fi
 \ifnum\outputpenalty>-20000\else\dosupereject\fi}
\def\doubleformat{\shipout\vbox{\makeheadline
   \hbox to\fullsize{\box\leftcolumn\hfil\columnbox}
   \makefootline}
 \advancepageno}
\def\columnbox{\leftline{\pagebody}}
%
%   And now, some more typical macros.
%
%\special{landscape()}
\raggedbottom
\def\section#1\\{\goodbreak\vskip\baselineskip\leftline{\bf #1}\vb}
\def\vb{\vskip\baselineskip}
\def\b #1(){{\bf #1()}}
\def\not{$\neg$}
\def\xor{$\oplus$}
%
%   Normally ~ is a tie.  We need it often enough to imply logical
%   negation that we redefine it, making it an active character.
%
\catcode`\~=\active\let~=\not
\def\reg#1/{{\tt #1\space}}
%
%   Some verbatim macros:
%
{\obeyspaces\gdef {\ }}
\def\begverb#1 {\goodbreak\vskip\baselineskip
\begingroup\def\par{\leavevmode\endgraf}%
\catcode`\\=12\catcode`\{=12
\catcode`\}=12\catcode`\$=12\catcode`\&=12
\catcode`\#=12\catcode`\%=12\catcode`\~=12
\catcode`\_=12\catcode`\^=12\obeyspaces\obeylines\tt
\parindent=0pt\catcode#1=0}
\def\endverb{\par\endgroup}
%
%   Now the report itself.
%
\ \vb
\centerline{\bf BlitLab and the Amiga Blitter}
\centerline{Tomas Rokicki}
\centerline{21 May 1987}
\vb
%
%   Prefatory apology
%
\section Preface\\
Due to time constraints, this is not yet a complete report.  There are
still a couple of things which are sketchy.  But pretend it is complete;
complain about missing items so I can add them; correct any inaccuracies
in the report.

This manual will not make much sense without
a working copy of BlitLab, as most
of the documentation is in the examples provided.  So, if you don't have
it yet, go out and get it.  It is on disk 69 of Fred Fish's Freely
Redistributable Software Library.  Or, just ask me for a copy.
%
%   Introduction
%
\section Introduction\\
So you have pored over the Hardware Manual and the ROM Kernel Manual, and
you cannot find the information you need on the blitter.  Well, never
fear; all the information you should ever need about the blitter is
contained in this one handy document.

All information below was derived from the Hardware Manual, ROM Kernel
Manual, and a lot of empirical testing.  Using the blitter directly,
as described in this report, however, bypasses the layers library.
If you want to use these techniques for graphics,
open your own custom screen; if you open any windows on this screen,
be careful to not destroy the graphics rendered by Intuition.
%
%   Introduction to the Hardware
%
\section The Hardware\\
The blitter comprises part of the Agnes chip in the Amiga, and can only
access the lower 512K (chip) memory.  To the 68000, it appears as a
set of approximately twenty sixteen bit write only registers.  It can
use memory at twice the bandwidth of the 68000, or 3.6 megabytes per
second (although, as we shall see, it doesn't always run this fast.)
Any video memory accesses can slow the blitter down, whether for
screen refresh or for the 68000.  For instance, the standard two bit
deep high resolution workbench screen can slow the blitter down by
approximately 30\%.  A low resolution single bit plane screen can slow it
down by about 8\%.  A high resolution four bit plane screen can slow down
the blitter by about 60\%.  The blitter is so fast, however, that even
with this handicap it performs its tasks many times faster than the 68000.

The first thing a programmer of this chip must realize is that
the Amiga blitter is not a `bit' blitter; rather, it operates on words.
With the appropriate programming, it can manipulate arbitrary bit
rectangles.  This fact must be kept in mind when programming the blitter.

The blitter uses four DMA channels to perform its work; these are called
A, B, and C (sources) and D (destination).  Any or all of them may be
disabled independently.  The destination can be calculated from any of
256 possible logical equations on A, B, and C.  The A and B sources can
be shifted up to 15 bits to the right, and the first and last word in a
line from the A source can be masked by a constant.  Each of the four
channels has its own modulo.  The blitter also has an area fill and a
line draw mode.

%   BlitLab
\section BlitLab\\
To allow easy experimentation with the blitter, I have written a program
called BlitLab.  This program provides a laboratory in which you can
play with the blitter registers in a safe manner.  It is over 1300 lines
of code that I wrote in under two days, so beware of any bugs.

The blitter has a much greater potential for damage to system memory
than the 68000 does, since once started it performs operations on large
areas of memory without interruption or instruction checking.  If the
68000 starts executing random data as instructions, it usually very
quickly executes an odd-address or illegal op-code trap.  The blitter,
on the other hand, could easily wipe out kilobytes of data before the
system noticed anything was amok.  BlitLab therefore carefully checks
the values you have entered to be sure that system memory will not be
overwritten.  Only if it will not be are you allowed to do the blit.
That is, except for line mode; it is so complicated to check for line
mode validity, that I let you trash memory with it.  But more on that
later.

%   Getting Access to the Blitter
\section Getting Access\\

There are currently four ways you can use the blitter.  Some work better
than others.  The first way is to use the standard ROM Kernel routines
for graphics.  This is the simplest and most reliable method; future
blitters and operating systems will not disrupt your code.  I am not
going to discuss this approach here, because I don't want to, and all of
that information is in the ROM Kernel Manuals.  The second method is
to arbitrarily write to the blitter registers, ignoring Intuition
and friends.  This is a good way to make enemies; you can trash disks
as well as system memory, but it makes for good laughs on those slow
winter nights.  Just pop some random values into those blitter registers,
and watch the pyrotechnics fly!

The third method is a variation of the second, but you politely request
permission from Intuition first, by calling \b OwnBlitter().  This routine
notifies the Amiga that you want exclusive access to the blitter, and you
don't want anyone else playing with it.  After this
call returns, you can almost use the blitter.  Unfortunately, someone
else may have already given the blitter something to do that hasn't completed;
therefore, you should call \b WaitBlit() before actually mucking with
the registers.  This second routine blocks until the blitter is actually
finished with its work.  Once \b WaitBlit() returns, you are free to do
what you like with the blitter.

While you have the blitter, you must remember that Intuition cannot
use it.  Therefore, \b Text() calls will not work, and your debug
printf's will block if they are written to the screen, for instance.
The blitter is used for disk I/O and most user interaction like gadgets,
so tying
up the blitter for long periods of time (longer than, say, a few
milliseconds) is considered highly unfriendly.  Tying up the blitter
for a second or more is grounds for lynching.

When you are finished with the blitter, you should call \b DisownBlitter(),
to allow Intuition to do what it likes.  Remember, however, that
\b DisownBlitter() might return before the blitter is finished with your
last operation, so before you use any data created by the blitter,
call \b WaitBlit(), to allow the blitter to finish.
This last point is worth rereading, as it is often
the source of some subtle bugs.

Thus, your code might look like the following:

\begverb{`\$}
OwnBlit() ;
WaitBlit() ;
/*
 *   here you can muck with the blitter
 *   until it falls off . . .
 */
draw_my_polygons() ; /* for example */
DisownBlitter() ;
/*
 *   Now we want to examine the memory region
 *   the blitter played with.
 */
WaitBlit() ;
copy_to_disk() ;    /* for example */
$endverb
%
%   QBSBlit   add more here at some later date
%
There is another way to gain access to the blitter; you can use the
supplied blitter queue routines.  This is described (perhaps inadequately)
in the ROM Kernel Manual, Volume 1, pages 2-62 through 2-65.  As of yet,
I haven't had reason to use these routines; perhaps a later edition of
this manuscript will have information on them.
%
%   DMA channels
%
\section DMA Channels\\

So now we have control of the blitter; we can write to all of its
registers and do whatever we like.  Before we get into exactly what
we can do, let me describe the blitter DMA channels.

As mentioned, the blitter has four DMA channels, A, B, C, and D.
These are shown in the lower right hand corner of the BlitLab display.
What, you say you are not in BlitLab yet?  Go to your Amiga, plug in
a disk with BlitLab on it, and {\tt run blitlab}.  The rest of this
manual assumes that you have done this.  If you are running an interlaced
screen or odd colors, you might find the BlitLab display visually jarring;
I recommend rebooting with the default WorkBench configuration and running
BlitLab from there.

Back to the DMA channels.
The first three are always sources, the last is always a destination.
You can use any combination of the four channels, from none of them
to all four.  Each of the four channels has an 18 bit address pointer which
points to the memory it will use or modify.  The least significant bit
of the low order word, and the most significant fifteen bits of the high
order word are ignored; this leaves 18 bits of a 32-bit pointer.
Each channel also has an independent 15 bit
signed modulo (in bytes, with the least significant bit again ignored.)
For the three source DMA channels, there is also a data register which
you can preload with constant data if the DMA channel is turned off.

The DMA channels share a width and a height register.  The
width is in 16 bit words, and can take a value from 1 to 64.  The height
is in pixels, and can take a value from 1 to 1024.  Thus, the largest
rectangular field on which the blitter can directly operate is 1024 by
1024.  However, larger fields can be handled by splitting a blit into
smaller blits, and using the modulo fields appropriately.

A key thing to remember here is that the width is in words, the modulos
and pointers are in bytes, and the height is in pixels.  You must remember
this.

\section Block Clear\\

The destination can receive any logical combination of its three source
operands.  Let's start experimenting.  First, we'll try to clear memory.
First we will try to clear memory.  The large black rectangle in the upper
left hand corner of the BlitLab window is the bit region we can experiment with.
Let's set some random bits to make sure clear is working.  Select the gadget
currently marked `Clear'; it should toggle and be marked `Set'.  Now, move
the mouse into the black rectangle, hold down the left mouse button, and
move the mouse around.  Set pixels until you tire of the novelty.  Note
that as you move the mouse, the Adrs and Shift fields change; these will
be useful in a second.

Once you have a reasonable number of pixels on the screen, you are ready
to begin.  To enter data into a string gadget, select the string gadget,
backspace over the old data,
type new data, and then hit return.  First, in the gadget marked W,
enter the number 6.  The field we are working on is 96 pixels wide by
32 pixels high; that is six words by 32 rows.  Enter 32 into the H gadget.
Now, turn on the D DMA channel by selecting the gadget marked `N' in
the D row; it
should toggle and show `Y'.  Enter M into the PT column; this is the
symbolic name for the address of the rectangle we are experimenting
with.  Enter 0 into the `Function' gadget near the center of the window.
We are ready to go.  At this point, select the `Calc' gadget.
BlitLab will read the values you have entered and make sure that you
are not going to clobber the system.  If the blit is safe, it will do
nothing; otherwise it will print an error message on the window title
bar.  If you get the error message, it is likely that your machine will
crash if you ask it to perform the blit.

Otherwise, go ahead and select the `GO' gadget.  The pixels you so
laboriously set should disappear.  Congratulations, you have performed
your first blit!

You may have noticed that the pixels actually disappeared rather
slowly.  If this is the case, you have a defective Amiga.  No, actually
this is an artifact of the program; BlitLab is performing the blits
in some other memory somewhere, and then copying and expanding the bits
to the rectangle displayed on the screen.  It is this updating and
expanding that is slow, not the blit.  So don't be alarmed.

Before we get steeped in the explanations, let's experiment some more.
Set the `Function' to 255, and hit `GO' again.  This should set all of
the pixels in our rectangle.  Set it back to 0, and set the height to
16.  Now, only half of the rectangle is cleared.  Set the modulo (for
the D channel) to 6 (bytes), and reduce the width to 3 (words, remember?)
Set the `Function'
back to 255, and `GO'.  Now the upper left corner of the rectangle is
cleared.

Oops, we are probably getting ahead of ourselves here.  For now, just
take my word for the fact that setting the function to 0 clears the
destination, and setting the function to 255 sets all of the bits in
the destination.  I am sure the width and height (that's the W and H
gadgets) explain themselves well enough, as does the address pointer
(that's PT.)  But, how is the modulo interpreted?

The algorithm the blitter uses to do a blit looks something like this:

\begverb{`\$}
doblit(daddress, height, width, dmodulo)
char *daddress ;
int height, width, dmodulo ;
{
   int i, j ;
    for (j=0; j<height; j++) {
      for (i=0; i<width; i++) {
         *daddress = function() ;
         daddress += 2 ;
      }
      daddress += dmodulo ;
   }
}
$endverb
Here \b function() calculates the data to be stored; it will be described
later.  A row consists of `width' 16 bit words, each of which are modified
in turn, incrementing the address pointer to the next word.
After a row, the value `dmodulo' is added to the address.  Thus, if your
bitmap is `n' words wide, $2*\hbox{width}+\hbox{dmodulo}$ should always
equal `n'.

\section Memory Copy\\

Clear the rectangle again.  This time do it with the mouse; select the
gadget marked `Point', it should toggle to `Box'.  If the gadget below
this says `Set', toggle it to `Clear'.  Now, move the mouse
to the upper left corner of the rectangle, press the left mouse button,
and drag the mouse to the lower right corner of the rectangle, holding
the mouse button down.  Then release the mouse button; the rectangle
should clear.  If it doesn't clear, do it again, being careful where you
press and release the mouse.  Now we are going to start experimenting
with using multiple DMA channels.

Turn on the A DMA channel, and set its pointer to `M'.  Set the D
channel pointer to `M+192'; this is the bottom half of the rectangle.
If you move the mouse to the middle of the left edge of the rectangle,
within the rectangle, the Adrs field should display `M+192'; this is
an easy way to find the address of a particular portion of the
rectangle.  Set the modulus of the D channel back to 0, set the W field
to 6, and the H field to 16.  Set the `Function' to `A'.  Now, draw
some random pixels in the upper half of the rectangle.  (You will need
to toggle both the `Box' and `Clear' gadgets to do this.)  Then, hit
`GO'.  The upper half of the rectangle should be duplicated in the
lower half!  Try setting the function to `~A' (that's tilde A
on the Amiga keyboard), and see what happens.  You are actually copying
memory!

\section Setting Memory to a Particular Value\\

You can also set memory to a particular value.
Turn off the A DMA channel, but leave the
function set to A.  Now put the value \$5555 in the ADAT gadget (type
the dollar sign, please.)  Hit `GO'.  You should get stripes in the
lower portion of the rectangle.  What is happening here is that the
A DMA channel is turned off, so no memory is being loaded into the A
channel.  Instead, the value in the A data register is being used.
This is a quick way to set a memory region to a particular value.
The important point to remember here is that you can use a
channel as a constant if you turn it off and preload its data register.

\section More Complex Operations\\

Let's try some more complex operations on memory, now.  We are going
to divide our rectangular region into four areas, one for A, one for
B, one for C, and one for D.  Set W to 3, all four modulus values to
6, and the PT gadgets for A, B, C, and D to M, M+6, M+192, and M+198,
respectively.  Now draw some random things in the regions corresponding
to A, B, and C.  I recommend, for instance, filling the left half of
A, the top half of B, and a smaller rectangle in the middle of C.
You can use the `Box' and `Set' drawing modes.  Turn all four DMA
channels on, and set the function to 
ABC+A~A~B+~AB~C+~A~BC.  Execute `GO'; there
should be eight distinct regions.  (The function is equivalent to
A\xor B\xor C).

Now you can experiment with the various possible functions.  Try the
function AB; is the result only those bits that have both A and B set?
Now try A+B, this time either A or B set should result in a destination
bit on.  And so on and so forth.

So how are these function codes computed?  Actually, it is quite simple.
(Have you noticed how I tend to overuse the word `actually'?  I suppose
that is what comes of being an engineer.)
As you enter a new function, note how the least significant four nybbles
of CON0 (lower left box on the display) change; this is the hexadecimal
representation of the equation you entered.  You must write your function
as a sum of products; the products have the values:

$$\vbox{\halign{\hfil#\quad&{\tt #}\qquad&\hfil#\quad&{\tt #}\qquad&
\hfil#\quad&{\tt #}\cr
A&F0&B&CC&C&AA\cr
~A&0F&~B&33&~C&55\cr
AB&C0&AC&A0&BC&88\cr
A~B&30&A~C&50&B~C&44\cr
~AB&0C&~AC&0A&~BC&22\cr
~A~B&03&~A~C&05&~B~C&11\cr
ABC&80&AB~C&40&A~BC&20\cr
~ABC&08&A~B~C&10&~AB~C&04\cr
~A~BC&02&~A~B~C&01\cr}}$$

To sum them, simply `or' them.  (Do not add them.)  Thus, A\xor B is
A~B+~AB or {\tt 30}$\vee${\tt 0C} or {\tt 3C}.  It is usually easier just to
enter the equation into BlitLab and read the result, however.

\section Shifts and Masks\\

After you have had your fill of experimenting with the logic equations, we
can proceed to shifts and masks.  Both the A and B DMA channel have
independent shifts.  In addition, the A channel has a first word and last
word mask.  These are essential in making our word blitter appear to
be an actual bit blitter.  First, using the same set-up you had for the
previous experiment, set the A shift to 3.  (The left half of the A
region, top half of the B region, and a middle section of the C region
should be set at this point.)  Shifts values are always to the
right.  Set the function to our good old ABC+A~B~C+~AB~C+~A~BC, and `GO'.
You should notice the shift of the A operand, and also notice that zeros
are shifted in from the left.  Now reset the A shift to 0, and set the
B shift to 3.  You should get some strange results; there are a few pixels
that should be set, but aren't, and a few set that should be clear.
Yes, your Amiga is
working.  Let's examine exactly what's happening here.  (We are also going
to update our blitter algorithm.)

To illustrate the `problem' best, fill the top half of the A region, and
clear the bottom half.  Set the function equal to `A', and set the A shift
to 3.  Hit `GO', and let me explain what you have on your screen.
The destination looks exactly like the source, except the first three pixels
in the top row are clear, and the first three pixels in the first row
which is supposed to be totally clear are set!  This is because of the way
the blitter shifts.

There is an internal register which holds bits between data fetches for
the A and B registers.  This register is initialized to all zeros at the
beginning of a blit (it is not the Data register, which is ignored on
all blits with that particular DMA channel turned on.)  As a word is fetched,
it is shifted to the right by the number of bits in the shift count.
The high bits come from this internal data register, and the low bits
are shifted out into the internal data register.  In other words, the
algorithm looks something like this:


---end---

rokicki@rocky.STANFORD.EDU (Tomas Rokicki) (06/23/87)

---start---
\begverb{`\$}
doblit(aaddress, height, width, amodulo, sh)
char *aaddress ;
int height, width, amodulo, sh ;
{
   int i, j ;
   int prev_data, data ;
    prev_data = 0 ;
   for (j=0; j<height; j++) {
      for (i=0; i<width; i++) {
         data = ((prev_data << 16)
                | *aaddress) >> sh ;
         prev_data = *aaddress ;
         function(data) ;
         aaddress += 2 ;
      }
      aaddress += amodulo ;
   }
}
$endverb
So, as you can see, things work nicely along the row.  The first time
around, the most significant bits of the data word get shifted right
and used.  The next operation uses the least significant bits of the previous
data word and the most significant bits of the current word, as it is
supposed to.  The only difficulty appears across rows.  For the first
word in a subsequent row, the low order bits of the last word in the
previous row are used, rather than shifting in zeros as happens on the first
row.  Things start to get a bit hairier.

But all is not lost.  Using the A register's ability to mask the first
and last word in a row, we can zero out any words we want, even if we
turn off the A DMA channel.  For instance, using the current settings of
BlitLab, set the least significant three bits of the ALWM to 0.  (Note
that {\it these} are the bits we need to mask out; the masks are applied before
the shifting.  In addition, the FWM is applied only to the first word in
each row; the LWM is applied only to the last word; if they are the same
word, both masks are applied.)  Redo the blit, and now things
appear to be working correctly.

\section Copying Arbitrary Regions\\

We will now attempt to move a rectangular array of bits from one random
bit location to another.
To set things up, clear out the entire rectangle.  Now, draw an ellipse
in the upper half region; try to get it close to the four borders.
Set the entire lower region, and reset an X across the entire region.
Reset all of the modulos to 0, and the width to 6.  Set the A and B source
addresses to M, and the C and D addresses to M+192.  We shall attempt to
move the portion of the ellipse from bit positions 2 through 28 to bit
positions 13 through 39 in the destination, leaving all the other bits
in the destination unchanged.  The source spans only two words, but
the destination spans three, so the blit width must be 3.  For some
other blits, the source might span more words than the destination;
the width must always be the maximum of the two.  So set the width to
3, and all modulos to 6.

The A channel is going to function as a mask.  Wherever the bit in the
A channel is set, we will copy the source to the destination; where the
bits are clear, the destination must not be changed.
The B channel will be used to actually fetch the source bits,
the C channel will
read the destination, because we will need to merge the destination
with the new source before writing, and the D channel will do the writing.
So, set the B address to M, and the C and D addresses to M+192.  Turn
off the A channel, but turn all three others on.  Set the A data to
\$FFFF, and the B shift to 11.  Note that we are using the A channel
as a constant; even though it is a constant, the mask registers will
still mask out bits of that constant.

Now we need to figure out how we are going to get A to mask out only
those bits we need to change.  Since our destination is the one which
spans three words, A must track it, so set the shift of A to zero,
and the FWM to zero for those bits of the first word of the destination
which are to be left alone.  In our case, we wish to leave the first 13
bits alone, so we use a value of \$0007.  The LWM gets set to zero for
the bits of the last word, similarly, yielding a value of \$FF00.
Note that if the source were three words and the destination two, the
A channel would have to track B.  In this case, you would set the shift
for A the same as the shift for B, and set the masks to mask out the
particular bits in B which will not be used.  We are almost ready.

Let's think about the function we need now.  For where the A bits are
masked out, or zero, we need to leave the destination alone; this gives
us the minterm ~AC.  Where A bits are set, we pass B through unchanged;
this gives us AB.  Summing these, we enter AB+~AC for our function, and
hit `GO'.  Carefully check out the picture; it should have worked.
We can also complement on the copy using the function A~B+~AC.  The
function AB+C provides an `or' draw, and the function AB~C+A~BC+~AC
provides an exclusive or copy.  You might try these.

Whew!  That's a lot.  You might take a breather here.  Then, later,
come back and reread the previous paragraphs, and play with BlitLab
some more.  It's really quite simple once you get the hang of it.
It is interesting to note that it required the full functionality of
the blitter---all four channels, shifts and masks---just to do an
arbitrary bit rectangle copy.  These are the difficulties you run into
when trying to make a word blitter perform as a bit blitter.

\section Decrement Mode\\

Sometimes the source and destination of your blits will overlap.  If
the destination comes at a lower memory address than the source, everything
will work fine.  However, if the destination comes at a higher memory
address and overlaps a source, a portion of the source will be overwritten
by the destination before it can be used as source, so the blit will not
perform as expected.  The blitter has a special flag which solves this
problem.  This flag puts the blitter in the decrement mode, where addresses
are decremented instead of incremented as the blit proceeds.
Toggle the gadget `(desc)', it should become
`DESC', indicating that it is set.  If you use this mode, you must
initialize the addresses to the end of the source or destination block,
and use negative modulo values.  The W and H must stay positive.
Try it.  Turn on the A and D channels, and turn off the B and C.
Set the A channel to M+190, and the D channel to M+382.  Set the
function to A, the modulos to 0, the width to -6, and the height to
16.  Set the FWM and LWM back to \$FFFF.
Draw some random pattern in the upper half of the rectangle,
insure that the DESC flag is set, and `GO'.  It should work as before.
Now set D to M+202, and watch the pattern step down with each blit.

%   Area Fill
\section Area Fill\\

Everything we have dealt with so far has been strictly data movement
and strict logical equations.  Now we examine one of the more esoteric
abilities of the blitter---area fills.  This feature is actually quite
easy to demonstrate, but it only works in the descending mode.  Set
up BlitLab as follows:  Channels A, D on; A address of M+190, D
address of M+382; W of 6, H of 16, modulos of 0, function of A, DESC
on.  Clear the rectangular array, and draw two vertical lines in
the top half of the rectangle, separated by at least one pixel.
If the lines are not exactly straight up and down, that's okay, just
insure that there is only one pixel set per row per line.  (You might
have to go to Point Clear mode and pick out a few pixels for this.)
Turn on the `(ife)' flag (inclusive fill enable), and `GO'.  The area
between the two lines should be filled in the lower portion of the
rectangle.

Now turn on the `(fci)' flag (fill carry in), and `GO'.  This time,
the area outside the two lines are filled.  The fill carry flag is
toggled for each bit seen, and if it is set, the bits in the destination
are set.  Now, turn off the `(fci)' flag, and turn on the `(efe)' flag
(exclusive fill enable.)  The area between the two lines is again filled,
but this time the line on the trailing edge of the fill was deleted.
This is useful for narrower fills.

Now draw a third vertical line between the previous two, and hit `GO'.
Note how the fill is indeed performed correctly, and the FCI bit is
restored to its set value at the beginning of each row.  Accidentally
set a random bit somewhere in A, and observe the effect is has on the
fill.  Now you know why each line must be only one pixel wide.

You can still perform any operation on the A, B, and C sources before
the area fill; the lines in the result will be used.  For instance,
you can area fill based on only a particular color of line, by setting
A, B, and C to the bit planes of the display, and setting the function
to one which selects only the appropriate color.

%   Line Mode
\section Line Drawing\\

This area is the sketchiest part of my knowledge.  I have actually gotten
the blitter to draw lines, but it took a lot of time and effort.  The
Hardware and ROM Kernel Manuals are incorrect in some of their assertions;
I had to disassemble part of the ROM to determine exactly how to draw
lines.  For
brevity, the following is an algorithm which will draw a line from
x1, y1 to x2, y2 on a window at m which is wx by wy pixels.  X and Y
are used to hold the slope values for the line, and assist in finding
the quadrant the line is to be drawn in.  Note how the flags (fci),
(ife), and (efe) are used to select the particular quadrant.

\begverb{`\$}
doline(x1, y1, x2, y2)
int x1, y1, x2, y2 ;
{
   int x, y ;
   int X, Y ;
   int q = 0 ;
   int t ;

   x = x2 - x1 ;
   y = y2 - y1 ;
   if (x < 0)
      X = - x ;
   else
      X = x ;
   if (y < 0)
      Y = -y ;
   else
      Y = y ;
   if (x > 0) {
      if (y > 0)
         q = (X > Y ? 1 : 0) ;
      else
         q = (X > Y ? 3 : 4) ;
   } else {
      if (y > 0)
         q = (X > Y ? 5 : 2) ;
      else
         q = (X > Y ? 7 : 6) ;
   }
   if (Y > X) {
      t = X ;
      X = Y ;
      Y = t ;
   }
   blit.height = X + 1 ;
   blit.apt = 4 * Y - 2 * X ;
   if (2 * Y - X < 0)
      blit.sign = 1 ;
   else
      blit.sign = 0 ;
   blit.amod = 4 * (Y - X) ;
   blit.bmod = 4 * Y ;
   blit.line = 1 ;
   blit.efe = (q & 1) ;
   blit.ife = (q & 2) >> 1 ;
   blit.fci = (q & 4) >> 2 ;
   blit.adat = 0x8000 ;
   blit.bdat = 0xffff ;
   blit.ash = x1 & 15 ;
   blit.cpt = blit.dpt = m +
      ((x1 >> 3) & ~1) + y * (wx >> 3) ;
   blit.cmod = blit.dmod = wx >> 3 ;
   blit.width = 2 ;
   blit.usea = 1 ;
   blit.useb = 0 ;
   blit.usec = 1 ;
   blit.used = 1 ;
}
$endverb
All of these calculations and initializations are done automatically by
BlitLab.  All you need do is enter the starting x and y and ending x
and y values into SX, SY, EX, and EY, respectively, and then hit `SETUP'.
(The x values can range from 0 to 95; the y values from 0 to 31.  If you
exceed these ranges, you will walk on system memory, and BlitLab won't
check line mode!)
We do not set the `function' variable in the above routine or in
BlitLab, because there
is more than one way to draw a line.  As the line is being drawn, the
bit set in the A register moves across and wraps around; this is the bit
that might be set.  The original destination is available from the C
channel, and the B channel provides a mask.  Thus, to just draw a solid
line, you would use the equation A+~AC (if A is set, draw a bit, otherwise
pass the destination through unchanged.)  Try it.  To draw an exclusive
or line, use A~C+~AC.  To draw a textured line, use AB+~AC, and put your
texture in B.  Note how the A and B address registers are used as
accumulators instead of address registers.

There is also an option to draw a line with only one bit set per horizontal
row; this is essential for drawing polygons to be filled later.  If you
set the `(desc)' flag, the lines will be drawn this way.
%   Speed
\section Speed\\

So, all of those fancy operations are fine and dandy, but just how fast
is the blitter, anyway?  This depends entirely on which DMA channels
are turned on.  You might be using a DMA channel as a constant, but unless
it is turned on, it does not count against you.  The minimum blitter
cycle is four clocks; the maximum is eight.  Use of the A register is
always free.  Use of the B register always adds two clocks to the
blitter cycle.  Use of either C or D is free, but use of both adds
another two clocks.  Thus, a copy cycle, using A and D, takes four
clocks per cycle; a copy cycle using B and D takes six clocks per
cycle, and a generalized bit copy using B, C, and D takes eight clocks.
When in line mode, each pixel takes eight clocks.

The clock is the 7.18 MHz system clock.  To calculate the total time
for the blit in microseconds, after setup, you use the equation
$$t={nHW\over 7.18}$$
where $t$ is the time in microseconds, $n$ is the number of clocks
per cycle, and $H$ and $W$ are the height and width of the blit,
respectively.

Actually, this is a minimum time, which is strictly impossible.
Display data fetches, 68000 cycles, and other operations can steal
cycle bandwidth away from the blitter.  One way to eliminate most of
this overhead is to call the macro {\tt OFF\_DISPLAY}
which turns off the display; this is not a friendly thing to do,
however.  Don't forget to call {\tt ON\_DISPLAY} after the blit
is finished!

\section Blitter Registers\\

So far we've discussed virtually every aspect of the blitter, except
exactly how its registers are organized, and how one actually stuffs
these registers.  Well, the blitter is accessible from the custom
hardware include file {\tt hardware/custom.h}, and makes available
20 write-only 16 bit registers, eight of which are organized as four
32 bit registers.  Full documentation is in the Hardware
Reference Manual, but I've summarized them here, as they are available
from C.  You might have noticed how the register contents block on
the lower left hand portion of the window changes as you set various
blitter parameters; this is an easy way to calculate register settings.
\vb{\leftskip=\parindent\parindent=-\parindent
\reg custom.bltcon0/  This sixteen bit register contains the A shift
value in its top four bits and the function code in its low eight bits.
Bits 8 through 11 are used to indicate which DMA channels are on; 8,
9, 10, and 11 correspond to DMA channels D, C, B, and A, respectively.

\reg custom.bltcon1/  This sixteen bit register holds the B shift in
its top four bits and five flags in its lower five bits.  Bits 0, 1,
2, 3, and 4 are (line), (desc), (fci), (ife), and (efe), respectively.
In the line mode, bits 5 and 6 are (ovf) and (sign), respectively.

\reg custom.bltafwm/  This sixteen bit register holds the first word
mask for the A DMA channel.

\reg custom.bltalwm/  This sixteen bit register holds the last word mask
for the A DMA channel.

\reg custom.bltapt/  This eighteen bit register holds the address of the
A DMA channel; it is written as a byte value, but the least significant
bit is ignored, as are the most significant thirteen bits.

\reg custom.bltbpt/  This eighteen bit register serves as the address for
the B DMA channel.

\reg custom.bltcpt/  This eighteen bit register provides the address for
the C DMA channel.

\reg custom.bltdpt/  This eighteen bit register is the destination or D
channel address.

\reg custom.bltsize/  This sixteen bit register gets the height
in rows, in the most significant ten bits, and the width in words, in the
least significant six bits.  Note that assigning to this register starts
the blitter, so it should be the last register initialized.

\reg custom.bltamod/  This fifteen bit signed register holds the modulo
value for the A DMA channel.  The value written is in bytes, but the least
significant bit is ignored.  The most significant bit is used as a sign
bit.

\reg custom.bltbmod/  This fifteen bit register serves a similar function
for the B DMA channel.

\reg custom.bltcmod/  This fifteen bit register provides the modulus for
the C DMA channel.

\reg custom.bltdmod/  This fifteen bit register holds the modulus for the
D channel.

\reg custom.bltadat/  This sixteen bit preloadable data register holds
data for the A DMA channel.

\reg custom.bltbdat/  This sixteen bit preloadable data register holds
data for the B DMA channel.

\reg custom.bltcdat/  This sixteen bit preloadable data register holds
data for the C DMA channel.
\par}
\section Missing Sections\\

The things we have neglected to talk about, which should be included,
are listed here.  We need to describe \b QBSBlit() and \b QBlit().
We also need to discuss the dirty mode of the blitter, and the zero
flag that the blitter returns.  It would be nice to have some
empirical timings comparing \b QBlit() with \b OwnBlitter() methods
of obtaining the blitter.

\section Plug for Amiga\TeX\\

This manual was typeset in a hurry on the Amiga using Amiga\TeX, previewed
on the screen, and printed on a QMS-Kiss at 300 dots per inch.
For a demonstration disk and more information, contact Tom Rokicki
at (415) 326-5312, or send mail to him at Box 2081, Stanford, CA\space\space
94305.
%
%   No, we are not really done, but that's enough for now.  We might
%   have to eject an empty half-page here.
%
\vfill\eject
\if R\lr \null\vfill\eject\fi
\end
20488!Funky!Stuff!
echo x - blitlab.c
cat > blitlab.c << '20488!Funky!Stuff!'
/*
 *   This is the main routine from BlitLab.
 */
#include "structures.h"
/*
 *   Here are all the globals we use.  (Yuck!  Globals!)
 */
struct Window *mywindow ;
struct GfxBase *GfxBase ;
struct IntuitionBase *IntuitionBase ;
struct RastPort *myrp ;
char strings[900] ;
char *bufarr[MAXGADG] ;
long gvals[MAXGADG] ;
struct Gadget *gadgets[MAXGADG] ;
char errorbuf[140] ;
short *realbits ;
struct blitregs blitregs ;
int errortitle ;
/*
 *   Externals we use:
 */
extern int blitsafe() ;
/*
 *   Some statics to this module.
 */
static int updatethem ;
/*
 *   Errors go through here.  Currently, we write to the CLI window.
 *   Later, we will write to the title bar of the window.
 */
error(s)
char *s ;
{
   if (mywindow == NULL || *s == '!')
      printf("blitlab: %s\n", s) ;
   else {
      SetWindowTitles(mywindow, s, -1L) ;
      errortitle = 1 ;
   }
   if (*s == '!')
      cleanup() ;
}
/*
 *   This routine handles a gadget selection.
 */
handlegadget(gp)
register struct Gadget *gp ;
{
   static int gocount = 0 ;

   if (errortitle == 1) {
      SetWindowTitles(mywindow, BANNER, -1L) ;
      errortitle = 0 ;
   }
   if (bufarr[gp->GadgetID] == NULL)
   switch(gp->GadgetID) {
      case GDGPNTREG:
      case GDGCLRSET:
      case GDGLINE:
      case GDGDESC:
      case GDGFCI:
      case GDGIFE:
      case GDGEFE:
      case GDGUSEA:
      case GDGUSEB:
      case GDGUSEC:
      case GDGUSED:
      case GDGOVF:
      case GDGSIGN:
         flipgadg(gp->GadgetID) ;
         break ;
      case GDGCALC:
         parseall() ;
         updatethem = 0 ;
         if (!blitsafe()) {
            error("Blit unsafe.") ;
         }
         break ;
      case GDGSETUP:
         setupline() ;
         parseall() ;
         break ;
      case GDGGO:
         gocount += 2 ;
         parseall() ;
         updatethem = 0 ;
         if (!blitsafe()) {
            if (gocount < 3)
               error("Blit unsafe---hit again to override") ;
            else {
               doblit() ;
               updatebits() ;
            }
         } else {
            doblit() ;
            updatebits() ;
         }
         break ;
      default:
         error("! bad value in gadget switch") ;
         break ;
   }
   if (gocount > 0)
      gocount-- ;
}
/*
 *   The main routine, no arguments.  Sets things up, and then goes
 *   through the standard Intuition message loop.
 *
 *   It may look like I'm setting message to NULL and checking it and
 *   everything all over, but that is so I can introduce interruptibility
 *   into some operations later, if I choose.
 */
main() {
   struct IntuiMessage *message = NULL ;
   int x, y ;
   int mousemoved = 0 ;
   int getouttahere = 0 ;
   int selectdown = 0 ;
   int bam ;
   int ox, oy ;

   initialize() ;
   while (1) {
      mousemoved = 0 ;
      bam = 0 ;
      if (message == NULL)
         WaitPort(mywindow->UserPort) ;
      while (message || (message = 
                       (struct IntuiMessage *)GetMsg(mywindow->UserPort))) {
         x = message->MouseX ;
         y = message->MouseY ;
         if (message->Class == MOUSEMOVE) {
            ReplyMsg(message) ;
            message = NULL ;
            mousemoved = 1 ;
         } else {
            if (message->Class == MOUSEBUTTONS) {
               selectdown = (message->Code == SELECTDOWN) ;
               bam = 1 ;
            } else if (message->Class == GADGETDOWN || 
                       message->Class == GADGETUP) {
               updatethem = 1 ;
               handlegadget((struct Gadget *)(message->IAddress)) ;
            } else if (message->Class == CLOSEWINDOW) {
               getouttahere = 1 ;
            } else
               error("! undefined message class") ;
            ReplyMsg(message) ;
            message = NULL ;
         }
      }
      if (getouttahere)
         break ;
      if (updatethem) {
         parseall() ;
         updatethem = 0 ;
      }
      x = (x - HBITSTART) / 6 ;
      y = (y - VBITSTART) / 3 ;
      if (y < 32 && x < 96 && x >= 0 && y >= 0) {
         if (gvals[GDGPNTREG]) {
            if (bam) {
               if (selectdown) {
                  ox = x ;
                  oy = y ;
               } else {
                  preg(ox, oy, x, y, (int)gvals[GDGCLRSET]) ;
               }
            }
         } else {
            if (selectdown)
               pdot(x, y, (int)gvals[GDGCLRSET]) ;
         }
         if (message != NULL || (message = 
               (struct IntuiMessage *)GetMsg(mywindow->UserPort))) ;
         else
            updatepos(x, y) ;
      }      
   }
   cleanup() ;
}
20488!Funky!Stuff!
echo x - blitlab.uue
cat > blitlab.uue << '20488!Funky!Stuff!'
begin 777 blitlab
M```#\P`````````#``````````(```UR```"OP````$```/I```-<D[Z)#1.5
M50``2JR$XF<*(&T`"`P0`"%F$"\M``A(>@`V3KHH\%!/8!I(>/__+RT`""\L\
MA.).NC5^3^\`##E\``&$X"!M``@,$``A9@1.N@E"3EU.=6)L:71L86(Z("5S.
M"@``3E4``"\*)&T`"`QL``&$X&882'C__TAZ`1XO+(3B3KHU,D_O``Q";(3@%
M<``P*@`FY8!![(6P2K`(`&8``.IP`#`J`"9@``#./RH`)DZZ'H143V```-).#
MNAA.0FR"IDZZ'"A*0&8*2'H!!$ZZ_S983V```+1.NA[T3KH8+&```*A4;(`"K
M3KH8($)L@J9.NAOZ2D!F'@QL``.``FP,2'H`VTZZ_P!83V`(3KHB3$ZZ!1Y@J
M"$ZZ(D).N@448&I(>@#?3KK^X%A/8%[_)/\D_US_F/^8_YC_F/\D_YC_F/\D\
M_R3_)/\D_U#_F/\D_R3_)/\D_YC_F/^8_YC_F/^8_YC_F/^8_YC_F/^8_YC_%
MF/^8_S+_)/\DL+P````F9*#C@#`[`*A.^P``2FR``F\$4VR``B1?3EU.=4)L&
M:71,86(@,2XR+"!#;W!Y<FEG:'0@*$,I(#$Y.#<L(%)A9&EC86P@17EE(%-OP
M9G1W87)E`$)L:70@=6YS869E+@!";&ET('5N<V%F92TM+6AI="!A9V%I;B!T1
M;R!O=F5R<FED90`A(&)A9"!V86QU92!I;B!G861G970@<W=I=&-H`$Y5_^Q"9
MK?_\0FW_]D)M__1";?_R3KH%!$)M__9";?_P2JW__&8.(&R$XB\H`%9.NC*(H
M6$]*K?_\9A8@;(3B+R@`5DZZ,C)83RM`__QG``#*(&W__#MH`"#_^B!M__P[E
M:``B__@@;?_\#*@````0`!1F&"\M__Q.NC(B6$]"K?_\.WP``?_V8```C"!M)
M__P,J`````@`%&8<(&W__`QH`&@`&%?`P'P``3M`__([?``!__!@4B!M__P,V
MJ````"``%&<.(FW__`RI````0``49A8Y?``!@J8@;?_\+R@`'$ZZ_6983V`@+
M(&W__`RH```"```49@@[?``!__1@"DAZ`0).NOSF6$\O+?_\3KHQCEA/0JW_>
M_&``_QY*;?_T9@``VDIL@J9G"$ZZ%<1";(*F,"W_^EE`2,"!_``&.T#_^C`MR
M__B0?``+2,"!_``#.T#_^`QM`"#_^&P``)P,;0!@__IL``"22FW_^FT``(I*A
M;?_X;0``@DJLADAG.$IM__!G,$IM__)G#CMM__K_[CMM__C_[&`</RR&3C\M/
M__@_+?_Z/RW_[#\M_^Y.N@&N3^\`"F`82FW_\F<2/RR&3C\M__@_+?_Z3KH`S
M=EQ/2JW__&84(&R$XB\H`%9.NC"<6$\K0/_\9P)@#C\M__@_+?_Z3KH!KEA/*
M8`#^(DZZ!7I.74YU(2!U;F1E9FEN960@;65S<V%G92!C;&%S<P!.50``2'D`'
M`0`"2'@#Z$ZZ$A!03]"\```!+"E`A/).74YU3E7__#`M``K!_``&,BT`".A!5
MT$$[0/_^,"T`",!\``]R#Y)`=`'C8CM"__Q*;0`,9W(P+?_^2,#C@"!LA/(R3
M,`@`PFW__&9:,"W__DC`XX`@;(3RT<`R+?_\@U`P+?_^2,#C@$'L@JC1P#(MS
M__R#4#\\``%.N@<25$\_/``"/SP`!#`M``K!_``#T'P`##\`,BT`",/\``98,
M03\!3KH(.E!/8'0P+?_^2,#C@"!LA/(R,`@`PFW__&=>,"W__DC`XX`@;(3RE
MT<`R+?_\1D'#4#`M__Y(P..`0>R"J-'`,BW__$9!PU`_/``"3KH&G%1//SP`K
M`C\\``0P+0`*P?P``]!\``P_`#(M``C#_``&6$$_`4ZZ!\103TY=3G5.5?_\"
M.VT`"/_^8"P[;0`*__Q@%C\M`!`_+?_\/RW__DZZ_L)<3U)M__PP+?_\L&T`*
M#F_@4FW__C`M__ZP;0`,;\I.74YU3E7__#`M``CF0`B````R+0`*P_P`#-!!Y
M/P!(>@!02&W__$ZZ(NA/[P`*2&W__#\\`#4_/`)B3KH'?E!/,"T`",!\``\_U
M`$AZ`"A(;?_\3KHBO$_O``I(;?_\/SP`13\\`E).N@=24$].74YU)3-D`"4R(
M9`!.5?_V2.<`,#M\``3__CM\``S__"1LA/)![(*H)D@[?`#`__H[?``&__@PP
M+?_Z4VW_^DI`;P``E#`2L%-F#%2*5(L&;0!@__Y@9CM\@`#_]DIM__9G4#`3(
MP&W_]C(2PFW_]K!!9RPP$L!M__9G!'`!8`)P`C\`3KH%.E1//SP``C\\``0_&
M+?_\/RW__DZZ!G103S`M__;B0,!\?_\[0/_V7&W__F"J($I4BB)+5(LRD%-MV
M__AF$#M\``;_^#M\``3__E9M__Q@`/]B3-\,`$Y=3G5";&ET3&%B(#$N,BP@4
M0V]P>7)I9VAT("A#*2`Q.3@W+"!2861I8V%L($5Y92!3;V9T=V%R90!.5?_\N
M0J=(>@'^3KHM8%!/*4"$ZF<20J=(>@'^3KHM3E!/*4"$YF8*2'H!_TZZ^*!8W
M3TAL@`1.NBX$6$\I0(3B9@I(>@'_3KKXAEA/(&R$XBEH`#*$[DZZ_*A.N@F0*
M3KH"/DZZ$6X_/``"3KH$/E1//SP`83\\`D`_/``+/SP`!$ZZ!7A03S\\``%.%
MN@0>5$\_/`!A/SP"0#\\``L_/``$3KH$W%!//SP``TZZ`_Y43SM\``'__C\\^
M`&$_/``"/SP`"S`M__[!_``85$`_`$ZZ!*Y03U)M__X,;0`8__YMUCM\``'_#
M_CM\``'__#`M__S!_``#T'P`"S\`,BW__L/\``9403\!-"W__,7\``/4?``+0
M/P(V+?_^Q_P`!E1#/P-.N@.N4$]2;?_\#&T`(/_\;;I2;?_^#&T`8/_^;:A"Y
M9TZZ`VA43S\\`&$_/`)`/SP`"S\\``1.N@0F4$\[?``!__X_/`!A/SP``C\\U
M``LP+?_^P?P`8%1`/P!.N@0"4$]2;?_^#&T`!__^;=8[?``!__XP+?_^P?P`*
M#-!\``L_`#\\`D,R+?_^P_P`#-)\``L_`3\\``1.N@,84$]2;?_^#&T`"?_^>
M;<I.NOTN/SP``4ZZ`MA43S\\`$$_/`)Y/SP`@C\\``1.N@.64$\_/`#"/SP!2
M!3\\`((_/`$%3KH"TE!/3EU.=6EN='5I=&EO;BYL:6)R87)Y`&=R87!H:6-S#
M+FQI8G)A<GD`(2!#;W5L9&XG="!O<&5N(&QI8G)A<FEE<P`A($-O=6QD;B=T4
M(&]P96X@=VEN9&]W`$Y5``!*K(3B9PHO+(3B3KHKQEA/2JR$YF<*+RR$YDZZY
M*IA83TJLA.IG"B\LA.I.NBJ(6$].N@SP0F=.NBBF5$].74YU3E4``$AZ`6H_+
M/``M/SP"2DZZ`XI03TAZ`5X_/``U/SP"2DZZ`WA03TAZ`5`_/``]/SP"2DZZX
M`V903TAZ`44_/`"Q/SP`%DZZ`U103TAZ`4L_/`"Z/SP!#DZZ`T)03TAZ`48_,
M/`"$/SP`"DZZ`S!03TAZ`3D_/`"-/SP`"DZZ`QY03TAZ`2P_/`"6/SP`"DZZ]
M`PQ03TAZ`1\_/`"?/SP`"DZZ`OI03TAZ`1(_/`"H/SP`"DZZ`NA03TAZ`04_G
M/`"-/SP`6DZZ`M903TAZ`/4_/`"6/SP`6DZZ`L103TAZ`.4_/`"?/SP`6DZZ%
M`K)03TAZ`-4_/`"H/SP`6DZZ`J!03TAZ`,4_/`"$/SP`;DZZ`HY03TAZ`,8_8
M/`"./SP!"DZZ`GQ03TAZ`+8_/`"9/SP!"DZZ`FI03TAZ`*8_/`"D/SP!"DZZO
M`EA03TAZ`)8_/`"O/SP!"DZZ`D903TAZ`(8_/`"$/SP!&DZZ`C103TY=3G5!9
M9')S.@`@32L`4VAI9G0Z`$)L:71T97(@4F5G:7-T97(@5F%L=65S`$1-02!#3
M:&%N;F5L<P!#3TXP`$-/3C$`4TE:10!!1E=-`$%,5TT`00!"`$,`1`!05$@@W
M(%!43"`@34]$("!$050`00!"`$,`1`!54T4@("!05"`@("`@34]$("`@("`@$
M("`@($1!5"`@("`@("`@("!32`!.50``,"T`"$C`+P`O+(3N3KHI%%!/0J<O\
M+(3N3KHI&%!/3EU.=4Y5__XP+0`(L&T`#&\2.VT`"/_^.VT`#``(.VW__@`,]
M,"T`"K!M``YO$CMM``K__CMM``X`"CMM__X`#C`M``BP;0`,9Q0R+0`*LFT`&
M#F<*2'H`.$ZZ\ZA83S`M``Y(P"\`,BT`#$C!+P$T+0`*2,(O`C8M``A(PR\#7
M+RR$[DZZ*&Y/[P`43EU.=2$@8V%N(&]N;'D@9')A=R!H+W8@;&EN97,@8W5R@
M<F5N=&QY`$Y5```P+0`(T&T`#%-`.T``##`M``K0;0`.4T`[0``./RT`"C\MY
M``P_+0`*/RT`"$ZZ_R!03S\M``X_+0`,/RT`"C\M``Q.NO\*4$\_+0`./RT`R
M"#\M``X_+0`,3KK^]%!//RT`"C\M``@_+0`./RT`"$ZZ_MY03TY=3G5.50``_
M,"T`"M!M``Y30$C`+P`R+0`(TFT`#%-!2,$O`30M``I(PB\"-BT`"$C#+P,OX
M+(3N3KHGCD_O`!1.74YU3E4``"EM``R`0#`M``I(P"\`,BT`"$C!+P%(;(`T&
M+RR$[DZZ)^I/[P`03EU.=4Y5``!(YP`P2'D``0``2'@`$$ZZ"*103R1`2'D`N
M`0``2'@`%$ZZ"))03R9`-VT`"``$-VT`"``(-VT`"@`*-VT`"@`.%7P``0`$J
M%7P``0`&%7P`!0`')4L`""`*3-\,`$Y=3G5.50``+PI(>0`!``!(>``43KH(^
M0E!/)$`4O``!%7P``0`"-7P``@`&+RT`"DZZ'TY83^=`,BT`"))`2,&#_``"/
M-4$`!"5M``H`#"`*)%].74YU3E4``"\*2'D``0``2'@`+$ZZ!_!03R1`-6T`K
M"@`$-6T`#``&-6T`#@`(-6T`$``*0FH`##5\``$`#C5\``$`$#\M`!`_+0`.F
M3KK_`%A/)4``$B\M`!(_+0`.3KK_4EQ/)4``&C5M``@`)C`M``A(P.6`0>R&E
MX"&*"`!"9R\*+RR$XDZZ)GQ/[P`*)%].74YU3E4``"\*2'D``0``2'@`+$ZZK
M!V!03R1`-6T`"@`$-6T`#``&-6T`#@`(-6T`$``*-7P``P`,-7P``@`.-7P`T
M`0`0/RT`$#\M``Y.NOYN6$\E0``2+RT`$C\M``Y.NO[`7$\E0``:-6T`"``F!
M+RT`%C\M``Y.NOZH7$\E0``H,"T`"$C`Y8!![(;@(8H(`$)G+PHO+(3B3KHEZ
MV$_O``HD7TY=3G5.5?_Z2.<(,$JM`!9G/B\M`!8P+0`,5$`_`#(M``I803\!V
M3KK]Q%!/+RT`%DZZ'<A83^=`4$#1;0`*+RT`%DZZ';983^=`4$"1;0`.,"T`!
M#EE`2,"!_``(.T#_^CM\``O__C`M__KG0%A`.T#__#`M``A(P.6`0>R%L"&LS
MA(P(`"\M`!`O+(2,3KH7S%!/2'D``0``2'@`+$ZZ!C103R1`2'D``0``2'@`N
M)$ZZ!B)03R9`)JR$C#`M`!1(P-&LA(Q![(0H)T@`!#=M`!0`"C`M``I40#5`_
M``0P+0`,5$`U0``&-6W__``(,"W__E=`-4``"D)J``PU?``!``XU?``$`!`P^
M+?_^54`_`#(M__Q503\!3KK]`EA/*``@1")H``@RO/_^($0B:``(,WS__@`")
M($0B:``(,WS__@`&($0B:``(,WS__@`,($0B:``(,WS__@`0($0B:``(,WS_L
M_@`2)40`$D*J`!HE2P`B-6T`"``F,"T`"$C`Y8!![(;@(8H(`$)G+PHO+(3BN
M3KHD0$_O``I,WPP03EU.=4Y5__Y![(=X*4B$C$AZ!$H_/``?/SP`,C\\`%\_F
M/`)(/SP``DZZ_0Q/[P`.2'H$+3\\``L_/``_/SP`=S\\`(0_/``.3KK\[$_O[
M``Y(>@03/SP`##\\`#8_/`!//SP"1C\\`"-.NOS,3^\`#DAZ`_Y(>@/T/SP`=
M##\\`#8_/``//SP"1D)G3KK].D_O`!)(>@/H2'H#WC\\``P_/``V/SP`'S\\?
M`D8_/``!3KK]%D_O`!)(>@/12'H#QC\\``L_/``_/SP`;#\\`(0_/``'3KK\\
M\D_O`!)(>@.[2'H#L#\\``L_/``_/SP`;#\\`40_/``*3KK\SD_O`!)(>@.DN
M2'H#FC\\``L_/``_/SP`;#\\`80_/``+3KK\JD_O`!)(>@.,2'H#@C\\``L_G
M/``_/SP`;#\\`<0_/``,3KK\AD_O`!)(>@-T2'H#:C\\``L_/``_/SP`;#\\-
M`@0_/``-3KK\8D_O`!)(>@-=2'H#4C\\``L_/``_/SP`=S\\`<0_/``D3KK\2
M/D_O`!)(>@-&2'H#/#\\``L_/``_/SP`=S\\`@0_/``E3KK\&D_O`!)";?_^'
M2'H#)DAZ`R`_/``+/SP`&#`M__[!_``+T'P`C#\`/SP!&C(M__[2?``0/P%.`
MNOOB3^\`$E)M__X,;0`$__YMP$AZ`NH_/``42'H"X#\\`#\_/`!L/SP`!#\\&
M``-.NOQ63^\`$DAZ`LL_/``42'H"P3\\`#\_/`!L/SP`1#\\``1.NOPR3^\`C
M$DAZ`JP_/``42'H"HC\\`#\_/`!W/SP`!#\\``5.NOP.3^\`$DAZ`HT_/``4D
M2'H"@S\\`#\_/`!W/SP`1#\\``9.NOOJ3^\`$DAZ`FX_/``42'H"9#\\`#\_2
M/`!L/SP`Q#\\``A.NOO&3^\`$DAZ`DX_/``42'H"1#\\`#\_/`!L/SP!!#\\(
M``E.NONB3^\`$DAZ`BX_/`!D2'H")#\\`/P_/`!W/SP`Q#\\``].NOM^3^\`H
M$D)M__Y"IS\\`!1(>@(%/SP`1#`M__[!_``+T'P`C#\`/SP!-C(M__[2?``4G
M/P%.NOM(3^\`$E)M__X,;0`$__YMPD)M__Y"IS\\`!1(>@'%/SP`-#`M__[!V
M_``+T'P`C#\`/SP!?C(M__[2?``8/P%.NOL&3^\`$E)M__X,;0`$__YMPD)M'
M__Y"IS\\`!1(>@&%/SP`E#`M__[!_``+T'P`C#\`/SP!MC(M__[2?``</P%.+
MNOK$3^\`$E)M__X,;0`#__YMPD)M__Y"IS\\`!1(>@%%/SP`)#`M__[!_``+9
MT'P`C#\`/SP"3C(M__[2?``?/P%.NOJ"3^\`$E)M__X,;0`"__YMPDAZ`2$_%
M/``42'H!!S\\`+0_/`"M/SP!MC\\`"%.NOI23^\`$DAZ`1,_/``42'H`^3\\^
M`+0_/`"X/SP!MC\\`").NOHN3^\`$D*G+RR$XB!LA.(O*``^3KH@(D_O``Q.)
M74YU1T\`4V5T=7``0V%L8P!0;VEN=``@0F]X(`!#;&5A<@`@4V5T(``H;&ENK
M92D`($Q)3D4@`"AD97-C*0`@1$530R``*&9C:2D`($9#22``*&EF92D`($E&.
M12``*&5F92D`($5&12``*'-I9VXI`"!324=.(``H;W9F*0`@3U9&(`!.`%D`7
M,`!36``P`%-9`#``15@`,`!%60`P`%<`,`!(`#``1G5N8P`P`#``,``P`"4QC
M,3$Q,3$Q,3$Q,3$Q,3$Q`$9730`E,3$Q,3$Q,3$Q,3$Q,3$Q,0!,5TT``$Y51
M__PO+0`,("T`"%"`+P!.NAWP4$\K0/_\2JW__&8*2'H`+DZZZ8A83R!M__P@K
M+0`(4(`A0``$(&W__""LA)`I;?_\A)`@+?_\4(!.74YU(2!O=70@;V8@;65MJ
M;W)Y`$Y5__Q*K(209R(@;(20*U#__"!LA)`O*``$+RR$D$ZZ':A03REM__R$,
MD&#83EU.=4Y5__(O"B1M``@[?``!__X[?``*__I"K?_V0FR$E`P2`"!F!%**Y
M8/8,$@!^9R8,$@!!9R`,$@!"9QH,$@!#9Q0,$@!A9PX,$@!B9P@,$@!C9@``L
MS$*M__(K?````/__]@P2`'YF"CM\`/___E**8`1";?_^($I2BA`02(`[0/_\6
---end---

rokicki@rocky.STANFORD.EDU (Tomas Rokicki) (06/23/87)

---start---
M2FW__&<(#&T`*__\9A(,K0```/__]F8&.7P``8248%(,;0!A__QM#@QM`'K_3
M_&X&!&T`(/_\#&T`0?_\;0@,;0!#__QO"#E\``&$E&`D,"W__)!\`$%(P..`V
M0>R`2#(P"``T+?_^M4%(P<.M__9@`/]P("W_]H&M__(,;0`K__QG#DIM__QG^
M!CE\``&$E&`$8`#_1B`M__(D7TY=3G4,$@!M9P8,$@!-9B@,*@`K``%F"E2*M
M*VR$\O_V8!9**@`!9@8@+(3R8-`Y?``!A)1P`&#&#!(`+68(.WS____^4HH,"
M$@`D9@H[?``0__I2BF`.#!(`)68(.WP``O_Z4HI"K?_R2A)F##E\``&$E"`MX
M__)@AB!*4HH0$$B`.T#__$IM__QG:`QM`&'__&T.#&T`>O_\;@8$;0`@__P,&
M;0!!__QM$`QM`$;__&X(!&T`-__\8`8$;0`P__Q*;?_\;0HP+?_\L&W_^FT('
M.7P``8248!PP+?_Z2,`B+?_R3KH/##0M__Q(PM""*T#_\F"&,"W__DC`(BW_U
M\DZZ#O#0K?_V8`#^^$Y5__Y";?_^,"W__DC`Y8!![(6P2K`(`&=<,"W__DC`<
MY8!![(6P+S`(`$ZZ_;)83S(M__Y(P>6!0>R&2"&`&`!*;(249S`P+?_^2,#EW
M@$'LA;`O,`@`2'H`,DALA21.N@]>3^\`#$ALA21.NN:06$]P`$Y=3G52;?_^U
M#&T`)O_^;89.N@"J<`%@Z$D@8V%N)W0@<&%R<V4@)7,``#`Q,C,T-38W.#EA1
M8F-D968``$Y5```P+0`,P'P`#R!L@$X9<```A)DP+0`,Z$`[0``,,"T`#,!\(
M``\@;(!.&7```(28,"T`#.A`.T``##`M``S`?``/(&R`3AEP``"$ES`M``SHI
M0#M```PP+0`,P'P`#R!L@$X9<```A)9(;(26/RT`"C\M``A.NO-"4$].74YUZ
M3E7__B`LAL3`O`````]R#..@)"R&B'8+YZ+0@B8LAHQT"N6CT(,F+(:0=`GE#
MH]"#)BR&E.&#T(,F+(:$QKP```#_T(,Y0(3Z("R&R,"\````#W(,XZ`D+(;84
M[8+0@B8LAMSK@]"#)BR&?.F#T(,F+(9XYX/0@R8LAG3E@]"#)BR&<..#T(/0N
MK(9D.4"$_"`LAFS`O````__M@"(LAFC"O````#_0@3E`A/X@+(;,P+P``/__5
M.4"%`"`LAM#`O```__\Y0(4"0FW__C`M__[0?``42,#E@$'LAD@B,`@`=!#DJ
MH<*\``#__S8M__Y(P^.#0^R%!#.!.``P+?_^T'P`%$C`Y8!![(9((C`(`,*\,
M``#__S0M__Y(PN."0^R%##.!*``P+?_^T'P`&$C`Y8!![(9((C`(`,*\``#_D
M_S0M__Y(PN."0^R%%#.!*`!2;?_^#&T`!/_^;0#_<D)M__XP+?_^T'P`'$C`Z
MY8!![(9((C`(`,*\``#__S0M__Y(PN."0^R%'#.!*`!2;?_^#&T``__^;<H_:
M+(3Z/SP`A#\\`#).NOW>7$\_+(3\/SP`C3\\`#).NOW,7$\_+(3^/SP`EC\\Y
M`#).NOVZ7$\_+(4`/SP`GS\\`#).NOVH7$\_+(4"/SP`J#\\`#).NOV67$]"U
M;?_^,"W__DC`XX!![(4$/S`(`#(M__[#_``)TGP`C3\!/SP`:DZZ_6I<3S`MW
M__Y(P..`0>R%##\P"``R+?_^P_P`"=)\`(T_`3\\`)).NOU"7$\P+?_^2,#C@
M@$'LA10_,`@`,BW__L/\``G2?`"-/P$_/`"Z3KK]&EQ/4FW__@QM``3__FT`>
M_WQ";?_^,"W__DC`XX!![(4</S`(`#(M__[#_``)TGP`C3\!/SP`XDZZ_.!<-
M3U)M__X,;0`#__YMS$Y=3G5.5?_H2JR&E&8&<`%.74YU*VR$\O_L("R$\M"\6
M```!?BM`_^@,K`````&&:&T>#*P````!AFQM%`RL````0(9H;@H,K```!`"&/
M;&\$<`!@O$JLAF1G!&```5(K;(:D__Q*K(9P9P``IB`LAFCC@"(M__R2@%2!L
M*T'_^"`LAFCC@"(LAK0(@0``T($B+(9L4X%.N@IR)"W__)2`*T+_]"`LAFCC7
M@"(M__22@%2!*T'_\"`M__RPK?_L;48B+?_XLJW_[&T\)"W_]+2M_^QM,B8M"
M__"VK?_L;2@@;?_\L>W_Z&X>(FW_^+/M_^AN%"QM__2][?_H;@HL;?_PO>W_`
MZ&\&<`!@`/\(<`%@`/\"("R&:..`T*W__%6`*T#_^"`LAFCC@"(LAK0(@0```
MT($B+(9L4X%.N@G0T*W__"M`__0@+(9HXX#0K?_T58`K0/_P("W__+"M_^QM5
M1B(M__BRK?_L;3PD+?_TM*W_[&TR)BW_\+:M_^QM*"!M__RQ[?_H;AXB;?_XR
ML^W_Z&X4+&W_]+WM_^AN"BQM__"][?_H;P9P`&``_FIP`6``_F1@`/Y@3E4`X
M`"\M``I.NOA$6$](P#(M``A(P>6!0>R&2"&`&``P+0`(2,#E@$'LAN`O,`@`O
M+RR$XDZZ%MA03R\M``HP+0`(2,#E@$'LAN`B<`@`+&D`(B\63KH(\%!//SS_0
M_S`M``A(P.6`0>R&X"\P"``O+(3B3KH62$_O``I"IR\LA.(P+0`(2,#E@$'LC
MAN`O,`@`3KH6:$_O``Q.74YU3E7_^#`M``A(P.6`0>R&X"MP"`#_^"\M__@OF
M+(3B3KH64E!/(&W_^"MH`!K__"!M__@B;?_X(V@`*``:(&W_^"%M__P`*"!MF
M__A"D#\\__\O+?_X+RR$XDZZ%<9/[P`*0J<O+(3B+RW_^$ZZ%?)/[P`,,"T`T
M"$C`Y8!![(9(,BT`"$C!Y8%#[(9(=`&4L1@`(8((`$Y=3G5.5?_T0FW_]$ZZN
M^3)(>@,R/SP`'$ZZ_K9<3TAZ`RH_/``=3KK^J%Q/.VR&5O_^.VR&6O_\,"W_>
M_L!\``\_`$AZ`PQ(;(4D3KH(LD_O``I(;(4D/SP`'TZZ_G1<3S`M__[F0`B`D
M```R+?_\P_P`#-!!.T#_^C\M__I(>@+52&R%)$ZZ"'A/[P`*2&R%)#\\`!9.1
MNOXZ7$](;(4D/SP`%TZZ_BQ<3TAZ`JX_/``:3KK^'EQ/2'H"HS\\`!M.NOX02
M7$](>@*8/SP`"$ZZ_@)<3R`LAER0K(94.T#__B`LAF"0K(98.T#__$IM__YL;
M##`M__Y$0#M`__A@!CMM__[_^$IM__QL##`M__Q$0#M`__9@!CMM__S_]DIM)
M__YO,DIM__QO%C`M__BP;?_V;P1P`6`"<``[0/_T8!0P+?_XL&W_]F\$<`-@_
M`G`$.T#_]&`P2FW__&\6,"W_^+!M__9O!'`%8`)P`CM`__1@%#`M__BP;?_V5
M;P1P!V`"<`8[0/_T,"W_]K!M__AO$CMM__C_^CMM__;_^#MM__K_]C`M__A2_
M0#\`2'H!N$ALA21.N@=.3^\`"DALA20_/``)3KK]$%Q/,"W_]N5`,BW_^.-!.
MD$$_`$AZ`8U(;(4D3KH'($_O``I(;(4D/SP`%$ZZ_.)<3S`M__;C0)!M__A*D
M0&P22JR&V&8*/SP`)$ZZ_6!43V`02JR&V&<*/SP`)$ZZ_4Y43S`M__:0;?_XY
MY4`_`$AZ`39(;(4D3KH&QD_O``I(;(4D/SP`&$ZZ_(A<3S`M__;E0#\`2'H!V
M$TALA21.N@:@3^\`"DALA20_/``93KK\8EQ/2'H`^#\\`"%.NOQ47$](>@#\_
M/SP`(DZZ_$9<3TJLAF1F"C\\``=.NOS25$\P+?_TP'P``4C`L*R&?&<*/SP`\
M#4ZZ_+A43S`M__3B0,!\``%(P+"LAGAG"C\\``Q.NOR<5$\P+?_TY$#`?``!I
M2,"PK(9T9PH_/``+3KK\@%1/2JR&B&8*/SP`$$ZZ_'!43TJLAHQG"C\\`!%.E
MNOQ@5$]*K(:09@H_/``23KK\4%1/2JR&E&8*/SP`$TZZ_$!43TJLAMQG"C\\R
M`"5.NOPP5$].74YU)#@P,#``)&9F9F8`)60`32LE9``Q,@`Q,@`R`"5D`"5DF
M`"5D`"5D`"4Q,3$Q,3$Q,3$Q,3$Q,3$Q`"4Q,3$Q,3$Q,3$Q,3$Q,3$Q`$Y5?
M``!.NA&.3KH1Q"!L@%(PK(3Z(&R`4C%LA/P``B!L@%(Q;(4```0@;(!2,6R%]
M`@`&(&R`4C%LA00`$"!L@%(Q;(4&``P@;(!2,6R%"``((&R`4C%LA0H`%"!LH
M@%(Q;(4,`!(@;(!2,6R%#@`.(&R`4C%LA1``"B!L@%(Q;(42`!8@;(!2,6R%P
M%``D(&R`4C%LA18`(B!L@%(Q;(48`"`@;(!2,6R%&@`F(&R`4C%LA1P`-"!L$
M@%(Q;(4>`#(@;(!2,6R%(``P(&R`4C%LA/X`&$ZZ$/I.NA"T3EU.=6%P0^R"W
MID7L@J:UR68.,CP"%6L(=``BPE')__PI3X2D+'@`!"E.A*A(YX"`""X`!`$I4
M9Q!+^@`(3J[_XF`&0J?S7TYS0_H`($ZN_F@I0(2L9@PN/``#@`=.KO^48`1."
MN@`:4$].=61O<RYL:6)R87)Y`$GY``!__DYU3E4``"\*2'D``0``,"R"HL'\\
M``8O`$ZZ#Y103RE`A+!F%$*G2'D``0``3KH/5%!/+FR$I$YU(&R$L$)H``0@>
M;(2P,7P``0`0(FR$L#-\``$`"B!LA*0@+(2DD*@`!%"`*4"$M"!LA+0@O$U!=
M3EA"ITZZ#TA83R1`2JH`K&<N+RT`#"\M``@O"DZZ`+)/[P`,.7P``82X(&R$Y
ML`!H@```!"!LA+``:(````I@1$AJ`%Q.N@]V6$](:@!<3KH/*EA/*4"$NB!L=
MA+I*J``D9Q`@;(2Z(F@`)"\13KH.3EA/+RR$NB\*3KH"?%!/*6R$NH2^3KH.P
M3B!LA+`@@$ZZ#FX@;(2P(4``!F<62'@#[4AZ`"Q.N@Y*4$\@;(2P(4``#"\L[
MA+X_+(3"3KK<1%Q/0F=.N@R25$\D7TY=3G4J`$Y5``!(YPPP)&T`$"!M``@@W
M*`"LY8`H`"!$("@`$.6`)D`0$TB`2,#0K0`,5(`Y0(3$0J<P+(3$2,`O`$ZZ%
M#C)03RE`A,9F"$S?##!.74YU$!-(@#\`($M2B"\(+RR$QDZZ`41/[P`*2'H!X
M.A`32(!(P-"LA,8O`$ZZ`7A03S\M``XO"B\LA,9.N@%$3^\`"D)LA,(F;(3&_
M)$L0$TB`.@"P?``@9QBZ?``)9Q*Z?``,9PRZ?``-9P:Z?``*9@12BV#8#!,`:
M(&UZ#!,`(F8N4HL@2U*+$!!(@#H`9QX@2E**$(6Z?``B9A`,$P`B9@12BV`&_
M0BK__V`"8-9@."!+4HL0$$B`.@!G)KI\`"!G(+I\``EG&KI\``QG%+I\``UG5
M#KI\``IG""!*4HH0A6#.($I2BD(02D5F`E.+4FR$PF``_UI"$D*G,"R$PE)`A
M2,#E@"\`3KH-'%!/*4"$OF8(0FR$PF``_N1Z`"9LA,9@'C`%2,#E@"!LA+XA@
MBP@`+PM.N@826$]20$C`U\!21;ILA,)MW#`%2,#E@"!LA+Y"L`@`8`#^IB``?
M3.\#```$(`@R+P`,8`(0V5?)__QG!E)!8`)"&%')__Q.=3`\?_]@!#`O``P@J
M;P`$2AAF_%-((F\`"%-`$-E7R/_\9P)"$"`O``1.=2!O``0@"")O``@0V6;\Q
M3G5(YW``-`'$P"8!2$/&P$A#0D/4@TA`P,%(0$)`T(),WP`.3G5.50``2.<.[
M,"1M``A"ITAZ`(Y.N@R`4$\I0(3V9@A,WPQP3EU.=2!M``PB:``D+RD`!$ZZ&
M#0)83R@`9U)(>@!M($0O*``V3KH,U%!/)D!*@&<T2'@#[2\+3KH+H%!/+`!G8
M)"`&Y8`J`"!%)6@`"`"D)48`G$AX`^U(>@`X3KH+?%!/)4``H"\$3KH,H%A/I
M+RR$]DZZ"Z)83T*LA/9@@&EC;VXN;&EB<F%R>0!724Y$3U<`*@!.50``+P0I&
M;0`(A)Q(;0`0+RT`#$AZ`!I.N@#<3^\`##@`(&R$G$(0,`0H'TY=3G5.50``.
M(&R$G%*LA)P0+0`)$(!(@,!\`/].74YU3E4``$AM``PO+0`(2'H$<$ZZ`)A/^
M[P`,3EU.=4Y5``!(YP@@)&T`#@QM``0`$F8((&T`""@08!Q*;0`,;PP@;0`(;
M<``P$"@`8`H@;0`(,!!(P"@`0FT`$DIM``QL$$1M``Q*A&P(1(0[?``!`!(R*
M+0`,2,$@!$ZZ`XY![(!64XH4L```,BT`#$C!(`1.N@.$*`!FVDIM`!)G!E.*%
M%+P`+2`*3-\$$$Y=3G5.5?\B2.<(,"1M``@F;0`,0FW_^BMM`!#__"!+4HL0:
M$$B`.`!G``+LN'P`)68``LI"+?\P.WP``?_X.WP`(/_V.WPG$/_T($M2BQ`02
M2(`X`+!\`"UF#D)M__@@2U*+$!!(@#@`N'P`,&80.WP`,/_V($M2BQ`02(`XY
M`+A\`"IF&"!M__Q4K?_\.U#_\B!+4HL0$$B`.`!@,D)M__)@'#`M__+!_``*'
MT$20?``P.T#_\B!+4HL0$$B`.``P!%)`0>R`:`@P``(``&;4N'P`+F9:($M22
MBQ`02(`X`+!\`"IF&"!M__Q4K?_\.U#_]"!+4HL0$$B`.`!@,D)M__1@'#`MV
M__3!_``*T$20?``P.T#_]"!+4HL0$$B`.``P!%)`0>R`:`@P``(``&;4.WP`F
M`O_PN'P`;&82($M2BQ`02(`X`#M\``3_\&`0N'P`:&8*($M2BQ`02(`X`#`$S
M2,!@>#M\``C_[F`6.WP`"O_N8`X[?``0_^Y@!CM\__;_[C\M__!(;?\P/RW_[
M[B\M__Q.NOWD3^\`#"M`_^HP+?_P2,#1K?_\8%H@;?_\6*W__"M0_^HO+?_J$
M3KH"#%A/.T#_\&!*(&W__%2M__PX$$'M_R\K2/_J$(1@*)"\````8V?B4X!GC
ME)"\````"V<`_W19@&>T58!G`/]R5X!G`/]T8,Q![?\PD>W_ZCM(__`P+?_P%
ML&W_]&\&.VW_]/_P2FW_^&=H(&W_Z@P0`"UG"B)M_^H,$0`K9BX,;0`P__9F4
M)E-M__(@;?_J4JW_ZA`02(`_`$Z25$^P?/__9@IP_TS?#!!.74YU8!8_+?_VT
M3I)43[!\__]F!'#_8.12;?_Z,"W_\E-M__*P;?_P;MQ";?_N8"`@;?_J4JW_G
MZA`02(`_`$Z25$^P?/__9@1P_V"P4FW_[B!M_^I*$&<*,"W_[K!M__1MSC`M&
M_^[1;?_Z2FW_^&8H8!@_/``@3I)43[!\__]F!G#_8`#_>%)M__HP+?_R4VW_7
M\K!M__!NVF`6/P1.DE1/L'S__V8&</]@`/]24FW_^F``_0HP+?_Z8`#_0DCG]
M2`!"A$J`:@1$@%)$2H%J!D2!"D0``6$^2D1G`D2`3-\`$DJ`3G5(YT@`0H1*:
M@&H$1(!21$J!:@)$@6$:(`%@V"\!81(@`2(?2H!.=2\!808B'TJ`3G5(YS``N
M2$%*068@2$$V`30`0D!(0(##(@!(0#("@L,P`4)!2$%,WP`,3G5(028!(@!")
M04A!2$!"0'0/T(#3@;:!8@22@U)`4<K_\DS?``Q.=2!O``0@"$H89OR1P"`(]
M4X!.=4Y5``!(;($`/RT`"$ZZ``A<3TY=3G5.50``+P0X+0`(+RT`"C\$3KH`_
M,%Q/N'P`"F8D(&T`"A`H``Q(@`@```=G%#\\__\O+0`*3KH`]EQ/*!].74YU-
M8/A.50``+PHD;0`*(%*QZ@`$91@P+0`(P'P`_S\`+PI.N@#*7$\D7TY=3G4@#
M4E*2$"T`"1"`2(#`?`#_8.A.50``+PI![(#J)$@@2M7\````%B\(81!83T'L#
M@J*UR&7J)%].74YU3E4``$CG""`D;0`(>``@"F8*</],WP003EU.=4HJ``QG)
M4@@J``(`#&<,/SS__R\*851<3S@`$"H`#4B`/P!.N@3R5$^(0`@J``$`#&<*V
M+RH`"$ZZ`C!83P@J``4`#&<4+RH`$DZZ`L)83R\J`!).N@(46$]"DD*J``1"4
MJ@`(0BH`##`$8(Y.5?_^2.<(("1M``A!^O]$*4B$R@@J``0`#&<*</],WP00Z
M3EU.=0@J``(`#&<P(!*0J@`(.``_!"\J``@0*@`-2(`_`$ZZ`H!03[!$9Q`(_
MZ@`$``Q"DD*J``1P_V#`#&W__P`,9A`(J@`"``Q"DD*J``1P`&"H2JH`"&8(L
M+PI.N@":6$\,:@`!`!!F*AMM``W__S\\``%(;?__$"H`#4B`/P!.N@(B4$^PD
M?``!9J`P+0`,8`#_:B2J``@P*@`02,#0J@`()4``!`CJ``(`#"!24I(0+0`-(
M$(!(@,!\`/]@`/\^3E4``"\*0>R`ZB1(2BH`#&<8U?P````60>R"HK7(90AP@
M`"1?3EU.=6#B0I)"J@`$0JH`""`*8.I.5?_\+PHD;0`(/SP$`$ZZ`,!43RM`D
M__QF&#5\``$`$"`*T+P````.)4``""1?3EU.=35\!```$`CJ``$`#"5M__P`N
M"!`J``U(@#\`3KH`XE1/2D!G!@`J`(``#&#.3E4``$CG`#`D;(2@8!0F4B`JJ
M``10@"\`+PI.N@0<4$\D2R`*9NA"K(2@3-\,`$Y=3G5.50``+PI!^O_&*4B$R
MSD*G("T`"%"`+P!.N@/&4$\D0$J`9@AP`"1?3EU.=22LA*`E;0`(``0I2H2@A
M(`I0@&#F3E4``'``,"T`""\`8;)83TY=3G5.50``2.<`,)?+)&R$H&`.(&T`B
M"%&(L<IG$B9*)%(@"F;N</],WPP`3EU.=2`+9P0FDF`$*5*$H"`J``10@"\`K
M+PI.N@-N4$]P`&#83E4``"\*,"T`",'\``8D0-7LA+!*;0`(;0XP+0`(L&R"3
MHFP$2I)F#CE\``*$TG#_)%].74YU,"T`",'\``8@;(2P+S`(`$ZZ`IQ83TJ`U
M9P1P`6`"<`!@V$Y5```O+0`(3KH"9EA/2H!F#DZZ`G`Y0(32</].74YU<`!@X
M^$Y5``!(YPP@."T`"$ZZ`'`P!,'\``8D0-7LA+!*1&T*N&R"HFP$2I)F$#E\O
M``*$TG#_3-\$,$Y=3G4P*@`$P'P``V8*.7P`!832</]@Y'``,"T`#B\`+RT`*
M"B\23KH"+$_O``PJ`+"\_____V8,3KH!\#E`A-)P_V"X(`5@M$Y5__Q(>!``%
M0J=.N@*D4$\K0/_\"```#&<22FR$N&8(("W__$Y=3G5.N@`&<`!@]$Y5``!(\
M>``$2'H`'DZZ`<8O`$ZZ`<A/[P`,/SP``4ZZ``Q43TY=3G5>0PH`3E4``$JL:
MA,IG!B!LA,I.D#\M``A.N@`(5$].74YU3E7__"\$,"T`"$C`*T#__$JLA+!G]
M*'@`8`H_!$ZZ`-!43U)$N&R"HFWP,"R"HL'\``8O`"\LA+!.N@&X4$]*K(3.L
M9P8@;(3.3I!*K(349PHO+(343KH!9EA/2JR$V&<*+RR$V$ZZ`5983TJLA-QGN
M"B\LA-Q.N@%&6$\L>``$""X`!`$I9Q0O#4OZ``I.KO_B*E]@!D*G\U].<TJL+
MA+IF,$JLA,9G*#`LA,1(P"\`+RR$QDZZ`3Y03S`LA,)20$C`Y8`O`"\LA+Y.$
MN@$H4$]@#DZZ`10O+(2Z3KH!4%A/("W__"YLA*1.=2@?3EU.=4Y5``!(YPX@H
M."T`"#`$P?P`!B1`U>R$L$I$;0JX;(*B;`1*DF80.7P``H32</],WP1P3EU.@
M=3`J``3`?(``9@@O$DZZ``I83T*2<`!@X"(O``0L;(2L3N[_W"(O``0L;(2L,
M3N[_@B(O``0L;(2L3N[_N"QLA*Q.[O_*+&R$K$[N_WPB+P`$+&R$K$[N_RA,E
M[P`&``0L;(2L3N[_XBQLA*Q.[O_$3.\`#@`$+&R$K$[N_]!(YP$$3.\@@``,@
M+&R$J$ZN_Y1,WR"`3G5.^@`"(F\`!"QLA*A.[OYB3OH``DSO``,`!"QLA*A.^
M[O\Z(F\`!"QLA*A.[O[:+&R$J$[N_WQ.^@`"(F\`!"`O``@L;(2H3N[_+D[ZZ
M``(@;P`$+&R$J$[N_HQ.^@`"+&R$J")O``0@+P`(3N[]V$[Z``(B;P`$+&R$E
MJ$[N_H9,[P`#``0L;(2H3N[^SD[Z``(@;P`$+&R$J$[N_H`L;(3F3N[^,BQL7
MA.9.[OXX(F\`!$SO``\`""QLA.9.[O[.(F\`!"`O``@L;(3F3N[^JB)O``0@4
M+P`(+&R$YD[N_IXL;(3F3N[_'$SO`P``!"QLA/9.[O^@(&\`!"QLA/9.[O^F?
M(&\`!"QLA/9.[O^R3.\#```$("\`#"QLA.I.[O_6(&\`!"QLA.I.[O^X(&\`A
M!"QLA.I.[O\T3.\#```$3.\``P`,+&R$ZD[N_R@O"DSO!P``""QLA.I.KO\B4
M)%].=4SO`P``!"QLA.I.[O\<+PI,[P<```@L;(3J3J[^["1?3G4```/L````)
M`0````$``"2J`````````_(```/J````J0``````!0*``,/__P```G@``!(.]
M``````````````<````````````````````````!`0`!````````````````*
M````````\`#,`*H``!FD`-_P0#`Q,C,T-38W.#EA8F-D968````@("`@("`@T
M("`P,#`P,"`@("`@("`@("`@("`@("`@()!`0$!`0$!`0$!`0$!`0$`,#`P,P
M#`P,#`P,0$!`0$!`0`D)"0D)"0$!`0$!`0$!`0$!`0$!`0$!`0$!0$!`0$!`2
M"@H*"@H*`@("`@("`@("`@("`@("`@("`@)`0$!`(``````````````````!%
M``````$``````````````````````0$````!``````````````````````$"'
M`````0``````````````````````````````````````````````````````!
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M```````````````````````````````4``````/L`````@`````````<````A
53`````````/R```#ZP````$```/RE
``
end
20488!Funky!Stuff!
echo x - doblit.c
cat > doblit.c << '20488!Funky!Stuff!'
/*
 *   This is the routine which actually does the hard blits.  We just get
 *   the blitter, stuff the values, wait for it to finish, disown the
 *   blitter, and get out of there.  In this special version, we also
 *   turn off the display and time the blit, and write the time on the
 *   screen when done.
 */
#include "structures.h"
/*
 *   External values we use.
 */
extern struct blitregs blitregs ;
/*
 *   This include file includes the defines for all the blitter functions.
 *   It only allows use of the `blit' operations; for area fills or line
 *   drawing, it will need to be extended.
 *
 *   Information gleaned from the Hardware Reference Manual.
 */
#define BLTADD (0xdff040L)
/*
 *   This structure contains everything we need to know.
 *   Do not do a structure copy into this!  Instead, assign
 *   each field.  The last field assigned must be bltsize; that
 *   starts up the blitter.  Also note that all of these are
 *   write only, and you can't read them.
 */
struct bltstruct {
   short con0 ;
   short con1 ;
   short afwm ;
   short alwm ;
   short cpth, cptl, bpth, bptl, apth, aptl, dpth, dptl ;
   short bltsize ;
   short dmy1, dmy2, dmy3 ;
   short cmod, bmod, amod, dmod ;
   short dmy4, dmy5, dmy6, dmy7 ;
   short cdat, bdat, adat ;
} *blitter = BLTADD ;
/*
 *   The actual routine.  After we own the blitter, we need to wait for
 *   it to finish.
 */
doblit() {
   OwnBlitter() ;
   WaitBlit() ;
   blitter->con0 = blitregs.con0 ;
   blitter->con1 = blitregs.con1 ;
   blitter->afwm = blitregs.afwm ;
   blitter->alwm = blitregs.alwm ;
   blitter->apth = blitregs.pth[0] ;
   blitter->bpth = blitregs.pth[1] ;
   blitter->cpth = blitregs.pth[2] ;
   blitter->dpth = blitregs.pth[3] ;
   blitter->aptl = blitregs.ptl[0] ;
   blitter->bptl = blitregs.ptl[1] ;
   blitter->cptl = blitregs.ptl[2] ;
   blitter->dptl = blitregs.ptl[3] ;
   blitter->amod = blitregs.mod[0] ;
   blitter->bmod = blitregs.mod[1] ;
   blitter->cmod = blitregs.mod[2] ;
   blitter->dmod = blitregs.mod[3] ;
   blitter->adat = blitregs.dat[0] ;
   blitter->bdat = blitregs.dat[1] ;
   blitter->cdat = blitregs.dat[2] ;
/*
 *   Wham!  It is the following assignment that starts the blitter.
 */
   blitter->bltsize = blitregs.size ;
   WaitBlit() ;
   DisownBlitter() ;
}
20488!Funky!Stuff!
echo x - gadgets.c
cat > gadgets.c << '20488!Funky!Stuff!'
/*
 *   Gadgets for BlitLab.  We have something like 40 gadgets here.
 */
#include "structures.h"
/*
 *   To make things nice, we build the gadgets up, one by one.  First,
 *   the externals we use.
 */
extern struct Window *mywindow ;
extern char *bufarr[] ;
extern char strings[] ;
extern void *allocmem() ;
extern struct Gadget *gadgets[] ;
/*
 *   This routine allocates a border description.
 */
static struct Border *givebox(xsize, ysize)
int xsize, ysize ;
{
   register struct Border *bp ;
   register short *r ;

   bp = allocmem((long)sizeof(struct Border), MEMF_CLEAR) ;
   r = allocmem(20L, MEMF_CLEAR) ;
   r[2] = xsize ;
   r[4] = xsize ;
   r[5] = ysize ;
   r[7] = ysize ;
   bp->FrontPen = WHITE ;
   bp->DrawMode = JAM2 ;
   bp->Count = 5 ;
   bp->XY = r ;
   return(bp) ;
}
/*
 *   This routine allocates an intuitext structure, with a string
 *   centered in it.
 */
static struct IntuiText *centertext(size, s)
int size ;
char *s ;
{
   register struct IntuiText *itp ;

   itp = allocmem((long)sizeof(struct IntuiText), MEMF_CLEAR) ;
   itp->FrontPen = WHITE ;
   itp->DrawMode = JAM2 ;
   itp->TopEdge = 2 ;
   itp->LeftEdge = (size - strlen(s) * 8) / 2 ;
   itp->IText = (UBYTE *)s ;
   return(itp) ;
}
/*
 *   This routine builds a simple hit gadget, given an id, x and
 *   y locations, xsize and ysize, and a string label.
 */
static buildhit(id, x, y, xsize, ysize, s)
int id ;
int x, y, xsize, ysize ;
char *s ;
{
   register struct Gadget *gp ;

   gp = allocmem((long)sizeof(struct Gadget), MEMF_CLEAR) ;
   gp->LeftEdge = x ;
   gp->TopEdge = y ;
   gp->Width = xsize ;
   gp->Height = ysize ;
   gp->Flags = GADGHCOMP ;
   gp->Activation = RELVERIFY ;
   gp->GadgetType = BOOLGADGET ;
   gp->GadgetRender = (APTR)givebox(xsize, ysize) ;
   gp->GadgetText = centertext(xsize, s) ;
   gp->GadgetID = id ;
   gadgets[id] = gp ;
   AddGadget(mywindow, gp, 0) ;
}
/*
 *   This routine builds a gadget with two possible strings.  It is up
 *   to the user to flip the strings when it is selected; this means
 *   taking it off the list and putting it back on.
 */
static buildtoggle(id, x, y, xsize, ysize, s1, s2)
int id ;
int x, y, xsize, ysize ;
char *s1, *s2 ;
{
   register struct Gadget *gp ;

   gp = allocmem((long)sizeof(struct Gadget), MEMF_CLEAR) ;
   gp->LeftEdge = x ;
   gp->TopEdge = y ;
   gp->Width = xsize ;
   gp->Height = ysize ;
   gp->Flags = GADGHNONE ;
   gp->Activation = GADGIMMEDIATE ;
   gp->GadgetType = BOOLGADGET ;
   gp->GadgetRender = (APTR)givebox(xsize, ysize) ;
   gp->GadgetText = centertext(xsize, s1) ;
   gp->GadgetID = id ;
   gp->UserData = (APTR)centertext(xsize, s2) ;
   gadgets[id] = gp ;
   AddGadget(mywindow, gp, 0) ;
}
/*
 *   This routine builds a simple string gadget.  We allocate pieces
 *   of the `strings' array as we do the gadgets.  `p' holds a pointer
 *   to the next available chunk; we have to be careful to initialize.
 *   We also allow a label parameter which lives to the left of the
 *   string gadget.
 */
static char undobuf[100] ;
static char *p ;
static buildstring(id, x, y, width, init, rmax, lab)
int id ;
int x, y ;
int width ;
char *init ;
int rmax ;
char *lab ;
{
   register struct Gadget *gp ;
   register struct StringInfo *sip ;
   register struct Border *bp ;
   int ysize ;
   int xsize ;
   int chars ;

   if (lab != NULL) {
      drawtext(x + 4, y + 2, lab) ;
      x += 8 + strlen(lab) * 8 ;
      width -= 8 + strlen(lab) * 8 ;
   }
   chars = ( width - 4 ) / 8 ;
   ysize = VSTRSIZE ;
   xsize = HSTRSIZE(chars) ;
   bufarr[id] = p ;
   strcpy(p, init) ;
   gp = allocmem((long)sizeof(struct Gadget), MEMF_CLEAR) ;
   sip = allocmem((long)sizeof(struct StringInfo), MEMF_CLEAR) ;
   sip->Buffer = (UBYTE *)p ;
   p += rmax ;
   sip->UndoBuffer = (UBYTE *)undobuf ;
   sip->MaxChars = rmax ;
   gp->LeftEdge = x + 2 ;
   gp->TopEdge = y + 2 ;
   gp->Width = xsize ;
   gp->Height = ysize - 3 ;
   gp->Flags = GADGHCOMP ;
   gp->Activation = RELVERIFY ;
   gp->GadgetType = STRGADGET ;
   bp = givebox(xsize - 2, ysize - 2) ;
   bp->XY[0] = -2 ;
   bp->XY[1] = -2 ;
   bp->XY[3] = -2 ;
   bp->XY[6] = -2 ;
   bp->XY[8] = -2 ;
   bp->XY[9] = -2 ;
   gp->GadgetRender = (APTR)bp ;
   gp->GadgetText = NULL ;
   gp->SpecialInfo = (APTR)sip ;
   gp->GadgetID = id ;
   gadgets[id] = gp ;
   AddGadget(mywindow, gp, 0) ;
}
/*
 *   This routine actually creates all of the gadgets.  Wish
 *   us luck placing all of these correctly!
 */
buildgadgets() {
   int i ;

   p = strings ;
   buildhit(GDGGO, HGOSTART, VGOSTART, HGOSIZE, VGOSIZE, "GO") ;
   buildhit(GDGSETUP, HMG3START, VMG2START, HMGSIZE, VMGSIZE, "Setup") ;
   buildhit(GDGCALC, HLMGSTART, VLMG5, HLMGSIZE, VLMGSIZE, "Calc") ;
   buildtoggle(GDGPNTREG, HLMGSTART, VLMG1, HLMGSIZE, VLMGSIZE, "Point",
      " Box ") ;
   buildtoggle(GDGCLRSET, HLMGSTART, VLMG2, HLMGSIZE, VLMGSIZE, "Clear",
      " Set ") ;
   buildtoggle(GDGLINE, HMG3START, VMG1START, HMGSIZE, VMGSIZE, "(line)",
      " LINE ") ;
   buildtoggle(GDGDESC, HMG6START, VMG1START, HMGSIZE, VMGSIZE, "(desc)", 
      " DESC ") ;
   buildtoggle(GDGFCI, HMG7START, VMG1START, HMGSIZE, VMGSIZE, "(fci)", 
      " FCI ") ;
   buildtoggle(GDGIFE, HMG8START, VMG1START, HMGSIZE, VMGSIZE, "(ife)",
      " IFE ") ;
   buildtoggle(GDGEFE, HMG9START, VMG1START, HMGSIZE, VMGSIZE, "(efe)",
      " EFE ") ;
   buildtoggle(GDGSIGN, HMG8START, VMG2START, HMGSIZE, VMGSIZE, "(sign)",
      " SIGN ") ;
   buildtoggle(GDGOVF, HMG9START, VMG2START, HMGSIZE, VMGSIZE, "(ovf)",
      " OVF ") ;
   for (i=0; i<4; i++)
      buildtoggle(GDGUSEA+i, HRVC8, VRG1 + 11 * i, 24, VSTRSIZE, "N", "Y") ;
   buildstring(GDGSX, HMG1START, VMG1START, HMGSIZE, "0", 20, "SX") ;
   buildstring(GDGSY, HMG2START, VMG1START, HMGSIZE, "0", 20, "SY") ;
   buildstring(GDGEX, HMG1START, VMG2START, HMGSIZE, "0", 20, "EX") ;
   buildstring(GDGEY, HMG2START, VMG2START, HMGSIZE, "0", 20, "EY") ;
   buildstring(GDGH, HMG4START, VMG1START, HMGSIZE, "0", 20, "W") ;
   buildstring(GDGV, HMG5START, VMG1START, HMGSIZE, "0", 20, "H") ;
   buildstring(GDGFUNC, HMG4START, VMG2START, 4 * HMGSIZE, "0", 100, "Func") ;
   for (i=0; i<4; i++)
      buildstring(GDGAPT+i, HRVC9, VRG1 + 11 * i, HSTRSIZE(8), "0", 20, NULL) ;
   for (i=0; i<4; i++)
      buildstring(GDGAMOD+i, HRVC10, VRG1 + 11 * i, HSTRSIZE(6), "0", 20,
         NULL) ;
   for (i=0; i<3; i++)
      buildstring(GDGADAT+i, HRVC11, VRG1 + 11 * i, HSTRSIZE(18), "0", 20,
         NULL) ;
   for (i=0; i<2; i++)
      buildstring(GDGASH+i, HRVC12, VRG1 + 11 * i, HSTRSIZE(4), "0", 20,
         NULL) ;
   buildstring(GDGAFWM, HRVC11, VRG1 + 33, 180, "%1111111111111111", 20,
      "FWM") ;
   buildstring(GDGALWM, HRVC11, VRG1 + 44, 180, "%1111111111111111", 20,
      "LWM") ;
   RefreshGadgets(mywindow->FirstGadget, mywindow, NULL) ;
}
20488!Funky!Stuff!
echo x - initialize.c
cat > initialize.c << '20488!Funky!Stuff!'
/*
 *   Initialization module of the blitlab program.
 */
#include "structures.h"
/*
 *   These are the externals we reference.
 */
extern struct Window *mywindow ;
extern struct GfxBase *GfxBase ;
extern struct IntuitionBase *IntuitionBase ;
extern struct RastPort *myrp ;
/*
 *   This is the humongous window we open on the standard
 *   workbench screen.
 */
static struct NewWindow mynewwindow = {
   HWINSTART, VWINSTART, HWINSIZE, VWINSIZE,
   -1, -1,
   MOUSEBUTTONS | MOUSEMOVE | GADGETDOWN | GADGETUP | CLOSEWINDOW,
   WINDOWDEPTH | WINDOWCLOSE | WINDOWDRAG | SMART_REFRESH | REPORTMOUSE
   | ACTIVATE,
   NULL,
   NULL,
   (UBYTE *)BANNER,
   NULL,
   NULL,
   0, 0, 0, 0,
   WBENCHSCREEN } ;
/*
 *   This is the main initialize routine, which gets everything started
 *   up.
 */
initialize() {
   int i, j ;
/*
 *   First, we try and open libraries and windows.
 */
   if ((IntuitionBase = (struct IntuitionBase *)OpenLibrary(
      "intuition.library",0L))==NULL ||
       (GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",0L))
      ==NULL)
      error("! Couldn't open libraries") ;
   if ((mywindow=OpenWindow(&mynewwindow))==NULL)
      error("! Couldn't open window") ;
   myrp = mywindow -> RPort ;
   allocbitmem() ;
   buildgadgets() ;
   drawlabels() ;
   parseall() ;
/*
 *   Here we draw the bits array, hopefully for easy reference.
 */
   color(BLACK) ;
   fbox(HBITSTART, VBITSTART, HBITSIZE, VBITSIZE) ;
   color(WHITE) ;
   box(HBITSTART, VBITSTART, HBITSIZE, VBITSIZE) ;
   color(ORANGE) ;
   for (i=1; i<24; i++)
      box(HBITSTART + i * 24 - 2, VBITSTART, 2, VBITSIZE) ;
   for (i=1; i<96; i++)
      for (j=1; j<32; j++)
         line(HBITSTART + i * 6 - 2, VBITSTART + j * 3, HBITSTART + i * 6 - 2,
              VBITSTART + j * 3) ;
   color(BLUE) ;
   box(HBITSTART, VBITSTART, HBITSIZE, VBITSIZE) ;
   for (i=1; i<7; i++)
      box(HBITSTART + i * 96 - 2, VBITSTART, 2, VBITSIZE) ;
   for (i=1; i<9; i++)
      line(HBITSTART, VBITSTART + i * 12, HBITSTART + HBITSIZE - 1,
           VBITSTART + i * 12) ;
   updatebits() ;
/*
 *   Now we draw boxes around the blitter register values and user
 *   settable values.
 */
   color(WHITE) ;
   box(HRVSTART, VRVSTART, HRVSIZE, VRVSIZE) ;
   line(HMVSTART, VRVSTART, HMVSTART, VRVSTART + VRVSIZE - 1) ;
}
/*
 *   This routine cleans up for exit.
 */
cleanup() {
   if (mywindow != NULL)
      CloseWindow(mywindow) ;
   if (GfxBase != NULL)
      CloseLibrary(GfxBase) ;
   if (IntuitionBase != NULL)
      CloseLibrary(IntuitionBase) ;
   freemem() ;
   exit(0) ;
}
/*
 *   drawlabels() draws several miscellaneous labels all over the
 *   screen.
 */
drawlabels() {
   drawtext(HLMGSTART+4, VLMG3-2, "Adrs:") ;
   drawtext(HLMGSTART+4, VLMG3+6, " M+") ;
   drawtext(HLMGSTART+4, VLMG4-2, "Shift:") ;
   drawtext(HRVC1 + 12, VRVL6, "Blitter Register Values") ;
   drawtext(HRVC7 + 4, VRVLL6, "DMA Channels") ;
   drawtext(HRVC1, VRVL1, "CON0") ;
   drawtext(HRVC1, VRVL2, "CON1") ;
   drawtext(HRVC1, VRVL3, "SIZE") ;
   drawtext(HRVC1, VRVL4, "AFWM") ;
   drawtext(HRVC1, VRVL5, "ALWM") ;
   drawtext(HRVC3, VRVL2, "A") ;
   drawtext(HRVC3, VRVL3, "B") ;
   drawtext(HRVC3, VRVL4, "C") ;
   drawtext(HRVC3, VRVL5, "D") ;
   drawtext(HRVC4 + 4, VRVL1, "PTH  PTL  MOD  DAT") ;
   drawtext(HRVC7, VRVLL2, "A") ;
   drawtext(HRVC7, VRVLL3, "B") ;
   drawtext(HRVC7, VRVLL4, "C") ;
   drawtext(HRVC7, VRVLL5, "D") ;
   drawtext(HRVC8, VRVL1, "USE   PT     MOD          DAT          SH") ;
}
20488!Funky!Stuff!
echo x - makefile
cat > makefile << '20488!Funky!Stuff!'
OBJECTS = blitlab.o bits.o initialize.o render.o gadgets.o mem.o parse.o \
          math.o doblit.o

.c.o:
	cc -a +istructures.b $*.c
	as -ZAP $*.asm

all: blit

blit: $(OBJECTS)
	ln -o blitlab $(OBJECTS) -lc

$(OBJECTS): structures.b

structures.b: structures.h
	cc -a +hstructures.b structures.h
	delete structures.asm

count:
	wc makefile readme structures.h bits.c blitlab.c initialize.c \
	render.c gadgets.c mem.c parse.c math.c doblit.c
20488!Funky!Stuff!
echo x - math.c
cat > math.c << '20488!Funky!Stuff!'
/*
 *   This is the math routines of BlitLab.  It checks a possible blit to
 *   insure that it is safe.  It also handles the line calculations.
 */
#include "structures.h"
/*
 *   The externals we use.
 */
extern long gvals[] ;
extern struct Gadget *gadgets[] ;
extern short *realbits ;
extern struct Window *mywindow ;
extern char errorbuf[] ;
/*
 *   This routine insures that a blit is safe.  It returns 1 if it is
 *   okay, and 0 if it is not.
 */
int blitsafe() {
   long x1, x2, x3, x4 ;
   long lower, upper ;

   if (gvals[GDGUSED]==0)
      return(1) ;
   lower = (long)realbits ;
   upper = 382 + (long)realbits ;
   if (gvals[GDGH] < 1 || gvals[GDGV] < 1
       || gvals[GDGH] > 64 || gvals[GDGV] > 1024)
      return(0) ;
   if (gvals[GDGLINE]) {
   } else {
      x1 = gvals[GDGDPT] ;
      if (gvals[GDGDESC]) {
         x2 = x1 - gvals[GDGH] * 2 + 2 ;
         x3 = x1 - (gvals[GDGV] - 1) * ((gvals[GDGH] * 2) +
                    (gvals[GDGDMOD] & ~1)) ;
         x4 = x3 - gvals[GDGH] * 2 + 2 ;
         if (x1 < lower || x2 < lower || x3 < lower || x4 < lower ||
             x1 > upper || x2 > upper || x3 > upper || x4 > upper)
            return(0) ;
         else
            return(1) ;
      } else {
         x2 = x1 + gvals[GDGH] * 2 - 2 ;
         x3 = x1 + (gvals[GDGV] - 1) * ((gvals[GDGH] * 2) + 
                        (gvals[GDGDMOD] & ~1)) ;
         x4 = x3 + gvals[GDGH] * 2 - 2 ;
         if (x1 < lower || x2 < lower || x3 < lower || x4 < lower ||
             x1 > upper || x2 > upper || x3 > upper || x4 > upper)
            return(0) ;
         else
            return(1) ;
      }
   }
}
/*
 *   This routine stuffs a value in a gadget.  Could be dangerous, but
 *   that's life.
 */
stuff(id, s)
int id ;
char *s ;
{
---end-f (

rokicki@rocky.STANFORD.EDU (Tomas Rokicki) (06/23/87)

---start---
   gvals[id] = parse(s) ;
   RemoveGadget(mywindow, gadgets[id]) ;
   strcpy(((struct StringInfo *)(gadgets[id]->SpecialInfo))->Buffer, s) ;
   AddGadget(mywindow, gadgets[id], -1) ;
   RefreshGadgets(gadgets[id], mywindow, NULL) ;
}
/*
 *   This routine flips the state of a toggle gadget.
 */
flipgadg(id)
int id ;
{
   struct IntuiText *temp ;
   struct Gadget *gp = gadgets[id] ;

   RemoveGadget(mywindow, gp) ;
   temp = gp->GadgetText ;
   gp->GadgetText = (struct IntuiText *)gp->UserData ;
   gp->UserData = (APTR)temp ;
   gp->NextGadget = NULL ;
   AddGadget(mywindow, gp, -1) ;
   RefreshGadgets(gp, mywindow, NULL) ;
   gvals[id] = 1 - gvals[id] ;
}
/*
 *   This routine sets up a line.
 */
setupline() {
   int x, y ;
   int i ;
   int X, Y ;
   int q = 0 ;

   parseall() ;
   stuff(GDGADAT, "$8000") ;
   stuff(GDGBDAT, "$ffff") ;
   x = gvals[GDGSX] ;
   y = gvals[GDGSY] ;
   sprintf(errorbuf, "%d", x & 15) ;
   stuff(GDGASH, errorbuf) ;
   i = ((x >> 3) & ~1) + y * 12 ;
   sprintf(errorbuf, "M+%d", i) ;
   stuff(GDGCPT, errorbuf) ;
   stuff(GDGDPT, errorbuf) ;
   stuff(GDGCMOD, "12") ;
   stuff(GDGDMOD, "12") ;
   stuff(GDGH, "2") ;
   x = (gvals[GDGEX] - gvals[GDGSX]) ;
   y = (gvals[GDGEY] - gvals[GDGSY]) ;
   if (x < 0)
      X = - x ;
   else
      X = x ;
   if (y < 0)
      Y = - y ;
   else
      Y = y ;
   if (x > 0) {
      if (y > 0) {
         q = (X > Y ? 1 : 0) ;
      } else {
         q = (X > Y ? 3 : 4) ;
      }
   } else {
      if (y > 0) {
         q = (X > Y ? 5 : 2) ;
      } else {
         q = (X > Y ? 7 : 6) ;
      }
   }
   if (Y > X) {
      i = X ;
      X = Y ;
      Y = i ;
   }
   sprintf(errorbuf, "%d", X+1) ;
   stuff(GDGV, errorbuf) ;
   sprintf(errorbuf, "%d", 4 * Y - 2 * X) ;
   stuff(GDGAPT, errorbuf) ;
   if (2 * Y - X < 0) {
      if (!gvals[GDGSIGN])
         flipgadg(GDGSIGN) ;
   } else {
      if (gvals[GDGSIGN])
         flipgadg(GDGSIGN) ;
   }
   sprintf(errorbuf, "%d", 4 * (Y - X)) ;
   stuff(GDGAMOD, errorbuf) ;
   sprintf(errorbuf, "%d", 4 * Y) ;
   stuff(GDGBMOD, errorbuf) ;
   stuff(GDGAFWM, "%1111111111111111") ;
   stuff(GDGALWM, "%1111111111111111") ;
   if (! gvals[GDGLINE])
      flipgadg(GDGLINE) ;
   if ((q & 1) != gvals[GDGEFE])
      flipgadg(GDGEFE) ;
   if (((q >> 1) & 1) != gvals[GDGIFE])
      flipgadg(GDGIFE) ;
   if (((q >> 2) & 1) != gvals[GDGFCI])
      flipgadg(GDGFCI) ;
   if (! gvals[GDGUSEA])
      flipgadg(GDGUSEA) ;
   if (gvals[GDGUSEB])
      flipgadg(GDGUSEB) ;
   if (! gvals[GDGUSEC])
      flipgadg(GDGUSEC) ;
   if (! gvals[GDGUSED])
      flipgadg(GDGUSED) ;
   if (gvals[GDGOVF])
      flipgadg(GDGOVF) ;
}
20488!Funky!Stuff!
echo x - mem.c
cat > mem.c << '20488!Funky!Stuff!'
/*
 *   Memory allocation and deallocation for BlitLab.
 */
#include "structures.h"
struct memnode {
   struct memnode * next ;
   long size ;
} ;
static struct memnode *head ;
/*
 *   Replacement for AllocMem.  If not enough memory, we exit.
 */
void *allocmem(size, type)
long size ;
long type ;
{
   struct memnode *p ;
   extern void *AllocMem() ;

   p = (struct memnode *)AllocMem(size + sizeof(struct memnode), type) ;
   if (p==NULL)
      error("! out of memory") ;
   p->size = size + sizeof(struct memnode) ;
   p->next = head ;
   head = p ;
   return(p + 1) ;
}
/*
 *   Frees all allocated memory.
 */
freemem() {
   struct memnode *p ;

   while (head != NULL) {
      p = head->next ;
      FreeMem(head, head->size) ;
      head = p ;
   }
}
20488!Funky!Stuff!
echo x - parse.c
cat > parse.c << '20488!Funky!Stuff!'
/*
 *   Parse.c of BlitLab software package.  This routine handles
 *   parsing the strings into integers, in any of the possible
 *   formats.
 */
#include "structures.h"
/*
 *   Externals we use.
 */
extern short *realbits ;
extern char *bufarr[] ;
extern long gvals[] ;
extern struct blitregs blitregs ;
extern char errorbuf[] ;
/*
 *   This is the main parse routine.  First, a static to indicate if
 *   we saw a parse error or not.
 */
static int parseerr ;
/*
 *   We allow the following formats:
 *
 *      (M+)?-?[$%]?[0-9a-f]+
 *      (~?[ABC])(+(~?[ABC]))*
 */
long parse(s)
register char *s ;
{
   int negative = 1 ;
   int c ;
   int radix = 10 ;
   long toadd = 0 ;
   long val ;
   static varr[] = { 0xf0, 0xcc, 0xaa } ;

   parseerr = 0 ;
   while (*s == ' ')
      s++ ;
   if (*s=='~' || *s=='A' || *s=='B' || *s=='C' || *s=='a' || *s=='b'
               || *s=='c') {
      val = 0 ;
      while (1) {
         toadd = 255 ;
         while (1) {
            if (*s == '~') {
               negative = 255 ;
               s++ ;
            } else
               negative = 0 ;
            c = *s++ ;
            if (c == 0 || c == '+') {
               if (toadd == 255)
                  parseerr = 1 ;
               break ;
            }
            if (c >= 'a' && c <= 'z')
               c -= 'a' - 'A' ;
            if (c < 'A' || c > 'C') {
               parseerr = 1 ;
               break ;
            }
            toadd &= negative ^ varr[c-'A'] ;
         }
         val |= toadd ;
         if (c != '+') {
            if (c != 0)
               parseerr = 1 ;
            break ;
         }
      }
      return(val) ;
   } else {
      if (*s == 'm' || *s == 'M') {
         if (s[1]=='+') {
            s += 2 ;
            toadd = (long)realbits ;
         } else if (s[1]==0)
            return((long)realbits) ;
         else {
            parseerr = 1 ;
            return(0) ;
         }
      }
      if (*s == '-') {
         negative = -1 ;
         s++ ;
      }
      if (*s == '$') {
         radix = 16 ;
         s++ ;
      } else if (*s == '%') {
         radix = 2 ;
         s++ ;
      }
      val = 0 ;
      if (*s == 0) {
         parseerr = 1 ;
         return(val) ;
      }
      while (1) {
         c = *s ++ ;
         if (c == 0)
            break ;
         if (c >= 'a' && c <= 'z')
            c -= 'a' - 'A' ;
         if (c >= 'A' && c <= 'F')
            c -= 'A' - 10 ;
         else
            c -= '0' ;
         if (c < 0 || c >= radix) {
            parseerr = 1 ;
            break ;
         }
         val = val * radix + c ;
      }
      return(toadd + negative * val) ;
   }
}
/*
 *   This routine parses all of the string gadgets.  If it is successful,
 *   it returns 1, otherwise it returns 0.
 */
int parseall() {
   int i ;

   for (i=0; i<MAXGADG; i++)
      if (bufarr[i] != NULL) {
         gvals[i] = parse(bufarr[i]) ;
         if (parseerr) {
            sprintf(errorbuf, "I can't parse %s", bufarr[i]) ;
            error(errorbuf) ;
            return(0) ;
         }
      }
   updateregs() ;
   return(1) ;
}
/*
 *   This routine writes a four-digit hexadecimal value to the
 *   screen.
 */
static char *hex = "0123456789abcdef" ;
static char tmp[5] ;
static writefour(x, y, val)
int x, y, val ;
{
   tmp[3] = hex[val & 15] ;
   val >>= 4 ;
   tmp[2] = hex[val & 15] ;
   val >>= 4 ;
   tmp[1] = hex[val & 15] ;
   val >>= 4 ;
   tmp[0] = hex[val & 15] ;
   drawtext(x, y, tmp) ;
}
/*
 *   This routine calculates and writes out *all* of the blitter
 *   register values.
 */
updateregs() {
   int i ;

   blitregs.con0 = ((gvals[GDGASH] & 15) << 12) + (gvals[GDGUSEA] << 11) +
      (gvals[GDGUSEB] << 10) + (gvals[GDGUSEC] << 9) + (gvals[GDGUSED] << 8) +
      (gvals[GDGFUNC] & 255) ;
   blitregs.con1 = ((gvals[GDGBSH] & 15) << 12) + (gvals[GDGSIGN] << 6) +
      (gvals[GDGOVF] << 5) + (gvals[GDGEFE] << 4) +
      (gvals[GDGIFE] << 3) + (gvals[GDGFCI] << 2) + (gvals[GDGDESC] << 1) +
      gvals[GDGLINE] ;
   blitregs.size = ((gvals[GDGV] & 1023) << 6) + (gvals[GDGH] & 63) ;
   blitregs.afwm = (gvals[GDGAFWM] & 65535) ;
   blitregs.alwm = (gvals[GDGALWM] & 65535) ;
   for (i=0; i<4; i++) {
      blitregs.pth[i] = ((gvals[GDGAPT+i] >> 16) & 65535) ;
      blitregs.ptl[i] = (gvals[GDGAPT+i] & 65535) ;
      blitregs.mod[i] = (gvals[GDGAMOD+i] & 65535) ;
   }
   for (i=0; i<3; i++)
      blitregs.dat[i] = (gvals[GDGADAT+i] & 65535) ;
/*
 *   Now we write out the values.
 */
   writefour(HRVC2, VRVL1, blitregs.con0) ;
   writefour(HRVC2, VRVL2, blitregs.con1) ;
   writefour(HRVC2, VRVL3, blitregs.size) ;
   writefour(HRVC2, VRVL4, blitregs.afwm) ;
   writefour(HRVC2, VRVL5, blitregs.alwm) ;
   for (i=0; i<4; i++) {
      writefour(HRVC4, VRVL2 + 9 * i, blitregs.pth[i]) ;
      writefour(HRVC5, VRVL2 + 9 * i, blitregs.ptl[i]) ;
      writefour(HRVC6, VRVL2 + 9 * i, blitregs.mod[i]) ;
   }
   for (i=0; i<3; i++)
      writefour(HRVC6B, VRVL2 + 9 * i, blitregs.dat[i]) ;
}
20488!Funky!Stuff!
echo x - render.c
cat > render.c << '20488!Funky!Stuff!'
/*
 *   This file handles the graphics primitives for BlitLab.
 */
#include "structures.h"
/*
 *   External variables we use.
 */
extern struct RastPort *myrp ;
/*
 *   color sets the current foreground color to the appropriate value.
 */
color(c)
int c ;
{
   SetAPen(myrp, (long)c) ;
   SetDrMd(myrp, (long)JAM1) ;
}
/*
 *   This routine draws a horizontal or vertical line.
 */
line(x1, y1, x2, y2)
int x1, y1, x2, y2 ;
{
   int t ;
   if (x1 > x2) {
      t = x1 ;
      x1 = x2 ;
      x2 = t ;
   }
   if (y1 > y2) {
      t = y1 ;
      y1 = y2 ;
      y2 = t ;
   }
   if (x1 != x2 && y1 != y2)
      error("! can only draw h/v lines currently") ;
   RectFill(myrp, (long)x1, (long)y1, (long)x2, (long)y2) ;
}
/*
 *   This routine draws a box.
 */
box(x1, y1, xsize, ysize)
int x1, y1, xsize, ysize ;
{
   xsize = x1 + xsize - 1 ;
   ysize = y1 + ysize - 1 ;
   line(x1, y1, xsize, y1) ;
   line(xsize, y1, xsize, ysize) ;
   line(xsize, ysize, x1, ysize) ;
   line(x1, ysize, x1, y1) ;
}
/*
 *   This routine draws a filled box.
 */
fbox(x1, y1, xsize, ysize)
int x1, y1, xsize, ysize ;
{
   RectFill(myrp, (long)x1, (long)y1, (long)(x1 + xsize - 1),
      (long)(y1 + ysize - 1)) ;
}
/*
 *   This routine draws a text string at a particular location.  It is
 *   somewhat crude; we build an IntuiText structure, and tell it to
 *   draw it.
 */
static struct IntuiText dmy = {
   WHITE, BLUE,
   JAM2,
   0, 0,
   NULL,
   NULL,
   NULL
} ;
drawtext(x, y, s)
int x, y ;
char *s ;
{
   dmy.IText = (UBYTE *)s ;
   PrintIText(myrp, &dmy, (long)(x), (long)(y)) ;
}
20488!Funky!Stuff!
echo x - structures.h
cat > structures.h << '20488!Funky!Stuff!'
/*
 *   The structures and include files used in BlitLab.
 */
#define BANNER "BlitLab 1.2, Copyright (C) 1987, Radical Eye Software"
#include "exec/exec.h"
#include "intuition/intuition.h"
#include "functions.h"
#include "graphics/display.h"
#include "graphics/gfx.h"
#include "graphics/gfxmacros.h"
#include "graphics/gfxbase.h"
#include "stdio.h"
/*
 *   This is the blitter register structure we use.
 */
struct blitregs {
   short con0, con1, size, afwm, alwm ;
   short pth[4] ;
   short ptl[4] ;
   short mod[4] ;
   short dat[4] ;
} ;
/*
 *   Here we number the gadgets.
 */
#define GDGPNTREG (0)
#define GDGCLRSET (1)
#define GDGGO (2)
#define GDGSX (3)
#define GDGSY (4)
#define GDGEX (5)
#define GDGEY (6)
#define GDGLINE (7)
#define GDGH (8)
#define GDGV (9)
#define GDGDESC (10)
#define GDGFCI (11)
#define GDGIFE (12)
#define GDGEFE (13)
#define GDGSETUP (14)
#define GDGFUNC (15)
#define GDGUSEA (16)
#define GDGUSEB (17)
#define GDGUSEC (18)
#define GDGUSED (19)
#define GDGAPT (20)
#define GDGBPT (21)
#define GDGCPT (22)
#define GDGDPT (23)
#define GDGAMOD (24)
#define GDGBMOD (25)
#define GDGCMOD (26)
#define GDGDMOD (27)
#define GDGADAT (28)
#define GDGBDAT (29)
#define GDGCDAT (30)
#define GDGASH (31)
#define GDGBSH (32)
#define GDGAFWM (33)
#define GDGALWM (34)
#define GDGCALC (35)
#define GDGSIGN (36)
#define GDGOVF (37)
#define MAXGADG (38)
/*
 *   These defines set the size of the screen and various subareas of
 *   the screen, including most gadget locations.
 */
#define HWINSTART (0)
#define VWINSTART (5)
#define HWINSIZE (640)
#define VWINSIZE (195)
#define HBITSTART (4)
#define VBITSTART (11)
#define HBITSIZE (96 * 6)
#define VBITSIZE (32 * 3 + 1)
#define HLMGSTART (HBITSIZE + HBITSTART + 2)
#define HLMGSIZE (HWINSIZE - HLMGSTART - 4)
#define VLMGSIZE (12)
#define VLMGINT (4)
#define VLMG1 (VBITSTART + VLMGINT)
#define VLMG2 (VLMG1 + VLMGSIZE + VLMGINT)
#define VLMG3 (VLMG2 + VLMGSIZE + VLMGINT)
#define VLMG4 (VLMG3 + VLMGSIZE + VLMGINT)
#define VLMG5 (VLMG4 + VLMGSIZE + VLMGINT)
#define VGOSTART (VLMG5 + VLMGSIZE + VLMGINT)
#define HGOSTART (HLMGSTART + 2)
#define HGOSIZE (HLMGSIZE - 4)
#define VGOSIZE (VRVSTART - VGOSTART - VLMGINT)
#define VSTRSIZE (11)
#define HSTRSIZE(a) (8 * (a) + 4)
#define HMGSIZE (63)
#define VMGSIZE (11)
#define HMGINT (1)
#define VMGINT (0)
#define HMG1START (HBITSTART)
#define HMG2START (HMG1START + HMGSIZE + HMGINT)
#define HMG3START (HMG2START + HMGSIZE + HMGINT)
#define HMG4START (HMG3START + HMGSIZE + HMGINT)
#define HMG5START (HMG4START + HMGSIZE + HMGINT)
#define HMG6START (HMG5START + HMGSIZE + HMGINT)
#define HMG7START (HMG6START + HMGSIZE + HMGINT)
#define HMG8START (HMG7START + HMGSIZE + HMGINT)
#define HMG9START (HMG8START + HMGSIZE + HMGINT)
#define VMG1START (VBITSTART + VBITSIZE)
#define VMG2START (VMG1START + VMGSIZE + VMGINT)
#define VRVSTART (VMG2START + VMGSIZE)
#define VRVL1 (VRVSTART + 2)
#define VRVL2 (VRVL1 + 9)
#define VRVL3 (VRVL2 + 9)
#define VRVL4 (VRVL3 + 9)
#define VRVL5 (VRVL4 + 9)
#define VRVL6 (VRVL5 + 9)
#define VRVLL2 (VRVL1 + 10)
#define VRVLL3 (VRVLL2 + 11)
#define VRVLL4 (VRVLL3 + 11)
#define VRVLL5 (VRVLL4 + 11)
#define VRVLL6 (VRVLL5 + 11)
#define VRG1 (VRVL1 + 8)
#define VRVSIZE (VWINSIZE - VRVSTART)
#define HRVSIZE (HWINSIZE - HBITSTART - 3)
#define HRVSTART (HBITSTART)
#define HRVC1 (HRVSTART + 6)
#define HRVC2 (HRVC1 + 5 * 8)
#define HRVC3 (HRVC2 + 5 * 8)
#define HRVC4 (HRVC3 + 2 * 8)
#define HRVC5 (HRVC4 + 5 * 8)
#define HRVC6 (HRVC5 + 5 * 8)
#define HRVC6B (HRVC6 + 5 * 8)
#define HMVSTART (HRVC6B + 35)
#define HRVC7 (HRVC6B + 5 * 8)
#define HRVC8 (HRVC7 + 2 * 8)
#define HRVC9 (HRVC8 + 3 * 8 + 4)
#define HRVC10 (HRVC9 + 9 * 8)
#define HRVC11 (HRVC10 + 7 * 8)
#define HRVC12 (HRVC11 + 19 * 8)
#define VTEXTOFF (2)
#define HTEXTOFF (2)
/*
 *   Colors.
 */
#define BLUE (0)
#define WHITE (1)
#define BLACK (2)
#define ORANGE (3)

20488!Funky!Stuff!
---end---