[net.sources] Amiga IFF examples

farren@well.UUCP (Mike Farren) (01/11/86)

    This is the first of two postings containing the Electronic Arts
    public domain examples for the IFF file interchange format.  This
    posting contains examples of reading and writing IFF files.
      The document describing the IFF formats will be included in the
    version 1.1 Rom Kernal Manual, which should be available soon.

      Briefly, an IFF file consists of one or more "chunks", each of
    which begins with a header consisting of a four-byte type identifier
    (such as "FORM", "ILBM", "PROP", etc.) followed by a long integer
    containing the size of the chunk. The data that make up the chunk
    follows as a string of bytes.  Each chunk is padded to end on an
    even word boundary (a la 68000).  Each chunk may contain other
    chunks.  The contents of each chunk is determined by its type, as
    specified in the identifier.

******************************* CUT HERE! *******************************
#!/bin/sh
#
#    This is a shell archive.  To unpack the files, delete everything
#    before "#!/bin/sh", and then pipe the body through the "sh" command.
#    This archive contains the following files:
#
#    iff.h - The header file for the IFF programs
#    iffr.c - The IFF read routines
#    iffw.c - The IFF write routines
#    iffcheck.c - A program demonstrating the use of the above routines.
#    iffcheck.lnk - The Alink "WITH" file for iffcheck
#
echo 'Start of IFF files'
echo 'x - iff.h'
sed 's/^X//' > iff.h << '/'
X#ifndef IFF_H
X#define IFF_H
X/*----------------------------------------------------------------------*/
X/* IFF.H  defs for IFF-85 Interchange Format Files.            11/15/85 */
X/*                                                                      */
X/* By Jerry Morrison and Steve Shaw, Electronic Arts.                   */
X/* This software is in the public domain.                               */
X/*----------------------------------------------------------------------*/
X
X#ifndef EXEC_TYPES_H
X#include "exec/types.h"
X#endif
X
X#ifndef LIBRARIES_DOS_H
X#include "libraries/dos.h"
X#endif
X
Xtypedef LONG IFFP;      /* Status code result from an IFF procedure */
X        /* LONG, because must be type compatable with ID for GetChunkHdr.*/
X        /* Note that the error codes below are not legal IDs.*/
X#define IFF_OKAY  0     /* Keep going...*/
X#define END_MARK  -1    /* As if there was a chunk at end of group.*/
X#define IFF_DONE  -2    /* clientProc returns this when it has READ enough.
X                         * It means return thru all levels. File is Okay.*/
X#define DOS_ERROR -3
X#define NOT_IFF   -4    /* not an IFF file.*/
X#define NO_FILE   -5    /* Tried to open file, DOS didn't find it.*/
X#define CLIENT_ERROR -6 /* Client made invalid request, for instance, write a
X                         * negative size chunk.*/
X#define BAD_FORM  -7    /* A client read proc complains about FORM semantics;
X                         * e.g. valid IFF, but missing a required chunk.*/
X#define SHORT_CHUNK -8  /* Client asked to IFFReadBytes more bytes than left
X                         * in the chunk. Could be client bug or bad form.*/
X#define BAD_IFF   -9    /* mal-formed IFF file. [TBD] Expand this into a
X                         * range of error codes.*/
X#define LAST_ERROR BAD_IFF
X
X/* This MACRO is used to RETURN immediately when a termination condition is
X * found. This is a pretty weird macro. It requires the caller to declare a
X * local "IFFP iffp" and assign it. This wouldn't work as a subroutine since
X * it returns for it's caller. */
X#define CheckIFFP()   { if (iffp != IFF_OKAY) return(iffp); }
X
X
X/* ---------- ID -------------------------------------------------------*/
X
Xtypedef LONG ID;        /* An ID is four printable ASCII chars but
X                         * stored as a LONG for efficient copy & compare.*/
X
X/* Four-character IDentifier builder.*/
X#define MakeID(a,b,c,d)  ( (a)<<24 | (b)<<16 | (c)<<8 | (d) )
X
X/* Standard group IDs.  A chunk with one of these IDs contains a
X   SubTypeID followed by zero or more chunks.*/
X#define FORM MakeID('F','O','R','M')
X#define PROP MakeID('P','R','O','P')
X#define LIST MakeID('L','I','S','T')
X#define CAT  MakeID('C','A','T',' ')
X#define FILLER MakeID(' ',' ',' ',' ')
X/* The IDs "FOR1".."FOR9", "LIS1".."LIS9", & "CAT1".."CAT9" are reserved
X * for future standardization.*/
X
X/* Pseudo-ID used internally by chunk reader and writer.*/
X#define NULL_CHUNK 0L          /* No current chunk.*/
X
X
X/* ---------- Chunk ----------------------------------------------------*/
X
X/* All chunks start with a type ID and a count of the data bytes that
X   follow--the chunk's "logical size" or "data size". If that number is odd,
X   a 0 pad byte is written, too. */
Xtypedef struct {
X    ID    ckID;
X    LONG  ckSize;
X    } ChunkHeader;
X
Xtypedef struct {
X    ID    ckID;
X    LONG  ckSize;
X    UBYTE ckData[ 1 /*REALLY: ckSize*/ ];
X    } Chunk;
X
X/* Pass ckSize = szNotYetKnown to the writer to mean "compute the size".*/
X#define szNotYetKnown 0x80000001L
X
X/* Need to know whether a value is odd so can word-align.*/
X#define IS_ODD(a)   ((a) & 1)
X
X/* This macro rounds up to an even number. */
X#define WordAlign(size)   ((size+1)&~1)
X
X/* ALL CHUNKS MUST BE PADDED TO EVEN NUMBER OF BYTES.
X * ChunkPSize computes the total "physical size" of a padded chunk from
X * its "data size" or "logical size". */
X#define ChunkPSize(dataSize)  (WordAlign(dataSize) + sizeof(ChunkHeader))
X
X/* The Grouping chunks (LIST, FORM, PROP, & CAT) contain concatenations of
X * chunks after a subtype ID that identifies the content chunks.
X * "FORM type XXXX", "LIST of FORM type XXXX", "PROPerties associated
X * with FORM type XXXX", or "conCATenation of XXXX".*/
Xtypedef struct {
X    ID    ckID;
X    LONG  ckSize;       /* this ckSize includes "grpSubID".*/
X    ID    grpSubID;
X    } GroupHeader;
X
Xtypedef struct {
X    ID    ckID;
X    LONG  ckSize;
X    ID    grpSubID;
X    UBYTE grpData[ 1 /*REALLY: ckSize-sizeof(grpSubID)*/ ];
X    } GroupChunk;
X
X
X/* ---------- IFF Reader -----------------------------------------------*/
X
X/******** Routines to support a stream-oriented IFF file reader *******
X *
X * These routines handle lots of details like error checking and skipping
X * over padding. They're also careful not to read past any containing context.
X *
X * These routines ASSUME they're the only ones reading from the file.
X * Client should check IFFP error codes. Don't press on after an error!
X * These routines try to have no side effects in the error case, except
X * partial I/O is sometimes unavoidable.
X *
X * All of these routines may return DOS_ERROR. In that case, ask DOS for the
X * specific error code.
X *
X * The overall scheme for the low level chunk reader is to open a "group read
X * context" with OpenRIFF or OpenRGroup, read the chunks with GetChunkHdr
X * (and its kin) and IFFReadBytes, and close the context with CloseRGroup.
X *
X * The overall scheme for reading an IFF file is to use ReadIFF, ReadIList,
X * and ReadICat to scan the file. See those procedures, ClientProc (below),
X * and the skeleton IFF reader. */
X
X/* Client passes ptrs to procedures of this type to ReadIFF which call them
X * back to handle LISTs, FORMs, CATs, and PROPs.
X *
X * Use the GroupContext ptr when calling reader routines like GetChunkHdr.
X * Look inside the GroupContext ptr for your ClientFrame ptr. You'll
X * want to type cast it into a ptr to your containing struct to get your
X * private contextual data (stacked property settings). See below. */
Xtypedef IFFP ClientProc(struct _GroupContext *);
X
X/* Client's context for reading an IFF file or a group.
X * Client should actually make this the first component of a larger struct
X * (it's personal stack "frame") that has a field to store each "interesting"
X * property encountered.
X * Either initialize each such field to a global default or keep a boolean
X * indicating if you've read a property chunk into that field.
X * Your getList and getForm procs should allocate a new "frame" and copy the
X * parent frame's contents. The getProp procedure should store into the frame
X * allocated by getList for the containing LIST. */
Xtypedef struct _ClientFrame {
X    ClientProc *getList, *getProp, *getForm, *getCat;
X    /* client's own data follows; place to stack property settings */
X    } ClientFrame;
X
X/* Our context for reading a group chunk. */
Xtypedef struct _GroupContext {
X    struct _GroupContext *parent; /* Containing group; NULL => whole file. */
X    ClientFrame *clientFrame;     /* Reader data & client's context state. */
X    BPTR file;          /* Byte-stream file handle. */
X    LONG position;      /* The context's logical file position. */
X    LONG bound;         /* File-absolute context bound
X                         * or szNotYetKnown (writer only). */
X    ChunkHeader ckHdr;  /* Current chunk header. ckHdr.ckSize = szNotYetKnown
X                         * means we need to go back and set the size (writer only).
X                         * See also Pseudo-IDs, above. */
X    ID subtype;         /* Group's subtype ID when reading. */
X    LONG bytesSoFar;    /* # bytes read/written of current chunk's data. */
X    } GroupContext;
X
X/* Computes the number of bytes not yet read from the current chunk, given
X * a group read context gc. */
X#define ChunkMoreBytes(gc)  ((gc)->ckHdr.ckSize - (gc)->bytesSoFar)
X
X
X/***** Low Level IFF Chunk Reader *****/
X
X/* Given an open file, open a read context spanning the whole file.
X * This is normally only called by ReadIFF.
X * This sets new->clientFrame = clientFrame.
X * ASSUME context allocated by caller but not initialized.
X * ASSUME caller doesn't deallocate the context before calling CloseRGroup.
X * NOT_IFF ERROR if the file is too short for even a chunk header.*/
Xextern IFFP OpenRIFF(BPTR, GroupContext *, ClientFrame *);
X                 /*  file, new,            clientFrame  */
X
X/* Open the remainder of the current chunk as a group read context.
X * This will be called just after the group's subtype ID has been read
X * (automatically by GetChunkHdr for LIST, FORM, PROP, and CAT) so the
X * remainder is a sequence of chunks.
X * This sets new->clientFrame = parent->clientFrame. The caller should repoint
X * it at a new clientFrame if opening a LIST context so it'll have a "stack
X * frame" to store PROPs for the LIST. (It's usually convenient to also
X * allocate a new Frame when you encounter FORM of the right type.)
X *
X * ASSUME new context allocated by caller but not initialized.
X * ASSUME caller doesn't deallocate the context or access the parent context
X * before calling CloseRGroup.
X * BAD_IFF ERROR if context end is odd or extends past parent. */
Xextern IFFP OpenRGroup(GroupContext *, GroupContext *);
X                   /*  parent,         new  */
X
X/* Close a group read context, updating its parent context.
X * After calling this, the old context may be deallocated and the parent
X * context can be accessed again. It's okay to call this particular procedure
X * after an error has occurred reading the group.
X * This always returns IFF_OKAY. */
Xextern IFFP CloseRGroup(GroupContext *);
X                    /*  old  */
X
X/* Skip any remaining bytes of the previous chunk and any padding, then
X * read the next chunk header into context.ckHdr.
X * If the ckID is LIST, FORM, CAT, or PROP, this automatically reads the
X * subtype ID into context->subtype.
X * Caller should dispatch on ckID (and subtype) to an appropriate handler.
X *
X * RETURNS context.ckHdr.ckID (the ID of the new chunk header); END_MARK
X * if there are no more chunks in this context; or NOT_IFF if the top level
X * file chunk isn't a FORM, LIST, or CAT; or BAD_IFF if malformed chunk, e.g.
X * ckSize is negative or too big for containing context, ckID isn't positive,
X * or we hit end-of-file.
X *
X * See also GetFChunkHdr, GetF1ChunkHdr, and GetPChunkHdr, below.*/
Xextern ID       GetChunkHdr(GroupContext *);
X  /*  context.ckHdr.ckID    context  */
X
X/* Read nBytes number of data bytes of current chunk. (Use OpenGroup, etc.
X * instead to read the contents of a group chunk.) You can call this several
X * times to read the data piecemeal.
X * CLIENT_ERROR if nBytes < 0. SHORT_CHUNK if nBytes > ChunkMoreBytes(context)
X * which could be due to a client bug or a chunk that's shorter than it
X * ought to be (bad form). (on either CLIENT_ERROR or SHORT_CHUNK,
X * IFFReadBytes won't read any bytes.) */
Xextern IFFP IFFReadBytes(GroupContext *, BYTE *, LONG);
X                     /*  context,        buffer, nBytes  */
X
X
X/***** IFF File Reader *****/
X
X/* This is a noop ClientProc that you can use for a getList, getForm, getProp,
X * or getCat procedure that just skips the group. A simple reader might just
X * implement getForm, store &ReadICat in the getCat field of clientFrame, and
X * use &SkipGroup for the getList and getProp procs.*/
Xextern IFFP SkipGroup(GroupContext *);
X
X/* IFF file reader.
X * Given an open file, allocate a group context and use it to read the FORM,
X * LIST, or CAT and it's contents. The idea is to parse the file's contents,
X * and for each FORM, LIST, CAT, or PROP encountered, call the getForm,
X * getList, getCat, or getProp procedure in clientFrame, passing the
X * GroupContext ptr.
X * This is achieved with the aid of ReadIList (which your getList should
X * call) and ReadICat (which your getCat should call, if you don't just use
X * &ReadICat for your getCat). If you want to handle FORMs, LISTs, and CATs
X * nested within FORMs, the getForm procedure must dispatch to getForm,
X * getList, and getCat (it can use GetF1ChunkHdr to make this easy).
X *
X * Normal return is IFF_OKAY (if whole file scanned) or IFF_DONE (if a client
X * proc said "done" first).
X * See the skeletal getList, getForm, getCat, and getProp procedures. */
Xextern IFFP ReadIFF(BPTR, ClientFrame *);
X                /*  file, clientFrame  */
X
X/* IFF LIST reader.
X * Your "getList" procedure should allocate a ClientFrame, copy the parent's
X * ClientFrame, and then call this procedure to do all the work.
X *
X * Normal return is IFF_OKAY (if whole LIST scanned) or IFF_DONE (if a client
X * proc said "done" first).
X * BAD_IFF ERROR if a PROP appears after a non-PROP. */
Xextern IFFP ReadIList(GroupContext *, ClientFrame *);
X                  /*  parent,         clientFrame  */
X
X/* IFF CAT reader.
X * Most clients can simply use this to read their CATs. If you must do extra
X * setup work, put a ptr to your getCat procedure in the clientFrame, and
X * have that procedure call ReadICat to do the detail work.
X *
X * Normal return is IFF_OKAY (if whole CAT scanned) or IFF_DONE (if a client
X * proc said "done" first).
X * BAD_IFF ERROR if a PROP appears in the CAT. */
Xextern IFFP ReadICat(GroupContext *);
X                 /*  parent  */
X
X/* Call GetFChunkHdr instead of GetChunkHdr to read each chunk inside a FORM.
X * It just calls GetChunkHdr and returns BAD_IFF if it gets a PROP chunk. */
Xextern ID       GetFChunkHdr(GroupContext *);
X  /*  context.ckHdr.ckID    context  */
X
X/* GetF1ChunkHdr is like GetFChunkHdr, but it automatically dispatches to the
X * getForm, getList, and getCat procedure (and returns the result) if it
X * encounters a FORM, LIST, or CAT. */
Xextern ID       GetF1ChunkHdr(GroupContext *);
X  /*  context.ckHdr.ckID    context  */
X
X/* Call GetPChunkHdr instead of GetChunkHdr to read each chunk inside a PROP.
X * It just calls GetChunkHdr and returns BAD_IFF if it gets a group chunk. */
Xextern ID       GetPChunkHdr(GroupContext *);
X  /*  context.ckHdr.ckID    context  */
X
X
X/* ---------- IFF Writer -----------------------------------------------*/
X
X/******* Routines to support a stream-oriented IFF file writer *******
X *
X * These routines will random access back to set a chunk size value when the
X * caller doesn't know it ahead of time. They'll also do things automatically
X * like padding and error checking.
X *
X * These routines ASSUME they're the only ones writing to the file.
X * Client should check IFFP error codes. Don't press on after an error!
X * These routines try to have no side effects in the error case, except that
X * partial I/O is sometimes unavoidable.
X *
X * All of these routines may return DOS_ERROR. In that case, ask DOS for the
X * specific error code.
X *
X * The overall scheme is to open an output GroupContext via OpenWIFF or
X * OpenWGroup, call either PutCk or {PutCkHdr {IFFWriteBytes}* PutCkEnd} for
X * each chunk, then use CloseWGroup to close the GroupContext.
X *
X * To write a group (LIST, FORM, PROP, or CAT), call StartWGroup, write out
X * its chunks, then call EndWGroup. StartWGroup automatically writes the
X * group header and opens a nested context for writing the contents.
X * EndWGroup closes the nested context and completes the group chunk. */
X
X
X/* Given a file open for output, open a write context.
X * The "limit" arg imposes a fence or upper limit on the logical file
X * position for writing data in this context. Pass in szNotYetKnown to be
X * bounded only by disk capacity.
X * ASSUME new context structure allocated by caller but not initialized.
X * ASSUME caller doesn't deallocate the context before calling CloseWGroup.
X * The caller is only allowed to write out one FORM, LIST, or CAT in this top
X * level context (see StartWGroup and PutCkHdr).
X * CLIENT_ERROR if limit is odd.*/
Xextern IFFP OpenWIFF(BPTR, GroupContext *, LONG);
X                 /*  file, new,            limit {file position}  */
X
X/* Start writing a group (presumably LIST, FORM, PROP, or CAT), opening a
X * nested context. The groupSize includes all nested chunks + the subtype ID.
X *
X * The subtype of a LIST or CAT is a hint at the contents' FORM type(s). Pass
X * in FILLER if it's a mixture of different kinds.
X *
X * This writes the chunk header via PutCkHdr, writes the subtype ID via
X * IFFWriteBytes, and calls OpenWGroup. The caller may then write the nested
X * chunks and finish by calling EndWGroup.
X * The OpenWGroup call sets new->clientFrame = parent->clientFrame.
X *
X * ASSUME new context structure allocated by caller but not initialized.
X * ASSUME caller doesn't deallocate the context or access the parent context
X * before calling CloseWGroup.
X * ERROR conditions: See PutCkHdr, IFFWriteBytes, OpenWGroup. */
Xextern IFFP StartWGroup(GroupContext *, ID, LONG, ID, GroupContext *);
X                    /*  parent, groupType, groupSize, subtype, new  */
X
X/* End a group started by StartWGroup.
X * This just calls CloseWGroup and PutCkEnd.
X * ERROR conditions: See CloseWGroup and PutCkEnd. */
Xextern IFFP EndWGroup(GroupContext *);
X                    /*  old  */
X
X/* Open the remainder of the current chunk as a group write context.
X * This is normally only called by StartWGroup.
X *
X * Any fixed limit to this group chunk or a containing context will impose
X * a limit on the new context.
X * This will be called just after the group's subtype ID has been written
X * so the remaining contents will be a sequence of chunks.
X * This sets new->clientFrame = parent->clientFrame.
X * ASSUME new context structure allocated by caller but not initialized.
X * ASSUME caller doesn't deallocate the context or access the parent context
X * before calling CloseWGroup.
X * CLIENT_ERROR if context end is odd or PutCkHdr wasn't called first. */
Xextern IFFP OpenWGroup(GroupContext *, GroupContext *);
X                   /*  parent,         new  */
X
X/* Close a write context and update its parent context.
X * This is normally only called by EndWGroup.
X *
X * If this is a top level context (created by OpenWIFF) we'll set the file's
X * EOF (end of file) but won't close the file.
X * After calling this, the old context may be deallocated and the parent
X * context can be accessed again.
X *
X * Amiga DOS Note: There's no call to set the EOF. We just position to the
X * desired end and return. Caller must Close file at that position.
X * CLIENT_ERROR if PutCkEnd wasn't called first. */
Xextern IFFP CloseWGroup(GroupContext *);
X                    /*  old  */
X
X/* Write a whole chunk to a GroupContext. This writes a chunk header, ckSize
X * data bytes, and (if needed) a pad byte. It also updates the GroupContext.
X * CLIENT_ERROR if ckSize == szNotYetKnown. See also PutCkHdr errors. */
Xextern IFFP PutCk(GroupContext *, ID,   LONG,   BYTE *);
X              /*  context,        ckID, ckSize, *data  */
X
X/* Write just a chunk header. Follow this will any number of calls to
X * IFFWriteBytes and finish with PutCkEnd.
X * If you don't yet know how big the chunk is, pass in ckSize = szNotYetKnown,
X * then PutCkEnd will set the ckSize for you later.
X * Otherwise, IFFWriteBytes and PutCkEnd will ensure that the specified
X * number of bytes get written.
X * CLIENT_ERROR if the chunk would overflow the GroupContext's bound, if
X * PutCkHdr was previously called without a matching PutCkEnd, if ckSize < 0
X * (except szNotYetKnown), if you're trying to write something other
X * than one FORM, LIST, or CAT in a top level (file level) context, or
X * if ckID <= 0 (these illegal ID values are used for error codes). */
Xextern IFFP PutCkHdr(GroupContext *, ID,   LONG);
X                 /*  context,        ckID, ckSize  */
X
X/* Write nBytes number of data bytes for the current chunk and update
X * GroupContext.
X * CLIENT_ERROR if this would overflow the GroupContext's limit or the
X * current chunk's ckSize, or if PutCkHdr wasn't called first, or if
X * nBytes < 0. */
Xextern IFFP IFFWriteBytes(GroupContext *, BYTE *, LONG);
X                      /*  context,        *data,  nBytes  */
X
X/* Complete the current chunk, write a pad byte if needed, and update
X * GroupContext.
X * If current chunk's ckSize = szNotYetKnown, this goes back and sets the
X * ckSize in the file.
X * CLIENT_ERROR if PutCkHdr wasn't called first, or if client hasn't
X * written 'ckSize' number of bytes with IFFWriteBytes. */
Xextern IFFP PutCkEnd(GroupContext *);
X                 /*  context  */
X
X#endif
/
echo 'x - iffr.c'
sed 's/^X//' > iffr.c << '/'
X/*----------------------------------------------------------------------*
X * IFFR.C  Support routines for reading IFF-85 files.          11/15/85
X * (IFF is Interchange Format File.)
X *
X * By Jerry Morrison and Steve Shaw, Electronic Arts.
X * This software is in the public domain.
X *
X * This version for the Commodore-Amiga computer.
X *----------------------------------------------------------------------*/
X#include "iff/iff.h"
X
X/* ---------- Read -----------------------------------------------------*/
X
X/* ---------- OpenRIFF --------------------------------------------------*/
XIFFP OpenRIFF(file0, new0, clientFrame)
X        BPTR file0;   GroupContext *new0;  ClientFrame *clientFrame; {
X    register BPTR file = file0;
X    register GroupContext *new = new0;
X    IFFP iffp = IFF_OKAY;
X
X    new->parent       = NULL;           /* "whole file" has no parent.*/
X    new->clientFrame  = clientFrame;
X    new->file         = file;
X    new->position     = 0;
X    new->ckHdr.ckID   = new->subtype    = NULL_CHUNK;
X    new->ckHdr.ckSize = new->bytesSoFar = 0;
X
X    /* Set new->bound. AmigaDOS specific code.*/
X    if (file <= 0)   return(NO_FILE);
X    Seek(file, 0, OFFSET_END);                  /* Seek to end of file.*/
X    new->bound = Seek(file, 0, OFFSET_CURRENT); /* Pos'n == #bytes in file.*/
X    if (new->bound < 0)   return(DOS_ERROR);    /* DOS being absurd.*/
X    Seek(file, 0, OFFSET_BEGINNING);            /* Go to file start.*/
X    /* Would just do this if Amiga DOS maintained fh_End: */
X    /* new->bound = (FileHandle *)BADDR(file)->fh_End; */
X
X    if ( new->bound < sizeof(ChunkHeader) )
X        iffp = NOT_IFF;
X    return(iffp);
X    }
X
X/* ---------- OpenRGroup -----------------------------------------------*/
XIFFP OpenRGroup(parent0, new0)   GroupContext *parent0, *new0; {
X    register GroupContext *parent = parent0;
X    register GroupContext *new    = new0;
X    IFFP iffp = IFF_OKAY;
X
X    new->parent       = parent;
X    new->clientFrame  = parent->clientFrame;
X    new->file         = parent->file;
X    new->position     = parent->position;
X    new->bound        = parent->position + ChunkMoreBytes(parent);
X    new->ckHdr.ckID   = new->subtype    = NULL_CHUNK;
X    new->ckHdr.ckSize = new->bytesSoFar = 0;
X
X    if ( new->bound > parent->bound  ||  IS_ODD(new->bound) )
X        iffp = BAD_IFF;
X    return(iffp);
X    }
X
X/* ---------- CloseRGroup -----------------------------------------------*/
XIFFP CloseRGroup(context)   GroupContext *context; {
X    register LONG position;
X
X    if (context->parent == NULL) {
X        }  /* Context for whole file.*/
X    else {
X        position = context->position;
X        context->parent->bytesSoFar += position - context->parent->position;
X        context->parent->position = position;
X        }
X    return(IFF_OKAY);
X    }
X
X/* ---------- SkipFwd --------------------------------------------------*/
X/* Skip over bytes in a context. Won't go backwards.*/
X/* Updates context->position but not context->bytesSoFar.*/
X/* This implementation is AmigaDOS specific.*/
XIFFP SkipFwd(context, bytes)   GroupContext *context;  LONG bytes; {
X    IFFP iffp = IFF_OKAY;
X
X    if (bytes > 0) {
X        if (-1 == Seek(context->file, bytes, OFFSET_CURRENT))
X            iffp = BAD_IFF;     /* Ran out of bytes before chunk complete.*/
X        else
X            context->position += bytes;
X        }
X    return(iffp);
X    }
X
X/* ---------- GetChunkHdr ----------------------------------------------*/
XID GetChunkHdr(context0)   GroupContext *context0;  {
X    register GroupContext *context = context0;
X    register IFFP iffp;
X    LONG remaining;
X
X    /* Skip remainder of previous chunk & padding. */
X    iffp = SkipFwd(context,
X        ChunkMoreBytes(context) + IS_ODD(context->ckHdr.ckSize));
X    CheckIFFP();
X
X    /* Set up to read the new header. */
X    context->ckHdr.ckID = BAD_IFF;      /* Until we know it's okay, mark it BAD.*/
X    context->subtype    = NULL_CHUNK;
X    context->bytesSoFar = 0;
X
X    /* Generate a psuedo-chunk if at end-of-context. */
X    remaining = context->bound - context->position;
X    if (remaining == 0) {
X        context->ckHdr.ckSize = 0;
X        context->ckHdr.ckID   = END_MARK;
X        }
X
X    /* BAD_IFF if not enough bytes in the context for a ChunkHeader.*/
X    else if (sizeof(ChunkHeader) > remaining) {
X        context->ckHdr.ckSize = remaining;
X        }
X
X    /* Read the chunk header (finally). */
X    else {
X        switch (Read(context->file, &context->ckHdr, sizeof(ChunkHeader))) {
X            case -1: return(context->ckHdr.ckID = DOS_ERROR);
X            case 0:  return(context->ckHdr.ckID = BAD_IFF);
X            }
X
X        /* Check: Top level chunk must be LIST or FORM or CAT. */
X        if (context->parent == NULL)
X            switch(context->ckHdr.ckID) {
X                case FORM:  case LIST:  case CAT:  break;
X                default:    return(context->ckHdr.ckID = NOT_IFF);
X                }
X
X        /* Update the context. */
X        context->position += sizeof(ChunkHeader);
X        remaining         -= sizeof(ChunkHeader);
X
X        /* Non-positive ID values are illegal and used for error codes.*/
X        /* We could check for other illegal IDs...*/
X        if (context->ckHdr.ckID <= 0)
X             context->ckHdr.ckID = BAD_IFF;
X
X        /* Check: ckSize negative or larger than # bytes left in context? */
X        else if (context->ckHdr.ckSize < 0  ||
X                 context->ckHdr.ckSize > remaining) {
X            context->ckHdr.ckSize = remaining;
X            context->ckHdr.ckID   = BAD_IFF;
X            }
X
X        /* Automatically read the LIST, FORM, PROP, or CAT subtype ID */
X        else switch (context->ckHdr.ckID) {
X            case LIST:  case FORM:  case PROP:  case CAT:  {
X                iffp = IFFReadBytes(context,
X                                    (BYTE *)&context->subtype,
X                                    sizeof(ID));
X                if (iffp != IFF_OKAY)
X                    context->ckHdr.ckID = iffp;
X                break; }
X            }
X
X        }
X    return(context->ckHdr.ckID);
X    }
X
X/* ---------- IFFReadBytes ---------------------------------------------*/
XIFFP IFFReadBytes(context, buffer, nBytes)
X    GroupContext *context;   BYTE *buffer;   LONG nBytes; {
X    register IFFP iffp = IFF_OKAY;
X
X    if (nBytes < 0)
X        iffp = CLIENT_ERROR;
X    else if (nBytes > ChunkMoreBytes(context))
X        iffp = SHORT_CHUNK;
X    else if (nBytes > 0)
X        switch ( Read(context->file, buffer, nBytes) ) {
X            case -1: {iffp = DOS_ERROR; break; }
X            case 0:  {iffp = BAD_IFF;   break; }
X            default: {
X                context->position   += nBytes;
X                context->bytesSoFar += nBytes;
X                }
X            }
X
X    return(iffp);
X    }
X
X/* ---------- SkipGroup ------------------------------------------------*/
XIFFP SkipGroup(context)  GroupContext *context;  {
X    }   /* Nothing to do, thanks to GetChunkHdr */
X
X/* ---------- ReadIFF --------------------------------------------------*/
XIFFP ReadIFF(file, clientFrame)  BPTR file;  ClientFrame *clientFrame;  {
X    /*CompilerBug register*/ IFFP iffp;
X    GroupContext context;
X
X    iffp = OpenRIFF(file, &context);
X    context.clientFrame = clientFrame;
X
X    if (iffp == IFF_OKAY)
X        switch (iffp = GetChunkHdr(&context)) {
X            case FORM: { iffp = (*clientFrame->getForm)(&context); break; }
X            case LIST: { iffp = (*clientFrame->getList)(&context); break; }
X            case CAT : { iffp = (*clientFrame->getCat )(&context); break; }
X            /* default: Includes IFF_DONE, BAD_IFF, NOT_IFF... */
X            }
X
X    CloseRGroup(&context);
X
X    if (iffp > 0)               /* Make sure we don't return an ID.*/
X        iffp = NOT_IFF;         /* GetChunkHdr should've caught this.*/
X    return(iffp);
X    }
X
X/* ---------- ReadIList ------------------------------------------------*/
XIFFP ReadIList(parent, clientFrame)
X    GroupContext *parent;  ClientFrame *clientFrame; {
X    GroupContext listContext;
X    IFFP iffp;
X    BOOL propOk = TRUE;
X
X    iffp = OpenRGroup(parent, &listContext);
X    CheckIFFP();
X
X    /* One special case test lets us handle CATs as well as LISTs.*/
X    if (parent->ckHdr.ckID == CAT)
X        propOk = FALSE;
X    else
X        listContext.clientFrame = clientFrame;
X
X    do {
X        switch (iffp = GetChunkHdr(&listContext)) {
X            case PROP: {
X                if (propOk)
X                    iffp = (*clientFrame->getProp)(&listContext);
X                else
X                    iffp = BAD_IFF;
X                break;
X                }
X            case FORM: { iffp = (*clientFrame->getForm)(&listContext); break; }
X            case LIST: { iffp = (*clientFrame->getList)(&listContext); break; }
X            case CAT : { iffp = (*clientFrame->getCat )(&listContext); break; }
X            /* default: Includes END_MARK, IFF_DONE, BAD_IFF, NOT_IFF... */
X            }
X        if (listContext.ckHdr.ckID != PROP)
X            propOk = FALSE;     /* No PROPs allowed after this point.*/
X        } while (iffp == IFF_OKAY);
X
X    CloseRGroup(&listContext);
X
X    if (iffp > 0)       /* Only chunk types above are allowed in a LIST/CAT.*/
X        iffp = BAD_IFF;
X    return(iffp == END_MARK ? IFF_OKAY : iffp);
X    }
X
X/* ---------- ReadICat -------------------------------------------------*/
X/* By special arrangement with the ReadIList implement'n, this is trivial.*/
XIFFP ReadICat(parent)  GroupContext *parent;  {
X    return( ReadIList(parent, NULL) );
X    }
X
X/* ---------- GetFChunkHdr ---------------------------------------------*/
XID GetFChunkHdr(context)   GroupContext *context; {
X    register ID id;
X
X    id = GetChunkHdr(context);
X    if (id == PROP)
X        context->ckHdr.ckID = id = BAD_IFF;
X    return(id);
X    }
X
X/* ---------- GetF1ChunkHdr ---------------------------------------------*/
XID GetF1ChunkHdr(context)   GroupContext *context; {
X    register ID id;
X    register ClientFrame *clientFrame = context->clientFrame;
X
X    switch (id = GetChunkHdr(context))  {
X        case PROP: { id = BAD_IFF; break; }
X        case FORM: { id = (*clientFrame->getForm)(context); break; }
X        case LIST: { id = (*clientFrame->getList)(context); break; }
X        case CAT : { id = (*clientFrame->getCat )(context); break; }
X        /* Default: let the caller handle other chunks */
X        }
X    return(context->ckHdr.ckID = id);
X    }
X
X/* ---------- GetPChunkHdr ---------------------------------------------*/
XID GetPChunkHdr(context)   GroupContext *context; {
X    register ID id;
X
X    id = GetChunkHdr(context);
X    switch (id) {
X        case LIST:  case FORM:  case PROP:  case CAT:  {
X            id = context->ckHdr.ckID = BAD_IFF;
X            break; }
X        }
X    return(id);
X    }
X
/
echo 'x - iffw.c'
sed 's/^X//' > iffw.c << '/'
X/*----------------------------------------------------------------------*
X * IFFW.C  Support routines for writing IFF-85 files.          11/15/85
X * (IFF is Interchange Format File.)
X *
X * By Jerry Morrison and Steve Shaw, Electronic Arts.
X * This software is in the public domain.
X *
X * This version for the Commodore-Amiga computer.
X *----------------------------------------------------------------------*/
X#include "iff/iff.h"
X
X/* ---------- IFF Writer -----------------------------------------------*/
X
X/* A macro to test if a chunk size is definite, i.e. not szNotYetKnown.*/
X#define Known(size)   ( (size) != szNotYetKnown )
X
X/* Yet another weird macro to make the source code simpler...*/
X#define IfIffp(expr)  {if (iffp == IFF_OKAY)  iffp = (expr);}
X
X
X/* ---------- OpenWIFF -------------------------------------------------*/
XIFFP OpenWIFF(file, new0, limit)  BPTR file; GroupContext *new0; LONG limit; {
X    register GroupContext *new = new0;
X    register IFFP iffp = IFF_OKAY;
X
X    new->parent       = NULL;
X    new->clientFrame  = NULL;
X    new->file         = file;
X    new->position     = 0;
X    new->bound        = limit;
X    new->ckHdr.ckID   = NULL_CHUNK;  /* indicates no current chunk */
X    new->ckHdr.ckSize = new->bytesSoFar = 0;
X
X    if (0 > Seek(file, 0, OFFSET_BEGINNING))    /* Go to start of the file.*/
X        iffp = DOS_ERROR;
X    else if ( Known(limit) && IS_ODD(limit) )
X        iffp = CLIENT_ERROR;
X    return(iffp);
X    }
X
X/* ---------- StartWGroup ----------------------------------------------*/
XIFFP StartWGroup(parent, groupType, groupSize, subtype, new)
X      GroupContext *parent, *new; ID groupType, subtype; LONG groupSize;  {
X    register IFFP iffp;
X
X    iffp = PutCkHdr(parent, groupType, groupSize);
X    IfIffp( IFFWriteBytes(parent, (BYTE *)&subtype, sizeof(ID)) );
X    IfIffp( OpenWGroup(parent, new) );
X    return(iffp);
X    }
X
X/* ---------- OpenWGroup -----------------------------------------------*/
XIFFP OpenWGroup(parent0, new0)  GroupContext *parent0, *new0; {
X    register GroupContext *parent = parent0;
X    register GroupContext *new    = new0;
X    register LONG ckEnd;
X    register IFFP iffp = IFF_OKAY;
X
X    new->parent       = parent;
X    new->clientFrame  = parent->clientFrame;
X    new->file         = parent->file;
X    new->position     = parent->position;
X    new->bound        = parent->bound;
X    new->ckHdr.ckID   = NULL_CHUNK;
X    new->ckHdr.ckSize = new->bytesSoFar = 0;
X
X    if ( Known(parent->ckHdr.ckSize) ) {
X        ckEnd = new->position + ChunkMoreBytes(parent);
X        if ( new->bound == szNotYetKnown || new->bound > ckEnd )
X            new->bound = ckEnd;
X        };
X
X    if ( parent->ckHdr.ckID == NULL_CHUNK || /* not currently writing a chunk*/
X         IS_ODD(new->position) ||
X         (Known(new->bound) && IS_ODD(new->bound)) )
X        iffp = CLIENT_ERROR;
X    return(iffp);
X    }
X
X/* ---------- CloseWGroup ----------------------------------------------*/
XIFFP CloseWGroup(old0)  GroupContext *old0; {
X    register GroupContext *old = old0;
X
X    if ( old->ckHdr.ckID != NULL_CHUNK )  /* didn't close the last chunk */
X        return(CLIENT_ERROR);
X    if ( old->parent == NULL ) {          /* top level file context */
X        /* [TBD] set logical EOF */
X        }
X    else {
X        old->parent->bytesSoFar += old->position - old->parent->position;
X        old->parent->position = old->position;
X        };
X    return(IFF_OKAY);
X    }
X
X/* ---------- EndWGroup ------------------------------------------------*/
XIFFP EndWGroup(old)  GroupContext *old;  {
X    register GroupContext *parent = old->parent;
X    register IFFP iffp;
X
X    iffp = CloseWGroup(old);
X    IfIffp( PutCkEnd(parent) );
X    return(iffp);
X    }
X
X/* ---------- PutCk ----------------------------------------------------*/
XIFFP PutCk(context, ckID, ckSize, data)
X      GroupContext *context; ID ckID; LONG ckSize; BYTE *data; {
X    register IFFP iffp = IFF_OKAY;
X
X    if ( ckSize == szNotYetKnown )
X        iffp = CLIENT_ERROR;
X    IfIffp( PutCkHdr(context, ckID, ckSize) );
X    IfIffp( IFFWriteBytes(context, data, ckSize) );
X    IfIffp( PutCkEnd(context) );
X    return(iffp);
X    }
X
X/* ---------- PutCkHdr -------------------------------------------------*/
XIFFP PutCkHdr(context0, ckID, ckSize)
X      GroupContext *context0;  ID ckID;  LONG ckSize; {
X    register GroupContext *context = context0;
X    LONG minPSize = sizeof(ChunkHeader); /* physical chunk >= minPSize bytes*/
X
X    /* CLIENT_ERROR if we're already inside a chunk or asked to write
X     * other than one FORM, LIST, or CAT at the top level of a file */
X    /* Also, non-positive ID values are illegal and used for error codes.*/
X    /* (We could check for other illegal IDs...)*/
X    if ( context->ckHdr.ckID != NULL_CHUNK  ||  ckID <= 0 )
X        return(CLIENT_ERROR);
X    else if (context->parent == NULL)  {
X        switch (ckID)  {
X            case FORM:  case LIST:  case CAT:  break;
X            default: return(CLIENT_ERROR);
X            }
X        if (context->position != 0)
X            return(CLIENT_ERROR);
X        }
X
X    if ( Known(ckSize) ) {
X        if ( ckSize < 0 )
X            return(CLIENT_ERROR);
X        minPSize += ckSize;
X        };
X    if ( Known(context->bound)  &&
X         context->position + minPSize > context->bound )
X        return(CLIENT_ERROR);
X
X    context->ckHdr.ckID   = ckID;
X    context->ckHdr.ckSize = ckSize;
X    context->bytesSoFar   = 0;
X    if (0 > Write(context->file, &context->ckHdr, sizeof(ChunkHeader)))
X        return(DOS_ERROR);
X    context->position += sizeof(ChunkHeader);
X    return(IFF_OKAY);
X    }
X
X/* ---------- IFFWriteBytes ---------------------------------------------*/
XIFFP IFFWriteBytes(context0, data, nBytes)
X      GroupContext *context0;  BYTE *data;  LONG nBytes; {
X    register GroupContext *context = context0;
X
X    if ( context->ckHdr.ckID == NULL_CHUNK  ||  /* not in a chunk */
X         nBytes < 0  ||                         /* negative nBytes */
X         (Known(context->bound)  &&             /* overflow context */
X            context->position + nBytes > context->bound)  ||
X         (Known(context->ckHdr.ckSize)  &&      /* overflow chunk */
X            context->bytesSoFar + nBytes > context->ckHdr.ckSize) )
X        return(CLIENT_ERROR);
X
X    if (0 > Write(context->file, data, nBytes))
X        return(DOS_ERROR);
X
X    context->bytesSoFar += nBytes;
X    context->position   += nBytes;
X    return(IFF_OKAY);
X    }
X
X/* ---------- PutCkEnd -------------------------------------------------*/
XIFFP PutCkEnd(context0)  GroupContext *context0; {
X    register GroupContext *context = context0;
X    WORD zero = 0;      /* padding source */
X
X    if ( context->ckHdr.ckID == NULL_CHUNK )  /* not in a chunk */
X        return(CLIENT_ERROR);
X
X    if ( context->ckHdr.ckSize == szNotYetKnown ) {
X        /* go back and set the chunk size to bytesSoFar */
X        if ( 0 > Seek(context->file,
X                      -(context->bytesSoFar + sizeof(LONG)),
X                      OFFSET_CURRENT)  ||
X             0 > Write(context->file, &context->bytesSoFar, sizeof(LONG))  ||
X             0 > Seek(context->file, context->bytesSoFar, OFFSET_CURRENT)  )
X            return(DOS_ERROR);
X        }
X    else {  /* make sure the client wrote as many bytes as planned */
X        if ( context->ckHdr.ckSize != context->bytesSoFar )
X            return(CLIENT_ERROR);
X        };
X
X    /* Write a pad byte if needed to bring us up to an even boundary.
X     * Since the context end must be even, and since we haven't
X     * overwritten the context, if we're on an odd position there must
X     * be room for a pad byte. */
X    if ( IS_ODD(context->bytesSoFar) ) {
X        if ( 0 > Write(context->file, &zero, 1) )
X            return(DOS_ERROR);
X        context->position += 1;
X        };
X
X    context->ckHdr.ckID   = NULL_CHUNK;
X    context->ckHdr.ckSize = context->bytesSoFar = 0;
X    return(IFF_OKAY);
X    }
X
/
echo 'x - iffcheck.c'
sed 's/^X//' > iffcheck.c << '/'
X/*---------------------------------------------------------------------*
X * IFFCheck.C  Print out the structure of an IFF-85 file,      11/19/85
X * checking for structural errors.
X *
X * DO NOT USE THIS AS A SKELETAL PROGRAM FOR AN IFF READER!
X * See ShowILBM.C for a skeletal example.
X *
X * This version for the Commodore-Amiga computer.
X *----------------------------------------------------------------------*/
X#include "iff/iff.h"
X
X
X/* ---------- IFFCheck -------------------------------------------------*/
X/* [TBD] More extensive checking could be done on the IDs encountered in the
X * file. Check that the reserved IDs "FOR1".."FOR9", "LIS1".."LIS9", and
X * "CAT1".."CAT9" aren't used. Check that reserved IDs aren't used as Form
X * types. Check that all IDs are made of 4 printable characters (trailing
X * spaces ok). */
X
Xtypedef struct {
X    ClientFrame clientFrame;
X    int levels;         /* # groups currently nested within.*/
X    } Frame;
X
Xchar MsgOkay[] = { "----- (IFF_OKAY) A good IFF file." };
Xchar MsgEndMark[] = {"----- (END_MARK) How did you get this message??" };
Xchar MsgDone[] = { "----- (IFF_DONE) How did you get this message??" };
Xchar MsgDos[] = { "----- (DOS_ERROR) The DOS gave back an error." };
Xchar MsgNot[] = { "----- (NOT_IFF) not an IFF file." };
Xchar MsgNoFile[] = { "----- (NO_FILE) no such file found." };
Xchar MsgClientError[] = {"----- (CLIENT_ERROR) IFF Checker bug."};
Xchar MsgForm[] = { "----- (BAD_FORM) How did you get this message??" };
Xchar MsgShort[] = { "----- (SHORT_CHUNK) How did you get this message??" };
Xchar MsgBad[] = { "----- (BAD_IFF) a mangled IFF file." };
X
X/* MUST GET THESE IN RIGHT ORDER!!*/
Xchar *IFFPMessages[-LAST_ERROR+1] = {
X    /*IFF_OKAY*/  MsgOkay,
X    /*END_MARK*/  MsgEndMark,
X    /*IFF_DONE*/  MsgDone,
X    /*DOS_ERROR*/ MsgDos,
X    /*NOT_IFF*/   MsgNot,
X    /*NO_FILE*/   MsgNoFile,
X    /*CLIENT_ERROR*/ MsgClientError,
X    /*BAD_FORM*/  MsgForm,
X    /*SHORT_CHUNK*/  MsgShort,
X    /*BAD_IFF*/   MsgBad
X    };
X
X/* FORWARD REFERENCES */
Xextern IFFP GetList(GroupContext *);
Xextern IFFP GetForm(GroupContext *);
Xextern IFFP GetProp(GroupContext *);
Xextern IFFP GetCat (GroupContext *);
X
Xvoid IFFCheck(name)  char *name; {
X    IFFP iffp;
X    BPTR file = Open(name, MODE_OLDFILE);
X    Frame frame;
X
X    frame.levels = 0;
X    frame.clientFrame.getList = GetList;
X    frame.clientFrame.getForm = GetForm;
X    frame.clientFrame.getProp = GetProp;
X    frame.clientFrame.getCat  = GetCat ;
X
X    printf("----- Checking file '%s' -----\n", name);
X    if (file == 0)
X        iffp = NO_FILE;
X    else
X        iffp = ReadIFF(file, (ClientFrame *)&frame);
X
X    Close(file);
X    printf("%s\n", IFFPMessages[-iffp]);
X    }
X
Xmain(argc, argv)   int argc;  char **argv; {
X    if (argc != 1+1) {
X        printf("Usage: 'iffcheck filename'\n");
X        exit(0);
X        }
X    IFFCheck(argv[1]);
X    }
X
X/* ---------- Put... ---------------------------------------------------*/
X
XPutLevels(count)   int count; {
X    for ( ;  count > 0;  --count) {
X        printf(".");
X        }
X    }
X
XPutID(id)  ID id; {
X    printf("%c%c%c%c", (id>>24)&0x7f, (id>>16)&0x7f, (id>>8)&0x7f, id&0x7f);
X    }
X
XPutN(n)   int n; {
X    printf(" %d ", n);
X    }
X
X/* Put something like "...BMHD 14" or "...LIST 14 PLBM". */
XPutHdr(context)  GroupContext *context; {
X    PutLevels( ((Frame *)context->clientFrame)->levels );
X    PutID(context->ckHdr.ckID);
X    PutN(context->ckHdr.ckSize);
X
X    if (context->subtype != NULL_CHUNK)
X        PutID(context->subtype);
X
X    printf("\n");
X    }
X
X/* ---------- AtLeaf ---------------------------------------------------*/
X
X/* At Leaf chunk.  That is, a chunk which does NOT contain other chunks.
X * Print "ID size".*/
XIFFP AtLeaf(context)  GroupContext *context; {
X
X    PutHdr(context);
X    /* A typical reader would read the chunk's contents, using the "Frame"
X     * for local data, esp. shared property settings (PROP).*/
X    /* IFFReadBytes(context, ...buffer, context->ckHdr->ckSize); */
X    return(IFF_OKAY);
X    }
X
X/* ---------- GetList --------------------------------------------------*/
X/* Handle a LIST chunk.  Print "LIST size subTypeID".
X * Then dive into it.*/
XIFFP GetList(parent)  GroupContext *parent; {
X    Frame newFrame;
X
X    newFrame = *(Frame *)parent->clientFrame;  /* copy parent's frame*/
X    newFrame.levels++;
X
X    PutHdr(parent);
X
X    return( ReadIList(parent, (ClientFrame *)&newFrame) );
X    }
X
X/* ---------- GetForm --------------------------------------------------*/
X/* Handle a FORM chunk.  Print "FORM size subTypeID".
X * Then dive into it.*/
XIFFP GetForm(parent)   GroupContext *parent; {
X    /*CompilerBug register*/ IFFP iffp;
X    GroupContext new;
X    Frame newFrame;
X
X    newFrame = *(Frame *)parent->clientFrame;  /* copy parent's frame*/
X    newFrame.levels++;
X
X    PutHdr(parent);
X
X    iffp = OpenRGroup(parent, &new);
X    CheckIFFP();
X    new.clientFrame = (ClientFrame *)&newFrame;
X
X    /* FORM reader for Checker. */
X    /* LIST, FORM, PROP, CAT already handled by GetF1ChunkHdr. */
X    do {if ( (iffp = GetF1ChunkHdr(&new)) > 0 )
X            iffp = AtLeaf(&new);
X        } while (iffp >= IFF_OKAY);
X
X    CloseRGroup(&new);
X    return(iffp == END_MARK ? IFF_OKAY : iffp);
X    }
X
X/* ---------- GetProp --------------------------------------------------*/
X/* Handle a PROP chunk.  Print "PROP size subTypeID".
X * Then dive into it.*/
XIFFP GetProp(listContext)  GroupContext *listContext; {
X    /*CompilerBug register*/ IFFP iffp;
X    GroupContext new;
X
X    PutHdr(listContext);
X
X    iffp = OpenRGroup(listContext, &new);
X    CheckIFFP();
X
X    /* PROP reader for Checker. */
X    ((Frame *)listContext->clientFrame)->levels++;
X
X    do {if ( (iffp = GetPChunkHdr(&new)) > 0 )
X            iffp = AtLeaf(&new);
X        } while (iffp >= IFF_OKAY);
X
X    ((Frame *)listContext->clientFrame)->levels--;
X
X    CloseRGroup(&new);
X    return(iffp == END_MARK ? IFF_OKAY : iffp);
X    }
X
X/* ---------- GetCat ---------------------------------------------------*/
X/* Handle a CAT chunk.  Print "CAT size subTypeID".
X * Then dive into it.*/
XIFFP GetCat(parent)  GroupContext *parent;  {
X    IFFP iffp;
X
X    ((Frame *)parent->clientFrame)->levels++;
X
X    PutHdr(parent);
X
X    iffp = ReadICat(parent);
X
X    ((Frame *)parent->clientFrame)->levels--;
X    return(iffp);
X    }
X
/
echo 'x - iffcheck.lnk'
sed 's/^X//' > iffcheck.lnk << '/'
XFROM startup.o,iffcheck.o,iffr.o
XLIBRARY lc.lib,amiga.lib,debug.lib
XTO iffcheck
/
exit
----------------
Have fun!
-- 
           Mike Farren
           uucp: {your favorite backbone site}!hplabs!well!farren
           Fido: Sci-Fido, Fidonode 125/84, (415)655-0667

