Received: from PURCCVM.BITNET by FINTUVM.UTU.FI (Mailer X1.25) with BSMTP id
 6562; Sat, 10 Sep 88 08:49:27 EET
Received: from PURCCVM by PURCCVM.BITNET (Mailer X1.25) with BSMTP id 4131;
 Thu, 08 Sep 88 09:37:46 EST
Received: from j.cc.purdue.edu by VM.CC.PURDUE.EDU ; Thu, 08 Sep 88 09:37:38 EST
Received: by j.cc.purdue.edu (5.54/1.14)
    id AA23601; Thu, 8 Sep 88 09:35:47 EST
Date: Thu, 8 Sep 88 09:35:47 EST
From: ain@j.cc.purdue.edu (Patrick White)
Message-Id: <8809081435.AA23601@j.cc.purdue.edu>
To: fys-ma@fintuvm.BITNET
Subject: comp.sources.amiga:   sources/amiga/volume5/iffcat.s.sh2
 
Newsgroups: comp.sources.amiga
Subject: iffcat (sources 2 of 2)
Keywords: IFF, cat, tested
Approved: ain@j.cc.purdue.edu    (Patrick White)
 
Program Name:    iffcat  (sources 2 of 2)
Submitted by:    nuchat!sugar!karl@uunet.uu.net
Summary:    Uses IFF form(?) CAT to pack several IFF files into one
Poster Boy:    Patrick White    (ain@j.cc.purdue.edu)
Archive Name:    sources/amiga/volume5/iffcat.s.sh2.Z
Tested
 
NOTES:
    Re-shar'ed.
 
 
-- Pat White   (co-moderator comp.sources/binaries.amiga)
ARPA/UUCP: j.cc.purdue.edu!ain  BITNET: PATWHITE@PURCCVM  PHONE: (317) 743-8421
U.S.  Mail:  320 Brown St. apt. 406,    West Lafayette, IN 47906
[archives at: j.cc.purdue.edu.ARPA]
 
========================================
 
#    This is a shell archive.
#    Remove everything above and including the cut line.
#    Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:    Shell Archiver
#    Run the following text with /bin/sh to create:
#    iff.c
#    iff.h
#    includes.c
#    main.c
#    misc.c
#    quickappend.c
#    replace.c
#    toc.c
# This archive created: Tue Sep  6 13:38:53 1988
# By:    Patrick White (PUCC Land, USA)
cat << \SHAR_EOF > iff.c
/* iffar - IFF CAT archiver, IFF support functions
 
   By Karl Lehenbauer, version 1.2, release date 5/9/88
   This code is released to the public domain.
   See the README file for more information.
 
*/
 
/* culled general purpose IFF file cracking routines for Karl's
 * IFF Stuff by Karl Lehenbauer, based originally on public domain IFF
 * code from Electronic Arts, 2/24/88
 */
 
#include <exec/types.h>
#include <exec/memory.h>
#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>
#include "assert.h"
 
#include "iff.h"
 
extern long lseek();
 
extern ULONG nextchunk();
 
/* print a chunkID to stderr */
PutID(id)
ID id;
{
    fprintf(stderr,"%c%c%c%c",
       (char)((id>>24L) & 0x7f),
       (char)((id>>16L) & 0x7f),
       (char)((id>>8)   & 0x7f),
       (char)(id        & 0x7f) );
}
 
UBYTE *MyAllocMem(bytes, type)
ULONG bytes, type;
{
    UBYTE *tmp;
    UBYTE *AllocMem();
        tmp = AllocMem(bytes, type);
    return tmp;
}
 
/* return chunktype of next chunk */
/* every time nextchunk is executed and returns that it found a chunk,
 * either readchunk or skipchunk must be called and only one time!
 */