farren@well.UUCP (Mike Farren) (01/11/86)

    This is the second of two postings containing the Electronic Arts
    public domain examples for the IFF file interchange format.  This
    posting contains examples of reading and writing ILBM (Interleaved
    BitMap) type IFF files.  These are the files used to represent
    graphics screens on the Amiga by such programs as EA's Deluxe Paint.
      The document describing the IFF formats will be included in the
    version 1.1 Rom Kernal Manual, which should be available soon.

******************************* CUT HERE! *******************************
#
#    This is a shell archive.  To unpack the files, delete everything
#    before "#!/bin/sh", and then pipe the body through the "sh" command.
#    This archive contains the following files:
#
#    ilbm.h - The header file for the ILBM programs
#    ilbmr.c - The ILBM read routines
#    ilbmw.c - The ILBM write routines
#    packer.h - The header file for the packer/unpacker routines
#    packer.c - The row packer routine
#    unpacker.c - The row unpacker routine
#    raw2ilbm.c - A program demonstrating the use of the ILBM routines
#    showilbm.c - Another ILBM demonstration program
#    showilbm.lnk - The linker file for showilbm
#
echo 'Start of ILBM files'
echo 'x - ilbm.h'
sed 's/^X//' > ilbm.h << '/'
X#ifndef ILBM_H
X#define ILBM_H
X/*----------------------------------------------------------------------*
X * ILBM.H  Definitions for InterLeaved BitMap raster image.    11/15/85
X *
X * By Jerry Morrison and Steve Shaw, Electronic Arts.
X * This software is in the public domain.
X *
X * This version for the Commodore-Amiga computer.
X *----------------------------------------------------------------------*/
X#ifndef EXEC_TYPES_H
X#include "exec/types.h"
X#endif
X
X#ifndef GRAPHICS_GFX_H
X#include "graphics/gfx.h"
X#endif
X
X#include "iff/iff.h"
X
X#define ID_ILBM MakeID('I','L','B','M')
X#define ID_BMHD MakeID('B','M','H','D')
X#define ID_CMAP MakeID('C','M','A','P')
X#define ID_GRAB MakeID('G','R','A','B')
X#define ID_DEST MakeID('D','E','S','T')
X#define ID_SPRT MakeID('S','P','R','T')
X#define ID_CAMG MakeID('C','A','M','G')
X#define ID_BODY MakeID('B','O','D','Y')
X
X/* ---------- BitMapHeader ---------------------------------------------*/
X
Xtypedef UBYTE Masking;          /* Choice of masking technique.*/
X#define mskNone                 0
X#define mskHasMask              1
X#define mskHasTransparentColor  2
X#define mskLasso                3
X
Xtypedef UBYTE Compression;      /* Choice of compression algorithm applied to
X     * each row of the source and mask planes. "cmpByteRun1" is the byte run
X     * encoding generated by Mac's PackBits. See Packer.h . */
X#define cmpNone      0
X#define cmpByteRun1  1
X
X/* Aspect ratios: The proper fraction xAspect/yAspect represents the pixel
X * aspect ratio pixel_width/pixel_height.
X *
X * For the 4 Amiga display modes:
X *   320 x 200: 10/11  (these pixels are taller than they are wide)
X *   320 x 400: 20/11
X *   640 x 200:  5/11
X *   640 x 400: 10/11           */
X#define x320x200Aspect 10
X#define y320x200Aspect 11
X#define x320x400Aspect 20
X#define y320x400Aspect 11
X#define x640x200Aspect  5
X#define y640x200Aspect 11
X#define x640x400Aspect 10
X#define y640x400Aspect 11
X
X/* A BitMapHeader is stored in a BMHD chunk. */
Xtypedef struct {
X    UWORD w, h;                 /* raster width & height in pixels */
X    WORD  x, y;                 /* position for this image */
X    UBYTE nPlanes;              /* # source bitplanes */
X    Masking masking;            /* masking technique */
X    Compression compression;    /* compression algoithm */
X    UBYTE pad1;                 /* UNUSED.  For consistency, put 0 here.*/
X    UWORD transparentColor;     /* transparent "color number" */
X    UBYTE xAspect, yAspect;     /* aspect ratio, a rational number x/y */
X    WORD  pageWidth, pageHeight;  /* source "page" size in pixels */
X    } BitMapHeader;
X
X/* RowBytes computes the number of bytes in a row, from the width in pixels.*/
X#define RowBytes(w)   (((w) + 15) >> 4 << 1)
X
X
X/* ---------- ColorRegister --------------------------------------------*/
X/* A CMAP chunk is a packed array of ColorRegisters (3 bytes each). */
Xtypedef struct {
X    UBYTE red, green, blue;   /* MUST be UBYTEs so ">> 4" won't sign extend.*/
X    } ColorRegister;
X
X/* Use this constant instead of sizeof(ColorRegister). */
X#define sizeofColorRegister  3
X
Xtypedef WORD Color4;    /* Amiga RAM version of a color-register,
X                         * with 4 bits each RGB in low 12 bits.*/
X
X/* Maximum number of bitplanes in RAM. Current Amiga max w/dual playfield. */
X#define MaxAmDepth 6
X
X/* ---------- Point2D --------------------------------------------------*/
X/* A Point2D is stored in a GRAB chunk. */
Xtypedef struct {
X    WORD x, y;          /* coordinates (pixels) */
X    } Point2D;
X
X/* ---------- DestMerge ------------------------------------------------*/
X/* A DestMerge is stored in a DEST chunk. */
Xtypedef struct {
X    UBYTE depth;        /* # bitplanes in the original source */
X    UBYTE pad1;         /* UNUSED; for consistency store 0 here */
X    UWORD planePick;    /* how to scatter source bitplanes into destination */
X    UWORD planeOnOff;   /* default bitplane data for planePick */
X    UWORD planeMask;    /* selects which bitplanes to store into */
X    } DestMerge;
X
X/* ---------- SpritePrecedence -----------------------------------------*/
X/* A SpritePrecedence is stored in a SPRT chunk. */
Xtypedef UWORD SpritePrecedence;
X
X/* ---------- Viewport Mode --------------------------------------------*/
X/* A Commodore Amiga ViewPort->Modes is stored in a CAMG chunk. */
X/* The chunk's content is declared as a LONG. */
X
X/* ---------- CRange ---------------------------------------------------*/
X/* A CRange is store in a CRNG chunk. */
Xtypedef struct {
X    WORD  pad1;         /* reserved for future use; store 0 here */
X    WORD  rate;         /* color cycling rate, 16384 = 60 steps/second */
X    WORD  active;       /* nonzero means color cycling is turned on */
X    UBYTE low, high;    /* lower and upper color registers selected */
X    } CRange;
X
X/* ---------- ILBM Writer Support Routines -----------------------------*/
X
X/* Note: Just call PutCk to write a BMHD, GRAB, DEST, SPRT, or CAMG
X * chunk. As below. */
X#define PutBMHD(context, bmHdr)  \
X    PutCk(context, ID_BMHD, sizeof(BitMapHeader), (BYTE *)bmHdr)
X#define PutGRAB(context, point2D)  \
X    PutCk(context, ID_GRAB, sizeof(Point2D), (BYTE *)point2D)
X#define PutDEST(context, destMerge)  \
X    PutCk(context, ID_DEST, sizeof(DestMerge), (BYTE *)destMerge)
X#define PutSPRT(context, spritePrec)  \
X    PutCk(context, ID_SPRT, sizeof(SpritePrecedence), (BYTE *)spritePrec)
X
X/* Initialize a BitMapHeader record for a full-BitMap ILBM picture.
X * This gets w, h, and nPlanes from the BitMap fields BytesPerRow, Rows, and
X * Depth. It assumes you want  w = bitmap->BytesPerRow * 8 .
X * CLIENT_ERROR if bitmap->BytesPerRow isn't even, as required by ILBM format.
X *
X * If (pageWidth, pageHeight) is (320, 200), (320, 400), (640, 200), or
X * (640, 400) this sets (xAspect, yAspect) based on those 4 Amiga display
X * modes. Otherwise, it sets them to (1, 1).
X *
X * After calling this, store directly into the BitMapHeader if you want to
X * override any settings, e.g. to make nPlanes smaller, to reduce w a little,
X * or to set a position (x, y) other than (0, 0).*/
Xextern IFFP InitBMHdr(BitMapHeader *, struct BitMap *,
X                  /*  bmHdr,          bitmap  */
X     int,     int,         int,              int,       int);
X /*  masking, compression, transparentColor, pageWidth, pageHeight  */
X /*  Masking, Compression, UWORD,            WORD,      WORD  */
X
X/* Output a CMAP chunk to an open FORM ILBM write context. */
Xextern IFFP PutCMAP(GroupContext *, WORD *,   UBYTE);
X                /*  context,        colorMap, depth  */
X
X/* This procedure outputs a BitMap as an ILBM's BODY chunk with
X * bitplane and mask data. Compressed if bmHdr->compression == cmpByteRun1.
X * If the "mask" argument isn't NULL, it merges in the mask plane, too.
X * (A fancier routine could write a rectangular portion of an image.)
X * This gets Planes (bitplane ptrs) from "bitmap".
X *
X * CLIENT_ERROR if bitmap->Rows != bmHdr->h, or if
X * bitmap->BytesPerRow != RowBytes(bmHdr->w), or if
X * bitmap->Depth < bmHdr->nPlanes, or if bmHdr->nPlanes > MaxAmDepth, or if
X * bufsize < MaxPackedSize(bitmap->BytesPerRow), or if
X * bmHdr->compression > cmpByteRun1. */
Xextern IFFP PutBODY(
X    GroupContext *, struct BitMap *, BYTE *, BitMapHeader *, BYTE *, LONG);
X    /*  context,           bitmap,   mask,   bmHdr,         buffer, bufsize */
X
X/* ---------- ILBM Reader Support Routines -----------------------------*/
X
X/* Note: Just call IFFReadBytes to read a BMHD, GRAB, DEST, SPRT, or CAMG
X * chunk. As below. */
X#define GetBMHD(context, bmHdr)  \
X    IFFReadBytes(context, (BYTE *)bmHdr, sizeof(BitMapHeader))
X#define GetGRAB(context, point2D)  \
X    IFFReadBytes(context, (BYTE *)point2D, sizeof(Point2D))
X#define GetDEST(context, destMerge)  \
X    IFFReadBytes(context, (BYTE *)destMerge, sizeof(DestMerge))
X#define GetSPRT(context, spritePrec)  \
X    IFFReadBytes(context, (BYTE *)spritePrec, sizeof(SpritePrecedence))
X
X/* Input a CMAP chunk from an open FORM ILBM read context.
X * This converts to an Amiga color map: 4 bits each of red, green, blue packed
X * into a 16 bit color register.
X * pNColorRegs is passed in as a pointer to a UBYTE variable that holds
X * the number of ColorRegisters the caller has space to hold. GetCMAP sets
X * that variable to the number of color registers actually read.*/
Xextern IFFP GetCMAP(GroupContext *, WORD *,   UBYTE *);
X                /*  context,        colorMap, pNColorRegs  */
X
X/* GetBODY can handle a file with up to 16 planes plus a mask.*/
X#define MaxSrcPlanes 16+1
X
X/* GetBODY reads an ILBM's BODY into a client's bitmap, de-interleaving and
X * decompressing.
X *
X * Caller should first compare bmHdr dimensions (rowWords, h, nPlanes) with
X * bitmap dimensions, and consider reallocating the bitmap.
X * If file has more bitplanes than bitmap, this reads first few planes (low
X * order ones). If bitmap has more bitplanes, the last few are untouched.
X * This reads the MIN(bmHdr->h, bitmap->Rows) rows, discarding the bottom
X * part of the source or leaving the bottom part of the bitmap untouched.
X *
X * GetBODY returns CLIENT_ERROR if asked to perform a conversion it doesn't
X * handle. It only understands compression algorithms cmpNone and cmpByteRun1.
X * The filed row width (# words) must agree with bitmap->BytesPerRow.
X *
X * Caller should use bmHdr.w; GetBODY only uses it to compute the row width
X * in words. Pixels to the right of bmHdr.w are not defined.
X *
X * [TBD] In the future, GetBODY could clip the stored image horizontally or
X * fill (with transparentColor) untouched parts of the destination bitmap.
X *
X * GetBODY stores the mask plane, if any, in the buffer pointed to by mask.
X * If mask == NULL, GetBODY will skip any mask plane. If
X * (bmHdr.masking != mskHasMask) GetBODY just leaves the caller's mask alone.
X *
X * GetBODY needs a buffer large enough for two compressed rows.
X * It returns CLIENT_ERROR if bufsize < 2 * MaxPackedSize(bmHdr.rowWords * 2).
X *
X * GetBODY can handle a file with up to MaxSrcPlanes planes. It returns
X * CLIENT_ERROR if the file has more. (Could be due to a bum file, though.)
X * If GetBODY fails, it might've modified the client's bitmap. Sorry.*/
Xextern IFFP GetBODY(
X    GroupContext *, struct BitMap *, BYTE *, BitMapHeader *, BYTE *, LONG);
X    /*  context,           bitmap,   mask,   bmHdr,         buffer, bufsize */
X
X/* [TBD] Add routine(s) to create masks when reading ILBMs whose
X * masking != mskHasMask. For mskNone, create a rectangular mask. For
X * mskHasTransparentColor, create a mask from transparentColor. For mskLasso,
X * create an "auto mask" by filling transparent color from the edges. */
X
X#endif
X
/
echo 'x - ilbmr.c'
sed 's/^X//' > ilbmr.c << '/'
X/*----------------------------------------------------------------------*
X * ILBMR.C  Support routines for reading ILBM files.           11/27/85
X * (IFF is Interchange Format File.)
X *
X * By Jerry Morrison and Steve Shaw, Electronic Arts.
X * This software is in the public domain.
X *
X * This version for the Commodore-Amiga computer.
X *----------------------------------------------------------------------*/
X#include "iff/packer.h"
X#include "iff/ilbm.h"
X
X
X/* ---------- GetCMAP ------------------------------------------------*/
X/* pNColorRegs is passed in as a pointer to the number of ColorRegisters
X * caller has space to hold.  GetCMAP sets to the number actually read.*/
XIFFP GetCMAP(ilbmContext, colorMap, pNColorRegs)
X      GroupContext *ilbmContext;  WORD *colorMap;  UBYTE *pNColorRegs;
X   {
X   register int nColorRegs;
X   register IFFP iffp;
X   ColorRegister colorReg;
X
X   nColorRegs = ilbmContext->ckHdr.ckSize / sizeofColorRegister;
X   if (*pNColorRegs < nColorRegs)   nColorRegs = *pNColorRegs;
X   *pNColorRegs = nColorRegs;   /* Set to the number actually there.*/
X
X   for ( ;  nColorRegs > 0;  --nColorRegs)  {
X      iffp = IFFReadBytes(ilbmContext, (BYTE *)&colorReg,sizeofColorRegister);
X      CheckIFFP();
X      *colorMap++ = ( ( colorReg.red   >> 4 ) << 8 ) |
X                    ( ( colorReg.green >> 4 ) << 4 ) |
X                    ( ( colorReg.blue  >> 4 )      );
X      }
X   return(IFF_OKAY);
X   }
X
X/*---------- GetBODY ---------------------------------------------------*/
X/* NOTE: This implementation could be a LOT faster if it used more of the
X * supplied buffer. It would make far fewer calls to IFFReadBytes (and
X * therefore to DOS Read) and to movemem. */
XIFFP GetBODY(context, bitmap, mask, bmHdr, buffer, bufsize)
X      GroupContext *context;  struct BitMap *bitmap;  BYTE *mask;
X      BitMapHeader *bmHdr;  BYTE *buffer;  LONG bufsize;
X   {
X   register IFFP iffp;
X   UBYTE srcPlaneCnt = bmHdr->nPlanes;   /* Haven't counted for mask plane yet*/
X   WORD srcRowBytes = RowBytes(bmHdr->w);
X   LONG bufRowBytes = MaxPackedSize(srcRowBytes);
X   int nRows = bmHdr->h;
X   Compression compression = bmHdr->compression;
X   register int iPlane, iRow, nEmpty;
X   register WORD nFilled;
X   BYTE *buf, *nullDest, *nullBuf, **pDest;
X   BYTE *planes[MaxSrcPlanes]; /* array of ptrs to planes & mask */
X
X   if (compression > cmpByteRun1)
X      return(CLIENT_ERROR);
X
X   /* Complain if client asked for a conversion GetBODY doesn't handle.*/
X   if ( srcRowBytes  !=  bitmap->BytesPerRow  ||
X         bufsize < bufRowBytes * 2  ||
X         srcPlaneCnt > MaxSrcPlanes )
X      return(CLIENT_ERROR);
X
X   if (nRows > bitmap->Rows)
X      nRows = bitmap->Rows;
X
X   /* Initialize array "planes" with bitmap ptrs; NULL in empty slots.*/
X   for (iPlane = 0; iPlane < bitmap->Depth; iPlane++)
X      planes[iPlane] = (BYTE *)bitmap->Planes[iPlane];
X   for ( ;  iPlane < MaxSrcPlanes;  iPlane++)
X      planes[iPlane] = NULL;
X
X   /* Copy any mask plane ptr into corresponding "planes" slot.*/
X   if (bmHdr->masking == mskHasMask) {
X      if (mask != NULL)
X         planes[srcPlaneCnt] = mask;  /* If there are more srcPlanes than
X               * dstPlanes, there will be NULL plane-pointers before this.*/
X      else
X         planes[srcPlaneCnt] = NULL;  /* In case more dstPlanes than src.*/
X      srcPlaneCnt += 1;  /* Include mask plane in count.*/
X      }
X
X   /* Setup a sink for dummy destination of rows from unwanted planes.*/
X   nullDest = buffer;
X   buffer  += srcRowBytes;
X   bufsize -= srcRowBytes;
X
X   /* Read the BODY contents into client's bitmap.
X    * De-interleave planes and decompress rows.
X    * MODIFIES: Last iteration modifies bufsize.*/
X   buf = buffer + bufsize;  /* Buffer is currently empty.*/
X   for (iRow = nRows; iRow > 0; iRow--)  {
X      for (iPlane = 0; iPlane < srcPlaneCnt; iPlane++)  {
X
X         pDest = &planes[iPlane];
X
X         /* Establish a sink for any unwanted plane.*/
X         if (*pDest == NULL) {
X            nullBuf = nullDest;
X            pDest   = &nullBuf;
X            }
X
X         /* Read in at least enough bytes to uncompress next row.*/
X         nEmpty  = buf - buffer;          /* size of empty part of buffer.*/
X         nFilled = bufsize - nEmpty;      /* this part has data.*/
X         if (nFilled < bufRowBytes) {
X            /* Need to read more.*/
X
X            /* Move the existing data to the front of the buffer.*/
X            /* Now covers range buffer[0]..buffer[nFilled-1].*/
X            movmem(buf, buffer, nFilled);  /* Could be moving 0 bytes.*/
X
X            if (nEmpty > ChunkMoreBytes(context)) {
X               /* There aren't enough bytes left to fill the buffer.*/
X               nEmpty = ChunkMoreBytes(context);
X               bufsize = nFilled + nEmpty;  /* heh-heh */
X               }
X
X            /* Append new data to the existing data.*/
X            iffp = IFFReadBytes(context, &buffer[nFilled], nEmpty);
X            CheckIFFP();
X
X            buf     = buffer;
X            nFilled = bufsize;
X            nEmpty  = 0;
X            }
X
X         /* Copy uncompressed row to destination plane.*/
X         if (compression == cmpNone) {
X            if (nFilled < srcRowBytes)  return(BAD_FORM);
X            movmem(buf, *pDest, srcRowBytes);
X            buf    += srcRowBytes;
X            *pDest += srcRowBytes;
X            }
X         else
X         /* Decompress row to destination plane.*/
X            if ( UnPackRow(&buf, pDest, nFilled,  srcRowBytes) )
X                    /*  pSource, pDest, srcBytes, dstBytes  */
X               return(BAD_FORM);
X         }
X      }
X
X   return(IFF_OKAY);
X   }
X
/
echo 'x - ilbmw.c'
sed 's/^X//' > ilbmw.c << '/'
X/*----------------------------------------------------------------------*
X * ILBMW.C  Support routines for writing ILBM files.           11/15/85
X * (IFF is Interchange Format File.)
X *
X * By Jerry Morrison and Steve Shaw, Electronic Arts.
X * This software is in the public domain.
X *
X * This version for the Commodore-Amiga computer.
X *----------------------------------------------------------------------*/
X#include "iff/packer.h"
X#include "iff/ilbm.h"
X
X/*---------- InitBMHdr -------------------------------------------------*/
XIFFP InitBMHdr(bmHdr0, bitmap, masking, compression, transparentColor,
X            pageWidth, pageHeight)
X        BitMapHeader *bmHdr0;  struct BitMap *bitmap;
X        int masking, compression, transparentColor, pageWidth, pageHeight;
X        /*  Masking, Compression, UWORD,            WORD,      WORD  */
X    {
X    register BitMapHeader *bmHdr = bmHdr0;
X    register WORD rowBytes = bitmap->BytesPerRow;
X
X    bmHdr->w = rowBytes << 3;
X    bmHdr->h = bitmap->Rows;
X    bmHdr->x = bmHdr->y = 0;    /* Default position is (0,0).*/
X    bmHdr->nPlanes = bitmap->Depth;
X    bmHdr->masking = masking;
X    bmHdr->compression = compression;
X    bmHdr->pad1 = 0;
X    bmHdr->transparentColor = transparentColor;
X    bmHdr->xAspect = bmHdr->yAspect = 1;
X    bmHdr->pageWidth = pageWidth;
X    bmHdr->pageHeight = pageHeight;
X
X    if (pageWidth = 320)
X        switch (pageHeight) {
X            case 200: {bmHdr->xAspect = x320x200Aspect;
X                       bmHdr->yAspect = y320x200Aspect; break;}
X            case 400: {bmHdr->xAspect = x320x400Aspect;
X                       bmHdr->yAspect = y320x400Aspect; break;}
X            }
X    else if (pageWidth = 640)
X        switch (pageHeight) {
X            case 200: {bmHdr->xAspect = x640x200Aspect;
X                       bmHdr->yAspect = y640x200Aspect; break;}
X            case 400: {bmHdr->xAspect = x640x400Aspect;
X                       bmHdr->yAspect = y640x400Aspect; break;}
X            }
X
X    return( IS_ODD(rowBytes) ? CLIENT_ERROR : IFF_OKAY );
X    }
X
X/*---------- PutCMAP ---------------------------------------------------*/
XIFFP PutCMAP(context, colorMap, depth)
X      GroupContext *context;  WORD *colorMap;  UBYTE depth;
X   {
X   register LONG nColorRegs;
X   IFFP iffp;
X   ColorRegister colorReg;
X
X   if (depth > MaxAmDepth)   depth = MaxAmDepth;
X   nColorRegs = 1 << depth;
X
X   iffp = PutCkHdr(context, ID_CMAP, nColorRegs * sizeofColorRegister);
X   CheckIFFP();
X
X   for ( ;  nColorRegs;  --nColorRegs)  {
X      colorReg.red   = ( *colorMap >> 4 ) & 0xf0;
X      colorReg.green = ( *colorMap      ) & 0xf0;
X      colorReg.blue  = ( *colorMap << 4 ) & 0xf0;
X      iffp = IFFWriteBytes(context, (BYTE *)&colorReg, sizeofColorRegister);
X      CheckIFFP();
X      ++colorMap;
X      }
X
X   iffp = PutCkEnd(context);
X   return(iffp);
X   }
X
X/*---------- PutBODY ---------------------------------------------------*/
X/* NOTE: This implementation could be a LOT faster if it used more of the
X * supplied buffer. It would make far fewer calls to IFFWriteBytes (and
X * therefore to DOS Write). */
XIFFP PutBODY(context, bitmap, mask, bmHdr, buffer, bufsize)
X      GroupContext *context;  struct BitMap *bitmap;  BYTE *mask;
X      BitMapHeader *bmHdr;  BYTE *buffer;  LONG bufsize;
X   {
X   IFFP iffp;
X   LONG rowBytes = bitmap->BytesPerRow;
X   int dstDepth = bmHdr->nPlanes;
X   Compression compression = bmHdr->compression;
X   int planeCnt;                /* number of bit planes including mask */
X   register int iPlane, iRow;
X   register LONG packedRowBytes;
X   BYTE *buf;
X   BYTE *planes[MaxAmDepth + 1]; /* array of ptrs to planes & mask */
X
X   if ( bufsize < MaxPackedSize(rowBytes)  ||   /* Must buffer a comprsd row*/
X        compression > cmpByteRun1  ||           /* bad arg */
X        bitmap->Rows != bmHdr->h   ||           /* inconsistent */
X        rowBytes != RowBytes(bmHdr->w)  ||      /* inconsistent*/
X        bitmap->Depth < dstDepth   ||           /* inconsistent */
X        dstDepth > MaxAmDepth )                 /* too many for this routine*/
X      return(CLIENT_ERROR);
X
X   planeCnt = dstDepth + (mask == NULL ? 0 : 1);
X
X   /* Copy the ptrs to bit & mask planes into local array "planes" */
X   for (iPlane = 0; iPlane < dstDepth; iPlane++)
X      planes[iPlane] = (BYTE *)bitmap->Planes[iPlane];
X   if (mask != NULL)
X      planes[dstDepth] = mask;
X
X   /* Write out a BODY chunk header */
X   iffp = PutCkHdr(context, ID_BODY, szNotYetKnown);
X   CheckIFFP();
X
X   /* Write out the BODY contents */
X   for (iRow = bmHdr->h; iRow > 0; iRow--)  {
X      for (iPlane = 0; iPlane < planeCnt; iPlane++)  {
X
X         /* Write next row.*/
X         if (compression == cmpNone) {
X            iffp = IFFWriteBytes(context, planes[iPlane], rowBytes);
X            planes[iPlane] += rowBytes;
X            }
X
X         /* Compress and write next row.*/
X         else {
X            buf = buffer;
X            packedRowBytes = PackRow(&planes[iPlane], &buf, rowBytes);
X            iffp = IFFWriteBytes(context, buffer, packedRowBytes);
X            }
X
X         CheckIFFP();
X         }
X      }
X
X   /* Finish the chunk */
X   iffp = PutCkEnd(context);
X   return(iffp);
X   }
/
echo 'x - packer.h'
sed 's/^X//' > packer.h << '/'
X#ifndef PACKER_H
X#define PACKER_H
X/*----------------------------------------------------------------------*
X * PACKER.H  typedefs for Data-Compresser.                     11/15/85
X *
X * This module implements the run compression algorithm "cmpByteRun1"; the
X * same encoding generated by Mac's PackBits.
X *
X * By Jerry Morrison and Steve Shaw, Electronic Arts.
X * This software is in the public domain.
X *
X * This version for the Commodore-Amiga computer.
X *----------------------------------------------------------------------*/
X
X#ifndef EXEC_TYPES_H
X#include "exec/types.h"
X#endif
X
X/* This macro computes the worst case packed size of a "row" of bytes. */
X#define MaxPackedSize(rowSize)  ( (rowSize) + ( ((rowSize)+127) >> 7 ) )
X
X
X/* Given POINTERS to POINTER variables, packs one row, updating the source
X * and destination pointers. Returns the size in bytes of the packed row.
X * ASSUMES destination buffer is large enough for the packed row.
X * See MaxPackedSize. */
Xextern LONG PackRow(BYTE **, BYTE **, LONG);
X                /*  pSource, pDest,   rowSize */
X
X/* Given POINTERS to POINTER variables, unpacks one row, updating the source
X * and destination pointers until it produces dstBytes bytes (i.e., the
X * rowSize that went into PackRow).
X * If it would exceed the source's limit srcBytes or if a run would overrun
X * the destination buffer size dstBytes, it stops and returns TRUE.
X * Otherwise, it returns FALSE (no error). */
Xextern BOOL UnPackRow(BYTE **, BYTE **, WORD,     WORD);
X                  /*  pSource, pDest,   srcBytes, dstBytes  */
X
X#endif
X
/
echo 'x - packer.c'
sed 's/^X//' > packer.c << '/'
X/*----------------------------------------------------------------------*
X * packer.c Convert data to "cmpByteRun1" run compression.     11/15/85
X *
X * By Jerry Morrison and Steve Shaw, Electronic Arts.
X * This software is in the public domain.
X *
X *      control bytes:
X *       [0..127]   : followed by n+1 bytes of data.
X *       [-1..-127] : followed by byte to be repeated (-n)+1 times.
X *       -128       : NOOP.
X *
X * This version for the Commodore-Amiga computer.
X *----------------------------------------------------------------------*/
X#include "iff/packer.h"
X
X#define DUMP    0
X#define RUN     1
X
X#define MinRun 3
X#define MaxRun 128
X#define MaxDat 128
X
XLONG putSize;
X#define GetByte()       (*source++)
X#define PutByte(c)      { *dest++ = (c);   ++putSize; }
X
Xchar buf[256];  /* [TBD] should be 128?  on stack?*/
X
XBYTE *PutDump(dest, nn)  BYTE *dest;  int nn; {
X        int i;
X
X        PutByte(nn-1);
X        for(i = 0;  i < nn;  i++)   PutByte(buf[i]);
X        return(dest);
X        }
X
XBYTE *PutRun(dest, nn, cc)   BYTE *dest;  int nn, cc; {
X        PutByte(-(nn-1));
X        PutByte(cc);
X        return(dest);
X        }
X
X#define OutDump(nn)   dest = PutDump(dest, nn)
X#define OutRun(nn,cc) dest = PutRun(dest, nn, cc)
X
X/*----------- PackRow --------------------------------------------------*/
X/* Given POINTERS TO POINTERS, packs one row, updating the source and
X   destination pointers.  RETURNs count of packed bytes.*/
XLONG PackRow(pSource, pDest, rowSize)
X    BYTE **pSource, **pDest;   LONG rowSize; {
X    BYTE *source, *dest;
X    char c,lastc = '\0';
X    BOOL mode = DUMP;
X    short nbuf = 0;             /* number of chars in buffer */
X    short rstart = 0;           /* buffer index current run starts */
X
X    source = *pSource;
X    dest = *pDest;
X    putSize = 0;
X    buf[0] = lastc = c = GetByte();  /* so have valid lastc */
X    nbuf = 1;   rowSize--;      /* since one byte eaten.*/
X
X
X    for (;  rowSize;  --rowSize) {
X        buf[nbuf++] = c = GetByte();
X        switch (mode) {
X                case DUMP:
X                        /* If the buffer is full, write the length byte,
X                           then the data */
X                        if (nbuf>MaxDat) {
X                                OutDump(nbuf-1);
X                                buf[0] = c;
X                                nbuf = 1;   rstart = 0;
X                                break;
X                                }
X
X                        if (c == lastc) {
X                            if (nbuf-rstart >= MinRun) {
X                                if (rstart > 0) OutDump(rstart);
X                                mode = RUN;
X                                }
X                            else if (rstart == 0)
X                                mode = RUN;     /* no dump in progress,
X                                so can't lose by making these 2 a run.*/
X                            }
X                        else  rstart = nbuf-1;          /* first of run */
X                        break;
X
X                case RUN: if ( (c != lastc)|| ( nbuf-rstart > MaxRun)) {
X                        /* output run */
X                        OutRun(nbuf-1-rstart,lastc);
X                        buf[0] = c;
X                        nbuf = 1; rstart = 0;
X                        mode = DUMP;
X                        }
X                        break;
X                }
X
X        lastc = c;
X        }
X
X    switch (mode) {
X        case DUMP: OutDump(nbuf); break;
X        case RUN: OutRun(nbuf-rstart,lastc); break;
X        }
X    *pSource = source;
X    *pDest = dest;
X    return(putSize);
X    }
X
/
echo 'x - unpacker.c'
sed 's/^X//' > unpacker.c << '/'
X/*----------------------------------------------------------------------*
X * unpacker.c Convert data from "cmpByteRun1" run compression. 11/15/85
X *
X * By Jerry Morrison and Steve Shaw, Electronic Arts.
X * This software is in the public domain.
X *
X *      control bytes:
X *       [0..127]   : followed by n+1 bytes of data.
X *       [-1..-127] : followed by byte to be repeated (-n)+1 times.
X *       -128       : NOOP.
X *
X * This version for the Commodore-Amiga computer.
X *----------------------------------------------------------------------*/
X#include "iff/packer.h"
X
X
X/*----------- UnPackRow ------------------------------------------------*/
X
X#define UGetByte()      (*source++)
X#define UPutByte(c)     (*dest++ = (c))
X
X/* Given POINTERS to POINTER variables, unpacks one row, updating the source
X * and destination pointers until it produces dstBytes bytes. */
XBOOL UnPackRow(pSource, pDest, srcBytes0, dstBytes0)
X        BYTE **pSource, **pDest;  WORD srcBytes0, dstBytes0; {
X    register BYTE *source = *pSource;
X    register BYTE *dest   = *pDest;
X    register WORD n;
X    register BYTE c;
X    register WORD srcBytes = srcBytes0, dstBytes = dstBytes0;
X    BOOL error = TRUE;  /* assume error until we make it through the loop */
X    WORD minus128 = -128;  /* get the compiler to generate a CMP.W */
X
X    while( dstBytes > 0 )  {
X        if ( (srcBytes -= 1) < 0 )  goto ErrorExit;
X        n = UGetByte();
X
X        if (n >= 0) {
X            n += 1;
X            if ( (srcBytes -= n) < 0 )  goto ErrorExit;
X            if ( (dstBytes -= n) < 0 )  goto ErrorExit;
X            do {  UPutByte(UGetByte());  } while (--n > 0);
X            }
X
X        else if (n != minus128) {
X            n = -n + 1;
X            if ( (srcBytes -= 1) < 0 )  goto ErrorExit;
X            if ( (dstBytes -= n) < 0 )  goto ErrorExit;
X            c = UGetByte();
X            do {  UPutByte(c);  } while (--n > 0);
X            }
X        }
X    error = FALSE;      /* success! */
X
X  ErrorExit:
X    *pSource = source;  *pDest = dest;
X    return(error);
X    }
X
/
echo 'x - raw2ilbm.c'
sed 's/^X//' > raw2ilbm.c << '/'
X/** Raw2ILBM.c **************************************************************
X *
X * Read an raw raster image file and write an IFF FORM ILBM file.  11/15/85.
X *
X * By Jerry Morrison and Steve Shaw, Electronic Arts.
X * This software is in the public domain.
X *
X * USE THIS AS AN EXAMPLE PROGRAM FOR AN IFF WRITER.
X *
X ****************************************************************************/
X#include "graphics/system.h"
X#include "iff/ilbm.h"
X
X/* Size of the buffer for PutBODY. */
X#define bufSize 512
X
X
X/** PutAnILBM() *************************************************************
X *
X * Write an entire BitMap as a FORM ILBM in an IFF file.
X * This procedure assumes the image is in the Amiga's 320 x 200 display mode.
X *
X * Normal return result is IFF_OKAY.
X *
X * The utility program IFFCheck would print the following outline of the
X * resulting file:
X *
X *   FORM ILBM
X *     BMHD
X *     CMAP
X *     BODY       (compressed)
X *
X ****************************************************************************/
X#define CkErr(expression)  {if (ifferr == IFF_OKAY) ifferr = (expression);}
X
XIFFP PutAnILBM(file, bitmap, mask, colorMap, depth, xy, buffer, bufsize)
X      LONG file;  struct BitMap *bitmap;  BYTE *mask;  WORD *colorMap;
X      UBYTE depth;  Point2D *xy;  BYTE *buffer;  LONG bufsize;
X   {
X   BitMapHeader bmHdr;
X   GroupContext fileContext, formContext;
X   IFFP ifferr;
X
X   ifferr = InitBMHdr(&bmHdr, bitmap, mskNone, cmpByteRun1, 0, 320, 200);
X        /* You could write an uncompressed image by passing cmpNone instead
X         * of cmpByteRun1 to InitBMHdr. */
X   bmHdr.nPlanes = depth;       /* This must be  <= bitmap->Depth */
X   if (mask != NULL) bmHdr.masking = mskHasMask;
X   bmHdr.x = xy->x;   bmHdr.y = xy->y;
X
X   CkErr( OpenWIFF(file, &fileContext, szNotYetKnown) );
X   CkErr(StartWGroup(&fileContext, FORM, szNotYetKnown, ID_ILBM, &formContext));
X
X   CkErr( PutBMHD(&formContext, &bmHdr) );
X   CkErr( PutCMAP(&formContext, colorMap, depth) );
X   CkErr( PutBODY(&formContext, bitmap, mask, &bmHdr, buffer, bufsize) );
X
X   CkErr( EndWGroup(&formContext) );
X   CkErr( CloseWGroup(&fileContext) );
X   return( ifferr );
X   }
X
X/** PutPicture() ************************************************************
X *
X * Put a picture into an IFF file.
X * This procedure calls PutAnILBM, passing in an <x, y> location of <0, 0>,
X * a NULL mask, and a locally-allocated buffer. It also assumes you want to
X * write out all the bitplanes in the BitMap.
X *
X ****************************************************************************/
XPoint2D nullPoint = {0, 0};
X
XIFFP PutPicture(file, bitmap, colorMap)
X      LONG file;  struct BitMap *bitmap;  WORD *colorMap;
X   {
X   BYTE buffer[bufSize];
X   return( PutAnILBM(file, bitmap, NULL,
X                     colorMap, bitmap->Depth, &nullPoint,
X                     buffer, bufSize) );
X   }
X
X/* [TBD] More to come. We need code to read a "raw" raster file into a BitMap
X * and code to handle file opening & closing, error msgs, etc. */
X
/
echo 'x - showilbm.c'
sed 's/^X//' > showilbm.c << '/'
X/** ShowILBM.c **************************************************************
X *
X * Read an ILBM raster image file and display it.  11/19/85.
X *
X * By Jerry Morrison, Steve Shaw, and Steve Hayes, Electronic Arts.
X * This software is in the public domain.
X *
X * USE THIS AS AN EXAMPLE PROGRAM FOR AN IFF READER.
X *
X * The IFF reader portion is essentially a recursive-descent parser.
X * This program will look into a CAT or LIST to find a FORM ILBM, but it
X * won't look inside another FORM type for a nested FORM ILBM.
X *
X * The display portion is specific to the Commodore Amiga computer.
X *
X * NOTE: This program displays an image, pauses, then exits. It doesn't
X * bother to switch you back to the CLI or workbench "screen", so type
X * Amiga-N when it's done.
X *
X ****************************************************************************/
X
X#include "graphics/system.h"
X#include "libraries/dos.h"
X#include "iff/ilbm.h"
X
X/* This example's max number of planes in a bitmap. Could use MaxAmDepth. */
X#define EXDepth 5
X#define maxColorReg (1<<EXDepth)
X#define MIN(a,b) ((a)<(b)?(a):(b))
X
X#define SafeFreeMem(p,q) {if(p)FreeMem(p,q);}
X
X/* general usage pointers */
Xstruct GfxBase *GfxBase;
X
X/* Globals for displaying an image */
Xstruct RastPort rP;
Xstruct BitMap bitmap;
Xstruct RasInfo rasinfo;
Xstruct View v = {0};
Xstruct ViewPort vp = {0};
X
X/* Define the size of a temporary buffer used in unscrambling the ILBM rows.*/
X#define bufSz 512
X
X/* Message strings for IFFP codes. */
Xchar MsgOkay[]        = { "(IFF_OKAY) No FORM ILBM in the file." };
Xchar MsgEndMark[]     = { "(END_MARK) How did you get this message?" };
Xchar MsgDone[]        = { "(IFF_DONE) All done."};
Xchar MsgDos[]         = { "(DOS_ERROR) The DOS returned an error." };
Xchar MsgNot[]         = { "(NOT_IFF) Not an IFF file." };
Xchar MsgNoFile[]      = { "(NO_FILE) No such file found." };
Xchar MsgClientError[] = { "(CLIENT_ERROR) ShowILBM bug or insufficient RAM."};
Xchar MsgForm[]        = { "(BAD_FORM) A malformed FORM ILBM." };
Xchar MsgShort[]       = { "(SHORT_CHUNK) A malformed FORM ILBM." };
Xchar MsgBad[]         = { "(BAD_IFF) A mangled IFF file." };
X
X/* THESE MUST APPEAR IN RIGHT ORDER!! */
Xchar *IFFPMessages[-LAST_ERROR+1] = {
X    /*IFF_OKAY*/  MsgOkay,
X    /*END_MARK*/  MsgEndMark,
X    /*IFF_DONE*/  MsgDone,
X    /*DOS_ERROR*/ MsgDos,
X    /*NOT_IFF*/   MsgNot,
X    /*NO_FILE*/   MsgNoFile,
X    /*CLIENT_ERROR*/ MsgClientError,
X    /*BAD_FORM*/  MsgForm,
X    /*SHORT_CHUNK*/  MsgShort,
X    /*BAD_IFF*/   MsgBad
X    };
X
X/*------------ ILBM reader -----------------------------------------------*/
X/* ILBMFrame is our "client frame" for reading FORMs ILBM in an IFF file.
X * We allocate one of these on the stack for every LIST or FORM encountered
X * in the file and use it to hold BMHD & CMAP properties. We also allocate
X * an initial one for the whole file.
X * We allocate a new GroupContext (and initialize it by OpenRIFF or
X * OpenRGroup) for every group (FORM, CAT, LIST, or PROP) encountered. It's
X * just a context for reading (nested) chunks.
X *
X * If we were to scan the entire example file outlined below:
X *    reading          proc(s)                new               new
X *
X * --whole file--   ReadPicture+ReadIFF   GroupContext        ILBMFrame
X * CAT              ReadICat                GroupContext
X *   LIST           GetLiILBM+ReadIList       GroupContext        ILBMFrame
X *     PROP ILBM    GetPrILBM                   GroupContext
X *       CMAP       GetCMAP
X *       BMHD       GetBMHD
X *     FORM ILBM    GetFoILBM                   GroupContext        ILBMFrame
X *       BODY       GetBODY
X *     FORM ILBM    GetFoILBM                   GroupContext        ILBMFrame
X *       BODY       GetBODY
X *   FORM ILBM      GetFoILBM                 GroupContext        ILBMFrame
X */
Xtypedef struct {
X   ClientFrame clientFrame;
X   UBYTE foundBMHD;
X   UBYTE nColorRegs;
X   BitMapHeader bmHdr;
X   Color4 colorMap[maxColorReg];
X   /* If you want to read any other property chunks, e.g. GRAB or CAMG, add
X    * fields to this record to store them. */
X   } ILBMFrame;
X
X
X/* NOTE: For a simple version of this program, set Fancy to 0.
X * That'll compile a program that skips all LISTs and PROPs in the input
X * file. It will look in CATs for FORMs ILBM. That's suitable for most uses.
X *
X * For a fancy version that handles LISTs and PROPs, set Fancy to 1. */
X#define Fancy  0
X
X
X/** DisplayPic() ************************************************************
X *
X * Interface to Amiga graphics ROM routines.
X *
X ****************************************************************************/
XDisplayPic(ptilbmFrame)  ILBMFrame *ptilbmFrame;  {
X    int i;
X    struct View *oldView = GfxBase->ActiView;   /* so we can restore it */
X
X    InitView(&v);
X    InitVPort(&vp);
X    v.ViewPort = &vp;
X    InitRastPort(&rP);
X    rP.BitMap = &bitmap;
X    rasinfo.BitMap = &bitmap;
X
X    /* Always show the upper left-hand corner of this picture. */
X    rasinfo.RxOffset = 0;
X    rasinfo.RyOffset = 0;
X
X    vp.DWidth = ptilbmFrame->bmHdr.pageWidth;   /* Physical display WIDTH */
X    vp.DHeight = ptilbmFrame->bmHdr.pageHeight; /* Display height */
X
X#if 0
X    /* Specify where on screen to put the ViewPort. */
X    vp.DxOffset = ptilbmFrame->bmHdr.x;
X    vp.DyOffset = ptilbmFrame->bmHdr.y;
X#else
X    /* Always display it in upper left corner of screen.*/
X#endif
X
X    if (ptilbmFrame->bmHdr.pageWidth <= 320)
X        vp.Modes = 0;
X    else vp.Modes = HIRES;
X    if (ptilbmFrame->bmHdr.pageHeight > 200) {
X        v.Modes |= LACE;
X        vp.Modes |= LACE;
X        }
X    vp.RasInfo = &rasinfo;
X    MakeVPort(&v,&vp);
X    MrgCop(&v);
X    LoadView(&v);       /* show the picture */
X    WaitBlit();
X    WaitTOF();
X    LoadRGB4(&vp, ptilbmFrame->colorMap, ptilbmFrame->nColorRegs);
X
X    for (i = 0; i < 5*60; ++i)  WaitTOF();      /* Delay 5 seconds. */
X
X    LoadView(oldView);  /* switch back to old view */
X    }
X
X/** GetFoILBM() *************************************************************
X *
X * Called via ReadPicture to handle every FORM encountered in an IFF file.
X * Reads FORMs ILBM and skips all others.
X * Inside a FORM ILBM, it stops once it reads a BODY. It complains if it
X * finds no BODY or if it has no BMHD to decode the BODY.
X *
X * Once we find a BODY chunk, we'll allocate the BitMap and read the image.
X *
X ****************************************************************************/
XIFFP GetFoILBM(parent)  GroupContext *parent;  {
X   /*compilerBug register*/ IFFP iffp;
X   GroupContext formContext;
X   ILBMFrame ilbmFrame;         /* only used for non-clientFrame fields.*/
X   register int i;
X   int plsize;  /* Plane size in bytes. */
X   int nPlanes; /* number of planes in our display image */
X   BYTE buffer[bufSz];
X
X   if (parent->subtype != ID_ILBM)
X      return(IFF_OKAY); /* just continue scaning the file */
X
X   ilbmFrame = *(ILBMFrame *)parent->clientFrame;
X   iffp = OpenRGroup(parent, &formContext);
X   CheckIFFP();
X
X   do switch (iffp = GetFChunkHdr(&formContext)) {
X      case ID_BMHD: {
X        ilbmFrame.foundBMHD = TRUE;
X        iffp = GetBMHD(&formContext, &ilbmFrame.bmHdr);
X        break; }
X      case ID_CMAP: {
X        ilbmFrame.nColorRegs = maxColorReg;  /* we have room for this many */
X        iffp = GetCMAP(
X           &formContext, (WORD *)&ilbmFrame.colorMap, &ilbmFrame.nColorRegs);
X        break; }
X      case ID_BODY: {
X         if (!ilbmFrame.foundBMHD)  return(BAD_FORM);   /* No BMHD chunk! */
X
X         nPlanes = MIN(ilbmFrame.bmHdr.nPlanes, EXDepth);
X         InitBitMap(
X            &bitmap,
X            nPlanes,
X            ilbmFrame.bmHdr.w,
X            ilbmFrame.bmHdr.h);
X         plsize = RowBytes(ilbmFrame.bmHdr.w) * ilbmFrame.bmHdr.h;
X         if (bitmap.Planes[0] =
X                (PLANEPTR)AllocMem(nPlanes * plsize, MEMF_CHIP))
X            {
X            for (i = 1; i < nPlanes; i++)
X                bitmap.Planes[i] = (PLANEPTR) bitmap.Planes[0] + plsize*i;
X            iffp = GetBODY(
X                &formContext,
X                &bitmap,
X                NULL,
X                &ilbmFrame.bmHdr,
X                buffer,
X                bufSz);
X            if (iffp == IFF_OKAY) iffp = IFF_DONE;      /* Eureka */
X            }
X         else
X            iffp = CLIENT_ERROR;        /* not enough RAM for the bitmap */
X         break; }
X      case END_MARK: { iffp = BAD_FORM; break; } /* No BODY chunk! */
X      } while (iffp >= IFF_OKAY);  /* loop if valid ID of ignored chunk or a
X                          * subroutine returned IFF_OKAY (no errors).*/
X
X   if (iffp != IFF_DONE)  return(iffp);
X
X   /* If we get this far, there were no errors. */
X   CloseRGroup(&formContext);
X   DisplayPic(&ilbmFrame);
X   return(iffp);
X   }
X
X/** Notes on extending GetFoILBM ********************************************
X *
X * To read more kinds of chunks, just add clauses to the switch statement.
X * To read more kinds of property chunks (GRAB, CAMG, etc.) add clauses to
X * the switch statement in GetPrILBM, too.
X *
X * To read a FORM type that contains a variable number of data chunks--e.g.
X * a FORM FTXT with any number of CHRS chunks--replace the ID_BODY case with
X * an ID_CHRS case that doesn't set iffp = IFF_DONE, and make the END_MARK
X * case do whatever cleanup you need.
X *
X ****************************************************************************/
X
X/** GetPrILBM() *************************************************************
X *
X * Called via ReadPicture to handle every PROP encountered in an IFF file.
X * Reads PROPs ILBM and skips all others.
X *
X ****************************************************************************/
X#if Fancy
XIFFP GetPrILBM(parent)  GroupContext *parent;  {
X   /*compilerBug register*/ IFFP iffp;
X   GroupContext propContext;
X   ILBMFrame *ilbmFrame = (ILBMFrame *)parent->clientFrame;
X
X   if (parent->subtype != ID_ILBM)
X      return(IFF_OKAY); /* just continue scaning the file */
X
X   iffp = OpenRGroup(parent, &propContext);
X   CheckIFFP();
X
X   do switch (iffp = GetPChunkHdr(&propContext)) {
X      case ID_BMHD: {
X        ilbmFrame->foundBMHD = TRUE;
X        iffp = GetBMHD(&propContext, &ilbmFrame->bmHdr);
X        break; }
X      case ID_CMAP: {
X        ilbmFrame->nColorRegs = maxColorReg; /* we have room for this many */
X        iffp = GetCMAP(
X          &propContext, (WORD *)&ilbmFrame->colorMap, &ilbmFrame->nColorRegs);
X        break; }
X      } while (iffp >= IFF_OKAY);  /* loop if valid ID of ignored chunk or a
X                          * subroutine returned IFF_OKAY (no errors).*/
X
X   CloseRGroup(&propContext);
X   return(iffp == END_MARK ? IFF_OKAY : iffp);
X   }
X#endif
X
X/** GetLiILBM() *************************************************************
X *
X * Called via ReadPicture to handle every LIST encountered in an IFF file.
X *
X ****************************************************************************/
X#if Fancy
XIFFP GetLiILBM(parent)  GroupContext *parent;  {
X    ILBMFrame newFrame; /* allocate a new Frame */
X
X    newFrame = *(ILBMFrame *)parent->clientFrame;  /* copy parent frame */
X
X    return( ReadIList(parent, (ClientFrame *)&newFrame) );
X    }
X#endif
X
X/** ReadPicture() ***********************************************************
X *
X * Read a picture from an IFF file, given a file handle open for reading.
X *
X ****************************************************************************/
XIFFP ReadPicture(file)  LONG file;  {
X   ILBMFrame iFrame;    /* Top level "client frame".*/
X   IFFP iffp = IFF_OKAY;
X
X#if Fancy
X   iFrame.clientFrame.getList = GetLiILBM;
X   iFrame.clientFrame.getProp = GetPrILBM;
X#else
X   iFrame.clientFrame.getList = SkipGroup;
X   iFrame.clientFrame.getProp = SkipGroup;
X#endif
X   iFrame.clientFrame.getForm = GetFoILBM;
X   iFrame.clientFrame.getCat  = ReadICat ;
X
X   /* Initialize the top-level client frame's property settings to the
X    * program-wide defaults. This example just records that we haven't read
X    * any BMHD property or CMAP color registers yet. For the color map, that
X    * means the default is to leave the machine's color registers alone.
X    * If you want to read a property like GRAB, init it here to (0, 0). */
X   iFrame.foundBMHD  = FALSE;
X   iFrame.nColorRegs = 0;
X
X   iffp = ReadIFF(file, (ClientFrame *)&iFrame);
X
X   Close(file);
X   return(iffp);
X   }
X
X/** main0() *****************************************************************/
Xvoid main0(filename)  char *filename;  {
X    LONG file;
X    IFFP iffp = NO_FILE;
X
X    /* load and display the picture */
X    if( !(GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",0)) )
X        exit(0);
X    file = Open(filename, MODE_OLDFILE);
X    if (file)
X        iffp = ReadPicture(file);
X
X    printf(" %s\n", IFFPMessages[-iffp]);
X
X    /* cleanup */
X    if (bitmap.Planes[0])  {
X        FreeMem(bitmap.Planes[0],
X                bitmap.BytesPerRow * bitmap.Rows * bitmap.Depth);
X        FreeVPortCopLists(&vp);
X        FreeCprList(v.LOFCprList);
X        }
X    CloseLibrary(GfxBase);
X    }
X
X/** main() ******************************************************************/
Xvoid main(argc, argv)  int argc;  char **argv;  {
X    printf("Showing file '%s' ...", argv[1]);
X    if (argc < 2)
X        printf("\nUsage: 'ShowILBM filename'");
X    else
X        main0(argv[1]);
X    printf("\n");
X    exit(0);
X    }
X
/
echo 'x - showilbm.lnk'
sed 's/^X//' > showilbm.lnk << '/'
XFROM startup.o,showilbm.o,ilbmr.o,unpacker.o,iffr.o
XTO showilbm
XLIBRARY lc.lib,amiga.lib
/
exit
------------
Have fun!

-- 
           Mike Farren
           uucp: {your favorite backbone site}!hplabs!well!farren
           Fido: Sci-Fido, Fidonode 125/84, (415)655-0667