ULONG nextchunk(fd,chunksize,chunk_bytes_left)
int fd;
long *chunksize, *chunk_bytes_left;
{
    int sawsize, i, blown = 0;
    ChunkHeader mychunkheader;
    char checkchar;
 
    /* if chunk_bytes_left is zero, we obey it as a virtual EOF, so
     * return 0 */
     if (*chunk_bytes_left == 0)
         return(0);
 
    /* read the next chunk header */
    if ((sawsize = read(fd,&mychunkheader,sizeof(mychunkheader))) !=
        sizeof(mychunkheader))
    {
        if (sawsize != 0)
            fprintf(stderr,"Something's wrong with nextchunk! (sawsize %d)\n",
 sawsize);
        *chunksize = 0;
        return(0);
    }
 
#ifdef MAJORDEBUG
    fputs("nextchunk: next chunk '",stderr);
    PutID(mychunkheader.ckID);
    fprintf(stderr,"', size %d, parent bytes left
 %d\n",mychunkheader.ckSize,*chunk_bytes_left);
#endif
 
    *chunksize = mychunkheader.ckSize;
 
    /* see if chunk ID looks OK */
    for (i = 0; i < 4; i++)
    {
        checkchar = (mychunkheader.ckID >> (i * 8)) & 0xff;
        if (!isprint(checkchar))
        {
            if (!blown)
            {
                blown = 1;
                fprintf(stderr,"nextchunk: chunk ID contains an unprintable
 character (0x%x)\n",checkchar);
            }
            break;
        }
    }
 
    /* see if chunk length is reasonable */
    if ((mychunkheader.ckSize < 0) || (mychunkheader.ckSize > MAXCHUNKSIZE))
    {
        fprintf(stderr,"nextchunk: chunk length of %ld is
 unreasonable\n",mychunkheader.ckSize);
        blown = 1;
    }
 
    if (blown)
    {
        fprintf(stderr,"nextchunk: I either got lost or the archive is
 blown\n");
        return(0);
    }
 
    /* square up the bytes left in the chunk by the size of a chunk header,
     * eight bytes.  We leave it to the caller to subtract the size of the
     * body of the chunk by calling skipchunk or readchunk
     */
    *chunk_bytes_left -= sizeof(mychunkheader);
 
    if (*chunk_bytes_left < 0)
    {
        fprintf("nextchunk: chunk overran its parent by %d bytes\n",
(0-*chunk_bytes_left));
        *chunksize = 0;
        *chunk_bytes_left = 0;
        return(0);
    }
 
    return(mychunkheader.ckID);
}
 
/* read next chunk into buffer supplied, size must be value returned by
 * nextchunk
 * zero is returned on failure, one on success
 */
readchunk(fd,buf,size,chunk_bytes_left)
int fd;
char *buf;
LONG size, *chunk_bytes_left;
{
    *chunk_bytes_left -= size;
 
    if (*chunk_bytes_left < 0)
    {
        fprintf(stderr,"readchunk: chunk requested passed the end of its parent
 chunk\n");
        *chunk_bytes_left = 0;
        return(0);
    }
 
    if (read(fd,buf,size) != size)
    {
        perror("smus file");
        fputs("LoadSMUS: read of IFF chunk failed\n",stderr);
        return(0);
    }
 
    /* odd-length chunks have a trailer byte - skip it */
    if (size & 1)
    {
        lseek(fd,1L,1);
        (*chunk_bytes_left)--;
    }
    return(1);
}
 
/* skip non-header portion of chunk, chunksize must have been returned
 * by nextchunk
 * returns 1 on success, 0 on failure
 */
skipchunk(fd,chunksize,chunk_bytes_left)
int fd;
LONG chunksize, *chunk_bytes_left;
{
    *chunk_bytes_left -= chunksize;
    if (chunksize & 1)
        (*chunk_bytes_left)--;
    if (*chunk_bytes_left < 0)
    {
        fprintf(stderr,"skipchunk: chunk size passes end of parent chunk's data
 by %d bytes\n",0 - *chunk_bytes_left);
        return(0);
    }
    /* skip over chunk data and skip an extra byte if length is odd */
    lseek(fd,(long)chunksize,1);
    if (chunksize & 1)
        lseek(fd,1L,1);
    return(1);
}
 
/* OpenIFF
 * given file name, open the IFF file.
 * read the header, return failure if it's not a FORM
 * (someday we'll handle the more complex types)
 * read the form type, return failure if it's not the type requested
 * success, return the file descriptor
 */
 
int OpenIFF(fname,expected_formtype,length_ptr)
char *fname;
LONG expected_formtype;
LONG *length_ptr;
{
    int iffile;
    ChunkHeader chunkhead;
    LONG formtype;
 
    /* open the file */
    if ((iffile = open(fname, O_RDONLY)) < 0)
    {
        fprintf(stderr,"OpenIFF: can't open IFF SMUS file %s\n",fname);
        perror(fname);
        return(-1);
    }
 
    /* get the length */
    *length_ptr = lseek(iffile,0,2);
    lseek(iffile,0,0);
 
    /* read the header chunk */
    if (read(iffile, &chunkhead, sizeof(chunkhead)) < 0)
    {
        fprintf(stderr,"OpenIFF: initial read from IFF file %s
 failed!\n",fname);
        return(-1);
    }
 
    /* return if the header chunk doesn't say it's IFF FORM */
    if (chunkhead.ckID != ID_FORM)
    {
        fprintf(stderr,"OpenIFF: File %s isn't IFF, is too complex, or doesn't
 start with FORM\n",fname);
        return(-1);
    }
    /* fprintf(stderr,"OpenIFF: FORM found, size is %d\n",chunkhead.ckSize); */
 
    /* read the form type */
    read(iffile, &formtype, sizeof(formtype));
 
    /* return if the form type isn't the type requested */
    if (formtype != expected_formtype)
    {
        fprintf(stderr,"OpenIFF: File %s is IFF ");
        PutID(formtype);
        fprintf(stderr," rather than the requested ");
        PutID(expected_formtype);
        fprintf(stderr,"\n");
        return(-1);
    }
    return(iffile);
}
 
/* read chunks until one of type chunktype is found or EOF
 * note that after a successful call to chunkuntil,
 * skipchunk or readchunk must be performed or the IFF reading
 * software will get lost on the next nextchunk
 * chunksize is returned on success, -1 otherwise
 * The caller should probably check the return explicitly for -1.
 * If checking only for less than zero, chunks larger than
 * two gigabytes will cause your code to break.
 */
 
LONG chunkuntil(fd,chunktype,file_bytes_left)
int fd;
ULONG chunktype;
long *file_bytes_left;
{
    ULONG currentchunk;
    LONG chunksize;
 
    while ((currentchunk = nextchunk(fd,&chunksize,file_bytes_left)) != NULL)
    {
        if (currentchunk == chunktype)
            return(chunksize);
        skipchunk(fd,chunksize,file_bytes_left);
    }
    return(0);
}
 
/* OpenCAT - Open an IFF CAT archive */
 
/* OpenCAT
 * Open an IFF CAT archive, insuring that the file starts with an
 * IFF CAT header and that the length in the header is valid.
 * Return the CAT subtype, file descriptor and length, leaving the
 * file pointed at the start of the first subchunk
 */
 
int OpenCAT(archive_name,subtype_ptr,length_ptr)
char *archive_name;
ULONG *subtype_ptr, *length_ptr;
{
    ChunkHeader mychunkheader;
    int archive_fd;
    long start_of_body, filesize;
    long placeholder;
 
    if ((archive_fd = open(archive_name,O_RDONLY)) == -1)
    {
        /* fprintf(stderr,"Can't open archive '%s'\n",archive_name); */
        return(-1);
    }
 
    if (read(archive_fd,&mychunkheader,sizeof(mychunkheader)) != sizeof
(mychunkheader))
    {
        perror(archive_name);
        fprintf(stderr,"couldn't read chunk header\n");
        return(-1);
    }
 
    if (mychunkheader.ckID != ID_CAT)
    {
        fprintf(stderr,"file '%s' is not an IFF CAT archive\n",archive_name);
        return(-1);
    }
 
    if (read(archive_fd,subtype_ptr,sizeof(subtype_ptr)) != sizeof(subtype_ptr))
    {
        fprintf(stderr,"error reading archive header - subtype\n");
        return(-1);
    }
 
    /* save location of current start of body */
    if ((start_of_body = lseek(archive_fd,0,1)) == -1)
    {
        perror(archive_name);
        return(-1);
    }
 
    /* seek to the end to get the size */
    if ((filesize = lseek(archive_fd,0,2)) == -1)
    {
        perror(archive_name);
        return(-1);
    }
 
    /* see if the shoe fits */
    if ((filesize - sizeof(ChunkHeader)) != mychunkheader.ckSize)
    {
        fprintf(stderr,"archive %s's CAT chunk size does not equal the file's
 size.\n",archive_name);
        fprintf(stderr,"I'm assuming it's blown.\n");
        return(-1);
    }
 
    /* go back to the start of the IFF CAT archive's data */
    if (lseek(archive_fd,start_of_body,0) == -1)
    {
        perror(archive_name);
        return(-1);
    }
 
    /* it worked store filesize in location pointed to by 'length'
     * and return the archive file's file descriptor
     */
    *length_ptr = filesize;
    return(archive_fd);
}
 
/* end of OpenCAT */
 
/* nextcat - read header info for the next entry in an IFF CAT */
 
/* nextCATchunk
 *
 * given fd, read into IFF file.
 * if we're not at a FORM, CAT or LIST, print the chunk type if verbose,
 *    then skip the chunk
 * if we are at a FORM, CAT or LIST, read the subtype and return it
 * via the argument subtype_ptr.
 * if the next chunk within the embedded FORM, CAT or LIST is FNAM,
 * read the text in the FNAM chunk (file name) and write it into space
 * pointed to by argument fname_ptr.
 * return the size of the chunk in argument chunk_length_ptr.
 * update the space left in the metachunk (usually the file) of argument
 * metachunk_length_ptr
 */
 
ULONG nextCATchunk
(fd,subtype_ptr,fname_ptr,chunk_length_ptr,metachunk_length_ptr)
int fd;
ULONG *subtype_ptr;
char *fname_ptr;
LONG *chunk_length_ptr, *metachunk_length_ptr;
{
    ULONG cat_type, chunkid, innerchunkid;
    long chunksize, innerchunkposition, innerchunksize, filesize;
    int odd;
 
    /* null out the returned subtype and fnam */
    *subtype_ptr = 0L;
    *fname_ptr = '\0';
 
    if ((chunkid = nextchunk(fd,chunk_length_ptr,metachunk_length_ptr)) == 0L)
        return(0L);
 
    /* if the chunk type isn't FORM, CAT or LIST, return the chunkid
     */
    if (chunkid != ID_FORM && chunkid != ID_CAT && chunkid != ID_LIST)
        return(chunkid);
 
    /* get the chunk subtype */
    if (read(fd,subtype_ptr,4) != 4)
    {
        perror("reading subtype");
        return(0);
    }
 
    /* reduce chunksize and metachunksize by the size of the subtype */
    *chunk_length_ptr -= sizeof(ULONG);
    *metachunk_length_ptr -= sizeof(ULONG);
 
    /* sneak a peek into the embedded FORM, CAT or LIST to see
     * if the next chunk is an FNAM chunk */
 
     assert(*chunk_length_ptr > 0);
 
    /* fetch the current location in the file - we'll restore it
     * if we don't find this next chunk to be a FNAM one
     */
    innerchunkposition = lseek(fd,0L,1);
 
    /* get the type and size of the inner chunk */
    chunksize = *chunk_length_ptr;
    innerchunkid = nextchunk(fd,&innerchunksize,&chunksize);
 
    /* if it's not an fname chunk, seek back to the start of the
     * chunk and return the chunk id - master length should be OK
     */
    if (innerchunkid != ID_FNAM)
    {
        lseek(fd,innerchunkposition,0);
        return(chunkid);
    }
 
    odd = innerchunksize & 1;
 
    /* read and zero-terminate the file name (contents of FNAM chunk) */
    if (!readchunk(fd,fname_ptr,innerchunksize,&chunksize))
    {
        fprintf(stderr,"nextCATchunk: got into trouble reading chunk text\n");
        return(0);
    }
    *(fname_ptr + innerchunksize) = '\0';
 
    /* update the length of the chunk and its parent &  return the chunk id
     * (nextchunk normally handles updating the length but we used different
     * variables to make restoring (in case we don't find an FNAM chunk)
     * easier
     */
    *chunk_length_ptr -= (sizeof(ChunkHeader) + innerchunksize);
    *metachunk_length_ptr -= (sizeof(ChunkHeader) + innerchunksize);
    if (odd)
    {
        (*chunk_length_ptr)--;
        (*metachunk_length_ptr)--;
    }
    return(chunkid);
}
 
/* end of nextCATchunk */
 
/* end of iff.c */
SHAR_EOF
cat << \SHAR_EOF > iff.h
#ifndef IFF_H
#define IFF_H
/*
   IFF.H  defs for IFF-85 Interchange Format Files.                1/22/86
 
   By Jerry Morrison and Steve Shaw, Electronic Arts.
 
   Mods and stripping of a lot of stuff by Karl Lehenbauer, 1987, 1988
 
   This software is in the public domain.
*/
 
#ifndef LIBRARIES_DOS_H
#include "libraries/dos.h"
#endif
 
typedef LONG ID;
 
/* Four-character IDentifier builder.*/
#define MakeID(a,b,c,d)  ( (LONG)(a)<<24L | (LONG)(b)<<16L | (c)<<8 | (d) )
 
/* Standard group IDs.  A chunk with one of these IDs contains a
   SubTypeID followed by zero or more chunks.*/
#define ID_FORM MakeID('F','O','R','M')
#define ID_PROP MakeID('P','R','O','P')
#define ID_LIST MakeID('L','I','S','T')
#define ID_CAT  MakeID('C','A','T',' ')
#define ID_FILLER MakeID(' ',' ',' ',' ')
#define ID_FNAM MakeID('F','N','A','M')
#define ID_MISC MakeID('M','I','S','C')
 
/* The IDs "FOR1".."FOR9", "LIS1".."LIS9", & "CAT1".."CAT9" are reserved
 * for future standardization.*/
 
/* ---------- Chunk ----------------------------------------------------*/
 
/* All chunks start with a type ID and a count of the data bytes that
   follow--the chunk's "logicl size" or "data size". If that number is odd,
   a 0 pad byte is written, too. */
 
typedef struct {
    ID      ckID;
    LONG  ckSize;
    } ChunkHeader;
 
typedef struct {
    ID      ckID;
    LONG  ckSize;
    UBYTE ckData[ 1 /*REALLY: ckSize*/ ];
    } Chunk;
 
/* define the maximum reasonable chunk size - the real max is around 2^32,
 * but we need some reasonability limit to check for */
#define MAXCHUNKSIZE 900000
 
/* The Grouping chunks (LIST, FORM, PROP, & CAT) contain concatenations of
 * chunks after a subtype ID that identifies the content chunks.
 * "FORM type XXXX", "LIST of FORM type XXXX", "PROPerties associated
 * with FORM type XXXX", or "conCATenation of XXXX".*/
typedef struct {
    ID      ckID;
    LONG  ckSize;    /* this ckSize includes "grpSubID".*/
    ID    grpSubID;
    } GroupHeader;
 
typedef struct {
    ID      ckID;
    LONG  ckSize;
    ID    grpSubID;
    UBYTE grpData[ 1 /*REALLY: ckSize-sizeof(grpSubID)*/ ];
    } GroupChunk;
 
 
#endif IFF_H
 
SHAR_EOF
cat << \SHAR_EOF > includes.c
/* iffar - archiver includes.c - for Manx +H +I options to precompile includes
 */
 
/* iffar - IFF CAT archiver includes.c for include precompile
 
   By Karl Lehenbauer, version 1.2, release date 5/9/88.
   This code is released to the public domain.
   See the README file for more information.
 
*/
 
#include <exec/types.h>
#include <exec/memory.h>
#include <exec/nodes.h>
#include <exec/lists.h>
/* #include <devices/audio.h> */
#include <fcntl.h>
#include <stdio.h>
#include "assert.h"
#include "iff.h"
 
/* end of includes.c */
SHAR_EOF
cat << \SHAR_EOF > main.c
/* iffar - IFF CAT archiver, main()
 
   By Karl Lehenbauer, version 1.2, release date 5/9/88.
   This code is released to the public domain.
   See the README file for more information.
 
*/
 
/* iffar - iff 'cat' archiver, 3/26/88 */
 
#include <stdio.h>
#include "assert.h"
 
/* options */
int verbose = 0;
int insert_after = 0;
int insert_before = 0;
int suppress_creation_message = 0;
 
/* if insert_after or insert_before is set, this global has the relevant
 * name */
char *location_modifier_name;
 
/* variables */
int number_of_arguments;    /* number of args to the program, a global */
 
/* this'll point to the command line's command char string */
char *cmdkeys;
 
/* this'll be the name of the archive file */
char *archive_name;
 
/* this'll contain the command */
char command_key;
 
/* if a command must have some names as arguments, must_have_names can be
 * called to insure that arguments were supplied.
 * Note that what constitutes the presence of arguments varies whether
 * a positional modifier ('i', 'b' or 'a') was specified.
 */
must_have_names()
{
    if ((insert_before || insert_after) && (number_of_arguments < 5))
    {
        fprintf(stderr,"a positioning element and at least one other element
 must be specified for\n");
        fprintf(stderr,"the combination of options you have requested.\n");
        usage();
    }
    if (number_of_arguments < 4)
    {
        fprintf(stderr,"at least one archive element must be specified for this
 option\n");
        usage();
    }
}
 
header()
{
    fprintf(stderr,"iffar - public domain IFF CAT archiver, Version 1.1, by Karl
 Lehenbauer\n\n");
    fprintf(stderr,"This program maintains archives of IFF FORM, CAT and LIST
 files\n");
    fprintf(stderr,"in a manner that complies with the IFF CAT
 specification.\n");
}
 
/* usage  print usage text and exit */
usage()
{
    fprintf(stderr,"\nUsage: iffar key [posname] afile name ...\n\n");
    fprintf(stderr,"key can be one of the following:\n");
    fprintf(stderr,"\td\tdelete\n\tr\treplace\n\tq\tquick append\n");
    fprintf(stderr,"\tt\ttable of contents\n\tx\textract\n");
    fprintf(stderr,"and zero or more of the following options:\n");
    fprintf(stderr,"\tv\tverbose\n\ta\tafter\n\ti,b\tbefore\n");
    fprintf(stderr,"\tc\tsuppress creation message\n");
    cleanup();
    exit(1);
}
 
main(argc,argv)
int argc;
char *argv[];
{
    int i, j;
    int archive_fd, files;
    char *nameptr;
    char **entryname_pointers;
    int entrycount;
 
    number_of_arguments = argc;
 
    if (argc < 3)
    {
        header();
        usage();
    }
 
    /* assign nice variable names to various command line arguments */
    cmdkeys = argv[1];
    command_key = *cmdkeys;
    archive_name = argv[2];
    location_modifier_name = (char *)NULL;
    entryname_pointers = &argv[3];
    entrycount = argc - 3;
 
    /* figure out any modifiers specified to the main function requested */
    for (i = 1; i < strlen(cmdkeys); i++)
    {
        switch(cmdkeys[i])
        {
            case 'v':                /* verbose selected, set verbose flag */
                verbose = 1;
                break;
 
            /* 'after' option, make sure they've selected 'r' or 'm' as
             * the main command.  Also, make sure they haven't already
             * chosen the insert_before option.  After that, we're OK,
             * so twiddle parameters and break
             */
            case 'a':
                if (command_key != 'r' && command_key != 'm')
                {
                    fprintf(stderr,"i, b and a modifiers are only good for r and
 m options\n");
                    usage();
                }
                if (insert_before)
                {
                    fprintf(stderr,"you can't select 'insert before' and 'insert
 after'\n");
                    usage();
                }
                insert_after = 1;
                location_modifier_name = argv[3];
                entryname_pointers = &argv[4];
                entrycount = argc - 4;
                break;
 
            /* 'insert before' option, make sure they've selected 'r' or
             * 'm' as the main command.  Also, make sure they haven't already
             * chosen the insert_after option.  After that, we're OK,
             * so twiddle parameters and break
             */
            case 'i':                /* insert; before */
            case 'b':
                if (command_key != 'r' && command_key != 'm')
                {
                    fprintf(stderr,"i, b and a modifiers are only good for r and
 m options\n");
                    usage();
                }
                if (insert_after)
                {
                    fprintf(stderr,"you can't select 'insert before' and 'insert
 after'\n");
                    usage();
                }
                insert_before = 1;
                location_modifier_name = argv[3];
                entryname_pointers = &argv[4];
                entrycount = argc - 4;
                break;
 
            case 'c':                /* supress creation message */
                suppress_creation_message = 1;
                break;
 
            default:                /* unrecognized option */
                fprintf(stderr,"option '%c' unrecognized\n",cmdkeys[i]);
                usage();
        }
    }
 
    /* now execute the major command */
 
    switch(command_key)
    {
        case 'd':        /* delete */
            must_have_names();
            delete_entries(archive_name,entryname_pointers,entrycount);
            break;
 
        /* replace - if the archive doesn't exist, fall through to
         * quick append */
        case 'r':
            must_have_names();
            if ((archive_fd = open(archive_name,O_RDONLY)) != -1)
            {
                close(archive_fd);
                replace_entries(archive_name,entryname_pointers,entrycount);
                break;
            }
        case 'q':        /* quick append */
            must_have_names();
            checknew(archive_name);
            if ((archive_fd = open_quick_append(archive_name)) == -1)
                break;
            quickappend_entries(archive_fd,entryname_pointers,entrycount);
            break;
        case 't':        /* table of contents */
            table_of_contents(archive_name);
            break;
 
        case 'm':        /* move */
            fprintf(stderr,"move option not implemented\n");
            must_have_names();
            break;
        case 'x':        /* extract */
            extract(archive_name,entryname_pointers,entrycount);
            break;
 
        default:
            fprintf(stderr,"requested command (%c) is invalid\n",command_key);
            usage();
    }
    cleanup();
    exit(0);
}
 
/* end of main.c */
SHAR_EOF
cat << \SHAR_EOF > misc.c
/* iffar - IFF CAT archiver miscellaneous functions
 
   By Karl Lehenbauer, version 1.2, release date 5/9/88.
   This code is released to the public domain.
   See the README file for more information.
 
*/
 
#include <ctype.h>
 
/* strnicmp - case-insensitive strncmp, not provided by manx, this one
 * is like lattice's */
char *strnicmp(s1, s2, len)
char *s1, *s2;
int len;
{
    char c1, c2;
 
    c1 = *s1;
    c2 = *s2;
    while (len-->0 && *s1)
    {
        if (isupper(*s1))
            c1 = tolower(*s1);
        else
            c1 = *s1;
        s1++;
        if (isupper(*s2))
            c2 = tolower(*s2);
        else
            c2 = *s2;
        s2++;
        if (c1 != c2)
            break;
    }
    return c1 - c2;
}
 
/* return the base portion of a file name - needs to be hacked in C style BWTF*/
 
char *basename(fname)
char *fname;
{
    char *basename_ptr;
    int i;
    int fnamelen = strlen(fname);
 
    basename_ptr = fname;
    for (i = fnamelen - 1; i > 0; i--)
    {
        if (fname[i] == '/' || fname[i] == ':')
        {
            basename_ptr = &fname[i+1];
            break;
        }
    }
    return(basename_ptr);
}
 
/* end of misc.c */
SHAR_EOF
cat << \SHAR_EOF > quickappend.c
/* iffar - IFF CAT archiver quick append functions
 
   By Karl Lehenbauer, version 1.2, release date 5/9/88.
   This code is released to the public domain.
   See the README file for more information.
 
*/
 
#include <stdio.h>
#include "assert.h"
 
extern int verbose;
 
/* append a list of files specified by an array of pointers to
 * names, the number of entries in the array, and a file decscriptor
 * to write them to
 */
quickappend_entries(archive_fd,entryname_pointers,entrycount)
int archive_fd;
char *entryname_pointers[];
int entrycount;
{
    int files;
    char *nameptr;
 
    /* for all names specified on the command line */
    for (files = 0; files < entrycount; files++)
    {
        /* scdir will return nonnull values as long as the pattern
         * matches files (it will return patternless names the first
         * time and NULL the second
         */
        while (nameptr = scdir(entryname_pointers[files]))
        {
            /* append the file */
            if (append_file_to_archive(nameptr,archive_fd))
            {
                if (verbose)
                    fprintf(stderr,"appended %s\n",nameptr);
            }
        }
    }
    if (!rewrite_archive_header(archive_fd))
    {
        fprintf(stderr,"rewrite of archive header failed... archive is presumed
 blown\n");
    }
    close(archive_fd);
}
 
/* end of quickappend.c */
SHAR_EOF
cat << \SHAR_EOF > replace.c
/* iffar - IFF CAT archiver replace functions
 
   By Karl Lehenbauer, version 1.2, release date 5/9/88.
   This code is released to the public domain.
   See the README file for more information.
 
*/
 
#include <exec/types.h>
#include <exec/memory.h>
#include <stdio.h>
#include <fcntl.h>
#include "assert.h"
#include "iff.h"
 
/* if a "insert before" or "insert after" position modifier is
 * not specified, the program attepts to replace entries at the
 * same place as where they occurred in the original archive.
 * if the entry could not be found, the file is appended to the
 * end of the archive.  hence, when a position modifier is not
 * specified and a file being replaced did not exist in the archive,
 * the file needs to be appended onto the
 * end - we do this by nulling out the argument if and only if
 * neiter insert_before or insert_after is set, else a bug would
 * be introduced as when they are selected the delete and insert
 * portions are asynchronous and either could occur before the
 * other, hence one guy nulling it makes the other guy not see
 * it.  it's not a problem for the specified case, though, 'cuz
 * it's only looked at in this one place and at the end,
 * where, again only when neither insert_before and insert_after
 * are checked, we run through the arguments and if the first byte
 * of any entry is not null, this implies that entry was not in
 * the original archive, hence not replaced in-place, so we tack
 * it on to the end.
 * if insert_before or insert_after is selected, archive entries
 * are deleted on the fly as they are read from the original
 * archive by being skipped and, when the insert conditions are
 * met, all of the names in the list of names are appended, thus,
 * we don't have to worry about it
 *
 * note that it's kind of broken for insert_after and insert_before
 * when the after or before name isn't found the old entries will
 * be deleted but the new ones won't be inserted, heck, the program
 * may even blow up
 */
 
extern ULONG nextchunk();
 
extern int insert_before;
extern int insert_after;
extern char *location_modifier_name;
 
int replace_entries(archive_name,fnames,nfiles)
char *archive_name;
char *fnames[];
int nfiles;
{
    int old_archive_fd, new_archive_fd;
    ULONG cat_type, chunkid, innerchunkid, subtype;
    long chunksize, innerchunksize, filesize;
    char textbuf[128], old_archive_name[128];
    int i, replace_file, file_bytes;
    int entryindex, insert_next_time = 0;
    int modifier_matches, did_insert = 0;
 
    extern int verbose;
 
    /* rename the archive to its old name concatenated with ".old"
     */
    sprintf(old_archive_name,"%s.old",archive_name);
    unlink(old_archive_name);
    rename(archive_name,old_archive_name);
 
    if ((old_archive_fd = OpenCAT(old_archive_name,&cat_type,&filesize)) == -1)
    {
        fprintf(stderr,"Can't open archive '%s'\n",old_archive_name);
        return(0);
    }
 
    if ((new_archive_fd = create_archive(archive_name,ID_MISC)) < 0)
        return(0);
 
    while ((chunkid = nextCATchunk
(old_archive_fd,&subtype,&textbuf[0],&chunksize,&filesize)) != 0L)
    {
        /* if the chunk type isn't FORM, CAT or LIST, copy it across
         * without looking at it */
        if (chunkid != ID_FORM && chunkid != ID_CAT && chunkid != ID_LIST)
        {
             if (!WriteChunkHeader(new_archive_fd,chunkid,chunksize))
                return(0);
            copychunkbytes(old_archive_fd,new_archive_fd,chunksize,&filesize);
            break;
        }
 
        /* we shouldn't ever not get a filename back here at the top level
         * that is, we saw a CAT, LIST or FORM, we expect a FNAM chunk*/
         if (textbuf[0] == '\0')
         {
             fprintf(stderr,"FORM, CAT or LIST in archive doesn't have an FNAM
 chunk, abandoning\n");
            return(0);
        }
 
        if (insert_before || insert_after)
            modifier_matches = !strnicmp(location_modifier_name,textbuf,128);
 
        /* if insert_before option has been selected and the chunk ID
         * we just read from the old archive matches global
         * location_modifier_name, append all the named files to the
         * new archive.  also do this if it's insert_after and
         * we matched the name last time */
        if ((modifier_matches && insert_before) || insert_next_time)
        {
            insert_next_time = 0;
 
            for (entryindex = 0; entryindex < nfiles; entryindex++)
            {
                if (verbose)
                {
                    if (insert_before)
                        fprintf(stderr,"inserting ");
                    else
                        fprintf(stderr,"appending ");
                    fprintf(stderr,"%s\n",fnames[entryindex]);
                }
                append_file_to_archive(fnames[entryindex],new_archive_fd);
            }
 
            did_insert = 1;
        }
 
        /* if this is a match and insert_after is selected, set
         * insert_next_time so we'll know to do the appends after the
         * next entry */
        if (modifier_matches && insert_after)
            insert_next_time = 1;
 
        /* search to see if this chunk's name is one specified in fnames,
         * an array of pointer to char strings */
        replace_file = 0;
        for (i = 0; i < nfiles; i++)
        {
            if (!strnicmp(basename(fnames[i]),textbuf,128))
            {
                /* it is */
                replace_file = 1;
                break;
            }
        }
 
        /* if we want to replace it, */
        if (replace_file)
        {
            /* copy the file being replaced into the archive if
             * neither insert_before or insert_after are set.
             * if they are set, this will be done elsewhere */
            if (!insert_before && !insert_after)
            {
                if (verbose)
                    fprintf(stderr,"replacing %s\n",textbuf);
                    append_file_to_archive(fnames[i],new_archive_fd);
                    /* null out the first byte of the name so we won't
                     * append it again at the end */
                    *fnames[i] = '\0';
            }
            else if (verbose)
                fprintf(stderr,"removing old %s\n",textbuf);
 
            /* in either case (replace selected, we don't care if
             * before or after are chosen), skip the chunk in the
             * old archive */
            if (!skipchunk(old_archive_fd,chunksize,&filesize))
            {
                fprintf(stderr,"replace: skipchunk failed\n");
                return(0);
            }
        }
        else    /* not on the replacement list, we copy it */
        {
            if (!WriteCATentry
(new_archive_fd,textbuf,chunkid,subtype,chunksize))
                return(0);
 
            copychunkbytes(old_archive_fd,new_archive_fd,chunksize,&filesize);
        }
    }
 
    /* now if insert_before or insert_after were not set, for all entries
     * in the list that don't have a null byte for their first byte,
     * append them to the archive */
    if ((!insert_before && !insert_after) || !did_insert)
    {
        /* if we didn't do the insert, report that we're planning
         * to append.  note that if insert_after and !insert_next_time
         * it means that they said append after the last one, so
         * don't mention it
         */
        if (insert_before || (insert_after && !insert_next_time))
            fprintf(stderr,"couldn't find entry %s that was specified as a
 position modifier\n, appending your entries\n",textbuf);
 
        for (i = 0; i < nfiles; i++)
        {
            if (*fnames[i] != '\0')
            {
                if (verbose)
                    fprintf(stderr,"appending %s\n",fnames[i]);
                append_file_to_archive(fnames[i],new_archive_fd);
            }
        }
    }
 
    /* write the right length in for the header */
    rewrite_archive_header(new_archive_fd);
 
    /* close the old and new archive files and return success */
    close(old_archive_fd);
    close(new_archive_fd);
    return(1);
}
 
/* end of extract.c */
 
SHAR_EOF
cat << \SHAR_EOF > toc.c
/* iffar - IFF CAT archiver table of contents functions
 
   By Karl Lehenbauer, version 1.2, release date 5/9/88.
   This code is released to the public domain.
   See the README file for more information.
 
*/
 
#include <exec/types.h>
#include <exec/memory.h>
#include <stdio.h>
#include <fcntl.h>
#include "assert.h"
#include "iff.h"
 
#define ID_NAME MakeID('N','A','M','E')
#define ID_AUTH MakeID('A','U','T','H')
#define ID_ANNO MakeID('A','N','N','O')
#define ID_Copyright MakeID('(','c',')',' ')
 
extern long lseek();
 
extern ULONG nextchunk();
 
int table_of_contents(fname)
char *fname;
{
    int fd;
    ULONG cat_type, chunkid, innerchunkid, subtype;
    long chunksize, innerchunksize, filesize;
    char namebuf[256];
 
    extern int verbose;
 
    if ((fd = OpenCAT(fname,&cat_type,&filesize)) == -1)
    {
        fprintf(stderr,"Can't open archive '%s'\n",fname);
        return(0);
    }
 
    if (verbose)
    {
        fprintf(stderr,"CAT subtype is ");
        PutID(cat_type);
        fprintf(stderr,"\n");
    }
 
    while ((chunkid = nextCATchunk
(fd,&subtype,&namebuf[0],&chunksize,&filesize)) != 0L)
    {
        /* if the chunk type isn't FORM, CAT or LIST, skip it */
         if (chunkid != ID_FORM && chunkid != ID_CAT && chunkid != ID_LIST)
         {
             if (verbose)
            {
                PutID(chunkid);
                fprintf(stderr," chunk is being skipped\n");
            }
            skipchunk(fd,chunksize,&filesize);
            goto bigchunkdone;
        }
 
        if (namebuf[0] == '\0')
        {
            fprintf(stderr,"CAT entry didn't contain a FNAM chunk -
 skipping\n");
            skipchunk(fd,chunksize,&filesize);
            goto bigchunkdone;
        }
 
        PutID(chunkid);
        fprintf(stderr," ");
        /* print out the chunk length */
        PutID(subtype);
        fprintf(stderr," %6ld  %s\n",chunksize, namebuf);
 
        /* if verbose isn't selected, skip the rest of the chunks in the
         * embedded FORM, CAT or LIST
         */
        if (!verbose)
        {
             skipchunk(fd,chunksize,&filesize);
            goto bigchunkdone;
        }
 
        /* else verbose is selected, dive down into the embedded FORM,
         * CAT or LIST and print chunks found there and their contents
         * when they're known to be text.
         */
 
        /* follow a good neighbor policy by reducing our (the parent's)
         * chunk size by the size we've found - since we'll be moving
         * through the embedded FORM effectively recursively; that is,
         * we'll be using the same nextchunk, skipchunk, readchunk
         * routines on the chunk nextchunk got for us, we'll
         * decrease parent chunk size by the size we've found,
         * as, when calling nextchunk, skipchunk and readchunk below,
         * we'll be concerned with keeping track of the form's chunk length
         * as a virtual EOF, rather than the whole CAT's as above
         */
        filesize -= chunksize;
        if (chunksize & 1)
            filesize--;
 
        while (chunksize != 0)
        {
            /* get the type and size of the inner chunk */
            innerchunkid = nextchunk(fd,&innerchunksize,&chunksize);
            if (chunksize == 0)
                break;
 
 
            /* print some presumably useful information */
            fprintf(stderr,"   ");
            PutID(innerchunkid);
 
            switch(innerchunkid)
            {
                case ID_FNAM:
                    panic("more than one FNAM chunk in an embedded FORM");
 
                case ID_NAME:
                case ID_AUTH:
                case ID_ANNO:
                case ID_Copyright:
                        if (innerchunksize >= sizeof(namebuf))
                        {
                            fprintf(stderr,"[too long]\n");
                            skipchunk(fd,innerchunksize,&chunksize);
                        }
                        else
                        {
                            if (!readchunk
(fd,namebuf,innerchunksize,&chunksize))
                            {
                                fprintf(stderr,"got into trouble reading chunk
 text\n");
                                return(0);
                            }
                            namebuf[innerchunksize] = '\0';
                            fprintf(stderr,", %s\n",namebuf);
                        }
                    break;
 
                default:
                    fprintf(stderr,", size %ld\n",innerchunksize);
 
                    /* skip the body of the subchunk */
                    skipchunk(fd,innerchunksize,&chunksize);
                }
        }
        /* we make it to here if the length of all the subchunks equalled
         * the length of their superchunk
         */
         bigchunkdone: ;
    }
    close(fd);
}
 
/* end of toc.c */
SHAR_EOF
#    End of shell archive
exit 0
