[alt.sources.amiga] iffar - IFF CAT archiver, part 1 of 2

karl@sugar.hackercorp.com (Karl Lehenbauer) (10/07/89)

:
#! /bin/sh
# This is a shell archive,
# created by Karl Lehenbauer on Fri Oct  6 12:34:41 1989
# Remove anything before the "#! /bin/sh" line, then unpack it by saving
# it into a file and typing "sh file".  If you do not have sh, you need 
# unshar, a dearchiving program which is widely available.  In the absolute
# wost case, you can crack the files out by hand.
# If the archive is complete, you will see the message "End of archive."
# at the end.
# This archive contains the following files...
#    'README'
#    'Makefile'
#    'iff.h'
#    'assert.h'
#    'cleanup.c'
#    'create.c'
#    'delete.c'
#    'extract.c'
#    'iff.c'
#    'includes.c'
#    'main.c'
#
# To extract them, run the following through /bin/sh
echo x - README
sed 's/^X//' > README << '//END_OF_FILE'
Xiffar - IFF CAT Archiver, Release 1.4, 9/23/89
X
XWritten by: 
X
X	Karl Lehenbauer/Hackercorp
X	3918 Panorama
X	Missouri City, TX  77459
X
X	Usenet:  ..uunet!sugar!karl
X	Internet: karl@sugar.hackercorp.com
X	BIX: kelehen
X
XAll liability is disclaimed!  This code is free.  We do not have a contract.
X
XThis software, source and binary, is released to the public domain, 5/9/88.
X
XI ask that you retain my name as the original author in the source and
Xdocumentation if you redistribute this and that I be credited in the user 
Xmanual if iffar is redistributed with a commercial product.
X
XSubsequent updates are also released to the public domain.
X
XRegards,  Karl Lehenbauer @ The Hacker's Haven, Missouri City, Texas, 7/31/89
X
X
X---------------------------------------------------------------------
X
XNAME
X
X	iffar - IFF CAT archiver
X
XSYNOPSIS
X
X	iffar option [posname] archive_file [IFF_file] ...
X
XDESCRIPTION
X
X	Iffar maintains archives of Interchange File Format (IFF) FORM and CAT 
X	files in a manner  that complies with the IFF CAT specification.
X
X	IFF CAT archives should be portable to different machines.  No promises.
X
X	The option string must start with 'd', 'q', 'r', 't' or 'x' and may
X	additionally have modifiers of 'a', 'b', 'c', 'i' or 'v'.  Not all
X	modifiers are valid with all options.
X
X	The options are:
X
X		'd'		Delete named IFF files from the CAT archive.
X
X		'q'		Quickly append named IFF files to the CAT archive.
X
X		'r'		Replace named IFF files in the CAT archive; append new ones.
X
X		't'		Print a table of contents of the CAT archive.
X
X		'x'		Extract named IFF files from the CAT archive.
X				If no names are specified, all files are extracted.
X
X
X	The modifiers are:
X		'a'		"after", replace or append IFF files after entry in CAT
X					archive named by posname 
X
X		'b'		"before", replace or append IFF files before entry in CAT
X					archive named by posname
X
X		'c'		Do not print a message indicating archive is being created
X					when it must be created.
X
X		'i'		a synonym for "before"
X
X		'v'		print verbose description of all activity
X				For table of contents, prints IDs, and
X				lengths of chunks within the IFF file chunks
X				in the CAT archive.  It prints the contents of
X				chunks that it knows to be textual and short.
X
X
X	On all operations that modify the archive, except for "quick append",
X	the archive will be renamed with a ".old" extension and a new archive
X	will be created.
X
X	The "quick append" option causes the named files to be added to the
X	end of the archive without rewriting the archive or looking to see
X	if the entry already exists.  This is to avoid quadratic behavior
X	when building up an archive one or just a few entries at a time.
X
X	Wildcards of the semi-Unix-type as supplied by Manx are provided.
X
X	Only the basename of the specified IFF filenames will be used for
X	element names within the archive.  In other words, pathnames are
X	stripped from filenames to create the archive element name.
X	This is also true for extracts.  
X	
XEXAMPLES
X
X	iffar x foo ram:t/bar
X
X		would extract element "bar" from archive "foo" into file 
X		"ram:t/bar"
X		
X	iffar ra sounds Cabasa dh0:sounds/Snare
X	
X		would replace a FORM, CAT or LIST named Snare in the 
X		archive with dh0:sounds/Snare, placing Snare directly 
X		after Cabasa.  If Cabasa is not found in the archive,
X		Snare is placed at the end.
X
XBUGS
X
X	The archive will be corrupted if a write error (including
X	running out of disk space) occurs during "quick 
X	append" mode and there won't be a ".old" backup file left
X	either.
X
X	Running out of disk space leaves corrupted archives.
X	For all options but "q", the ".old" backup file will contain
X	the archive in its state prior to the run that blew it up.
X	The program should delete the corrupted archive and restore 
X	the ".old" file, but it doesn't.
X
X	A user contacted me after grabbing the 1.2 version and insisted 
X	that it is illegal under the IFF spec to add chunks to FORMs that 
X	your program doesn't understand.  If you agree with this, iffar 
X	must be considered broken because it adds a chunk that it
X	uses to identify the embedded FORM or CAT.  iffar strips this
X	chunk when extracting a file, however, but there were conflicts
X	when FORMs already contained FNAM chunks.  
X	    I have given a lot of thought to this and I believe from a 
X	careful reading of the spec that it is legal to add chunks to FORMs 
X	your program doesn't understand.  I have renamed iffar's chunk ID 
X	from FNAM to IFAR which should vastly reduce the possibility of a 
X	conflict.
X		I would like to rewrite iffar to not embed the ID chunk within
X	the form but rather to keep it outside the form, though still in 
X	an IFF manner, which would solve the problem. This would reduce the 
X	complexity of the program  and increase its performance.  However, 
X	it's  a lot of work and the archiver is useful (to me at least) as 
X	is, hence the 1.3 version is a spruce-up rather than a rewrite.
X
X	iffar doesn't archive IFF LIST files properly.  They're rare.
X
X
XIMPLEMENTATION NOTES
X
X	Iffar is written to run under Manx Aztec C 3.6a for the Amiga
X	under AmigaDOS version 1.3.
X
X	My IFF archiving routines were written to be well-behaved from an 
X	IFF point of view; that is, they try to respect the virtual 
X	end-of-file defined by the size field of a chunk's header when 
X	diving through the chunk's subchunks.  It makes for more work to use
X	the routines, but adds some certainty that the program is working
X	properly.  I don't know.  The IFF spec requests that we do, so I do.
X
X	Note that when converting your code to use a CAT file instead of
X	reading several FORM files (it's about twice as fast for an
X	application of mine involving about 25 files from two to ten
X	kilobytes each, reading them in from floppy.), your code needs 
X	to either be driven by the files read from the CAT (you search 
X	a list of names you're looking for when you see a FORM and load 
X	what you want) or your code has to know what's coming from the 
X	CAT specifically.  I'd prefer that you do the former, of course.
X	The table of contents routines (toc.c) provide a reasonable template 
X	for your CAT-reading application.
X
XNOTES FOR THOSE WISHING TO PORT THE ARCHIVER
X
X	Knock yourselves out.  The code is written using standard C library
X	calls, with the exception of calls to "scdir", which Manx uses as
X	a means of expanding wildcards.  If you get it to work on a different
X	system, such as Unix System V, please send me a copy, preferably
X	with #ifdefs so the Manx stuff still works.  Ultimately, I think an
X	alternative "_main" startup routine should be written for the Amiga
X	that expands wildcards inline to provide an argc and argv as they
X	would look on Unix after the shell had expanded all the wildcards.
X
X
XREVISION HISTORY
X
X4/25/88, version 1.1
X
X	Initial version - never distributed, as far as I know
X
X5/9/88, version 1.2
X
X	Made 'r' (replace) option create the archive if it isn't there.
X
X	Fixed bug that caused the IFF reader to get lost when doing
X	  a verbose table of contents on certain archives.
X
X	Flattened the source directory structure.
X
X	Updated the documentation.
X
X7/31/89, version 1.3
X
X	Changed the chunk type iffar uses as the name for embedded FORMs and 
X	CATs from FNAM to IFAR to reduce conflict with certain FORMs.
X
X	Updated the documentation.
X
X	Compiled and tested under AmigaDOS 1.3.
X
X
X9/23/89, version 1.4
X
X	Fixed bug where some archive entry names would be truncated
X
X
X-----------------------------------------------------------------------------
//END_OF_FILE
echo x - Makefile
sed 's/^X//' > Makefile << '//END_OF_FILE'
X# iffar - IFF CAT archiver, makefile
X#
X#  By Karl Lehenbauer, version 1.3, release date 7/31/89.
X#  This code is released to the public domain.
X#  See the README file for more information.
X#
X#  This makefile is for Manx Aztec C 3.6a for the Commodore Amiga computer.
X#  It probably works under 3.4, but I haven't tried it.
X#
X
XCFLAGS=	+p +Iincludes.pre
X#CFLAGS=	-n +p -DDEBUG +Iincludes.pre
X
XSDBFLAGS=
X#SDBFLAGS= -g
X
XHFILES= assert.h iff.h
X
XOFILES= main.o cleanup.o create.o toc.o iff.o \
X	extract.o delete.o replace.o quickappend.o misc.o
X
X.c.o:
X	cc $(CFLAGS) $*.c
X
Xall:	iffar
X	say "ready"
X
Xclean:
X	-delete #?.o quiet
X	-delete #?.bak quiet
X	-delete includes.pre quiet
X
Xscratch:	clean	all
X
Xiffar:	includes.pre $(OFILES)
X	ln $(SDBFLAGS) +q -o iffar $(OFILES) -lcl32
X
Xincludes.pre:	$(HFILES) includes.c
X	cc +p +Hincludes.pre includes.c
X
X
//END_OF_FILE
echo x - iff.h
sed 's/^X//' > iff.h << '//END_OF_FILE'
X#ifndef IFF_H
X#define IFF_H
X/*
X   IFF.H  defs for IFF-85 Interchange Format Files.	        	1/22/86
X
X   By Jerry Morrison and Steve Shaw, Electronic Arts.
X
X   Mods and stripping of a lot of stuff by Karl Lehenbauer, 1987, 1988
X
X   This software is in the public domain.
X*/
X
X#ifndef LIBRARIES_DOS_H
X#include "libraries/dos.h"
X#endif
X
Xtypedef LONG ID;
X
X/* Four-character IDentifier builder.*/
X#define MakeID(a,b,c,d)  ( (LONG)(a)<<24L | (LONG)(b)<<16L | (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 ID_FORM MakeID('F','O','R','M')
X#define ID_PROP MakeID('P','R','O','P')
X#define ID_LIST MakeID('L','I','S','T')
X#define ID_CAT  MakeID('C','A','T',' ')
X#define ID_FILLER MakeID(' ',' ',' ',' ')
X#define ID_IFAR MakeID('I','F','A','R')
X#define ID_MISC MakeID('M','I','S','C')
X
X/* The IDs "FOR1".."FOR9", "LIS1".."LIS9", & "CAT1".."CAT9" are reserved
X * for future standardization.*/
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 "logicl size" or "data size". If that number is odd,
X   a 0 pad byte is written, too. */
X
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/* define the maximum reasonable chunk size - the real max is around 2^32,
X * but we need some reasonability limit to check for */
X#define MAXCHUNKSIZE 900000
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#endif IFF_H
X
//END_OF_FILE
echo x - assert.h
sed 's/^X//' > assert.h << '//END_OF_FILE'
X/* hackercorp - hackercore assert file for Amiga
X * kel 12/87 v1.0
X */
X
X#ifndef NODEBUG
X#ifndef stderr
X#include <stdio.h>
X#endif
X#define assert(x) if (!(x)) {fprintf(stderr,"Assertion failed: x, file %s, line %d\n",__FILE__,__LINE__); cleanup(); exit(1);}
X#else
X#define assert(x)
X#endif
//END_OF_FILE
echo x - cleanup.c
sed 's/^X//' > cleanup.c << '//END_OF_FILE'
X/* cleanup - Hackercorp Hackercore, standard cleanup management routines, v1.2
X  
X   This code is released to the public domain, 5/9/88, by Peter da Silva
X   and Karl Lehenbauer
X  
X   Usage:
X  
X  	add_cleanup(cleanup_routine);
X  	void cleanup_routine();
X
X  		Add a routine to the cleanup list.  When cleanup  is called, routines 
X		passed as arguments to add_cleanup will be executed  in the reverse 
X		order in which they were received.  See the source to  the IFF CAT 
X		archiver, iffar, for an example of how to use this.
X
X	void cleanup()
X
X		Execute all the routines passed as arguments to add_cleanup
X  	
X	panic(s)
X	char *s;
X
X		Abort the program by printing a panic message including string "s"
X		on stderr, flushing stdout and stderr, calling cleanup and exiting.
X
X	_abort()
X		By defining _abort to call panic, entering control-C while using
X		stdio will cause the program to abort, executing your cleanup
X		routines.  Also note that Manx sdb calls _abort when the user
X		requests an exit, so your cleanup routines will be executed then
X		as well.
X*/
X
X#include <exec/memory.h>
X#include <stdio.h>
X
Xstruct _clean 
X{
X	int (*function)();
X	struct _clean *next;
X} *cleanlist = NULL;
X
X/* add_cleanup
X * given a function, add it to a list of functions to call when cleanup
X * is executed
X */
Xadd_cleanup(function)
Xint (*function)();
X{
X	struct _clean *ptr;
X
X	ptr = AllocMem(sizeof(struct _clean), MEMF_PUBLIC);
X	if(!ptr)
X		return 0;
X	ptr->function = function;
X	ptr->next = cleanlist;
X	cleanlist = ptr;
X}
X
X/* cleanup
X * call all the functions that were passed as arguments to add_cleanup
X * this run
X */
Xcleanup()
X{
X	struct _clean *ptr;
X	int (*f)();
X
X	while(cleanlist) 
X	{
X		/* locate the next cleanup function and get the function pointer */
X		ptr = cleanlist;
X		cleanlist = cleanlist->next;
X		f = ptr->function;
X
X		/* cleanup must clean up after itself */
X		FreeMem(ptr, sizeof(struct _clean));
X
X		/* execute the function */
X		(*f)();
X	}
X}
X
X/* panic - abort with an error message */
X
Xshort panic_in_progress = 0;
X
Xpanic(s)
Xchar *s;
X{
X	fflush(stdout);
X	fprintf(stderr,"panic: %s\n",s);
X	fflush(stderr);
X	if (!panic_in_progress)
X	{
X		cleanup();
X		exit(10);
X	}
X	fprintf(stderr,"double panic!\n");
X	exit(11);
X}
X
X_abort()
X{
X	panic("^C or other C library abort");
X}
//END_OF_FILE
echo x - create.c
sed 's/^X//' > create.c << '//END_OF_FILE'
X/* iffar - IFF CAT archiver output and file copy rouines
X
X   By Karl Lehenbauer, version 1.2, release date 5/9/88.
X   This code is released to the public domain.
X   See the README file for more information.
X
X*/
X
X/* culled from general purpose IFF file cracking routines for Karl's 
X * Audio Stuff by Karl Lehenbauer, based originally on public domain IFF 
X * code from Electronic Arts, 2/24/88
X */
X
X#include <exec/types.h>
X#include <exec/memory.h>
X#include <stdio.h>
X#include <fcntl.h>
X#include "assert.h"
X
X#include "iff.h"
X
Xextern long lseek();
X
Xextern int verbose;
X
Xextern char *basename();
X
X#define ID_MISC MakeID('M','I','S','C')
X
XWriteCATheader(fd)
Xint fd;
X{
X	static ULONG dummy = ID_MISC;
X	WriteChunk(fd,ID_CAT, &dummy, sizeof(dummy));
X}
X
X/* write a chunk header, that's the 4-byte chunk ID and a 4-byte 
X * chunk length 
X */
XWriteChunkHeader(fd,chunktype,length)
Xint fd;
XULONG chunktype;
Xlong length;
X{
X	ChunkHeader chunkheader;
X
X	chunkheader.ckID = chunktype;
X	chunkheader.ckSize = length;
X
X	if (write(fd,&chunkheader,sizeof(chunkheader)) == -1)
X	{
X		perror("WriteChunkHeader");
X		return(0);
X	}
X	return(1);
X}
X
X/* write a chunk that has a four character subtype, like FORM, CAT or LIST
X*/
XWriteSuperChunkHeader(fd,chunktype,subtype,length)
Xint fd;
XULONG chunktype, subtype;
Xlong length;
X{
X	if (!WriteChunkHeader(fd,chunktype,length+sizeof(subtype)))
X		return(0);
X	return(write(fd,&subtype,sizeof(subtype)) != -1);
X}
X
X/* WriteCATentry
X	This routine is given all of the info for a superchunk header and,
X	in addition, a file name.  It writes the chunk header and an
X	IFAR chunk containing the file name out to the fd provided.
X*/
XWriteCATentry(fd,fname,chunktype,subtype,length)
Xint fd;
Xchar *fname;
XULONG chunktype, subtype;
Xlong length;
X{
X	int fnamelen;
X	int calc_chunk_length;
X
X	/* Figure out the length of the file name.  Remember that
X	 * it should be even.  (I should use a cool macro to force
X	 * that, but I don't)
X	 * Add the size of the IFAR chunk we're going to write to our
X	 * calculated chunk length.
X	 */
X	fnamelen = strlen(fname);
X	calc_chunk_length = fnamelen;
X	if (fnamelen & 1)
X		calc_chunk_length++;
X	calc_chunk_length += length + sizeof(ChunkHeader);
X
X	WriteSuperChunkHeader(fd,chunktype,subtype,calc_chunk_length);
X	if (!WriteChunk(fd,ID_IFAR,fname,strlen(fname)))
X		return(0);
X	return(1);
X}
X
X/* write a chunk header and body, that's the 8-byte header and the data
X * to match the specified length
X */
XWriteChunk(fd,chunktype,data,length)
Xint fd;
XULONG chunktype;
Xchar *data;
Xlong length;
X{
X	static char zero = '\0';
X
X	if (!WriteChunkHeader(fd,chunktype,length))
X		return(0);
X
X	/* if there's a body, write it out */
X	if (length != 0)
X		if (write(fd,data,length) == -1)
X		{
X			perror("WriteChunk");
X			return(0);
X		}
X
X	/* if the length is odd, write out an additional zero byte */
X	if (length & 1)
X		if (write(fd,&zero,1) == -1)
X		{
X			perror("WriteChunk");
X			return(0);
X		}
X	return(1);
X}
X
X/* checknew - given fname, this routine prints "Creating IFF CAT archive "
X * and the fname if file fname does not exist
X */
Xchecknew(fname)
Xchar *fname;
X{
X	extern int suppress_creation_message;
X	int fd;
X
X	if (!suppress_creation_message)
X	{
X		if ((fd = open(fname,O_RDONLY)) < 0)
X		{
X			fprintf(stderr,"Creating IFF CAT archive '%s'\n",fname);
X		}
X		else
X			close(fd);
X	}
X}
X
X/* open_quick_append
X * open the archive for append, verifying that it is IFF, subtype CAT,
X * that the length in the header matches the length of the file and
X * such.  Note that this has been wrapped into a better OpenCAT routine
X * but I have not fixed open_quick_append to call it yet.
X */
Xint open_quick_append(fname)
Xchar *fname;
X{
X	ChunkHeader mychunkheader;
X	long filesize;
X	int fd;
X
X	/* If I can't open the archive read only, it doesn't exist, so
X	 * create it
X	 */
X	if ((fd = open(fname,O_RDONLY)) < 0)
X	{
X		if ((fd = create_archive(fname,ID_MISC)) < 0)
X		{
X			perror(fname);
X			return(-1);
X		}
X	}
X	else
X	{
X		/* read the IFF header and validate we've got a CAT */
X		if (read(fd,&mychunkheader,sizeof(mychunkheader)) != sizeof(mychunkheader))
X		{
X			perror(fname);
X			fprintf(stderr,"couldn't read chunk header\n");
X			return(-1);
X		}
X
X		if (mychunkheader.ckID != ID_CAT)
X		{
X			fprintf(stderr,"file '%s' is not an IFF CAT archive\n",fname);
X			return(-1);
X		}
X
X		/* verify that the header's filesize matches the file's size */
X		if ((filesize = lseek(fd,0,2)) == -1)
X		{
X			perror(fname);
X			return(-1);
X		}
X
X		if ((filesize - sizeof(ChunkHeader)) != mychunkheader.ckSize)
X		{
X			fprintf(stderr,"archive %s's CAT chunk size does not equal the file's size.\n",fname);
X			fprintf(stderr,"I'm assuming it's OK and using file size.\n");
X		}
X
X		/* ok, reopen the file for append and return the fd */
X		close(fd);
X		if ((fd = open(fname,O_RDWR|O_APPEND)) < 0)
X		{
X			perror(fname);
X			return(-1);
X		}
X	}
X	return(fd);
X}
X
X#define COPY_BUFFER_SIZE 32768
X
Xchar *copy_buffer = 0;
X
X/* append_file_to_archive
X *
X * this routine copies IFF file named by fname to the CAT archive known
X * by it's open-for-append fd.
X */
Xappend_file_to_archive(fname,archive_fd)
Xchar *fname;
Xint archive_fd;
X{
X	char *basename_ptr;
X	int bytes, i;
X	int infd, fnamelen, basenamelen;
X	ULONG chunkid, subtype;
X	long chunksize, new_chunk_address;
X	ULONG subchunkid;
X	long subchunksize, placeholder, calculated_chunk_size, inputfilesize;
X
X	/* seek to the end of the archive */
X	lseek(archive_fd,0,2);
X
X	/* open the file to appended to the archive, read only */
X	if ((infd = open(fname,O_RDONLY)) == -1)
X	{
X		perror(fname);
X		return(0);
X	}
X
X	/* get the filesize of the input file and relocate back to the start
X	 * of it */
X	inputfilesize = lseek(infd,0,2);
X	lseek(infd,0,0);
X
X	/* get the ID and size of the next chunk */
X	if ((chunkid = nextchunk(infd,&chunksize,&inputfilesize)) == 0)
X	{
X		fprintf(stderr,"couldn't get header chunk from file %s\n",fname);
X		close(infd);
X		return(0);
X	}
X
X	/* if the header isn't CAT, FORM or LIST, don't copy it */
X	if (chunkid != ID_CAT && chunkid != ID_FORM && chunkid != ID_LIST)
X	{
X		fprintf(stderr,"file %s is not an IFF CAT, FORM or LIST, ignored\n",fname);
X		close(infd);
X		return(0);
X	}
X
X	/* kludgily get the subtype - for FORMs, LISTs & CATs it's the
X	 * first 4 bytes of the chunk data.  These are included in chunksize
X	 */
X	if (read(infd,&subtype,4) != 4)
X	{
X		perror("copy subtype");
X		return(0);
X	}
X	inputfilesize -= 4;
X
X	/* record where we are in the archive so we can rewrite the header
X	 * which we'll need to do if we add an IFAR chunk */
X	 new_chunk_address = lseek(archive_fd,0L,1) + 4;
X
X	/* write in the chunk ID and the subtype - the program will
X	 * rewrite the length when we know for sure how long the
X	 * chunk is */
X	if (!WriteChunk(archive_fd,chunkid,&subtype,4))
X	{
X		perror("append WriteChunk");
X		return(0);
X	}
X
X	/* keep track of the size of the FORM, CAT or LIST we're reading
X	 * through.  We start with 4, the size of subtype written above */
X	calculated_chunk_size = 4;
X
X	fnamelen = strlen(fname);
X
X	/* if the filename includes a path, use only the base portion */
X	basename_ptr = basename(fname);
X	basenamelen = strlen(basename_ptr);
X
X	/* write an IFAR chunk, it's the basename determined above,
X	 * and our handle for it */
X	if (!WriteChunk(archive_fd,ID_IFAR,basename_ptr,basenamelen))
X		return(0);
X
X	/* add size of the chunk header and the length of the chunk
X	 * body to the calculated chunk size.  If the number is odd,
X	 * increment it by one as the IFF spec requires odd-sized
X	 * chunks to be one-null-padded to make them even.  Note
X	 * that WriteChunk took care of it for the actual data written
X	 */
X	calculated_chunk_size += sizeof(ChunkHeader) + basenamelen;
X	if (basenamelen & 1)
X		calculated_chunk_size++;
X
X	/* for all remaining chunks inside the FORM, LIST or CAT that
X	 * we're adding to the archive, */
X	while ((subchunkid = nextchunk(infd,&subchunksize,&inputfilesize)) != 0)
X	{
X		/* if it's an IFAR chunk, discard it */
X		if (subchunkid == ID_IFAR)
X			skipchunk(infd,subchunksize,&inputfilesize);
X		else
X		{
X			calculated_chunk_size += subchunksize + sizeof(ChunkHeader);
X			if (subchunksize & 1)
X				calculated_chunk_size++;
X
X			/* write the chunk header for the embedded chunk we're copying */
X			if (!WriteChunkHeader(archive_fd,subchunkid,subchunksize))
X				return(0);
X
X			/* now copy the embedded chunk's data */
X			copychunkbytes(infd,archive_fd,subchunksize,&inputfilesize);
X		}
X	}
X	/* get current position in the archive, seek back to the header of the
X	 * FORM, CAT or LIST we just copied (into the archive) and write in the 
X	 * correct chunk size, then seek back to where we were
X	 */
X
X	placeholder = lseek(archive_fd,0L,1);
X	lseek(archive_fd,new_chunk_address,0);
X	if (write(archive_fd,&calculated_chunk_size,4) != 4)
X	{
X		perror("archive subheader rewrite");
X		fprintf(stderr,"archive is blown.\n");
X		close(infd);
X		return(0);
X	}
X	/* return to previous place in archive, close file we copied and
X	 * return 'success' */
X	lseek(archive_fd,placeholder,0);
X	close(infd);
X	return(1);
X}
X
X/* rewrite_archive_header - write (filesize - sizeof(ChunkHeader)) into
X * CAT archive's header, assumes file is open for write
X */
Xrewrite_archive_header(fd)
X{
X	long filesize;
X
X	/* get filesize by seeking to EOF */
X	if ((filesize = lseek(fd,0,2)) == -1)
X	{
X		perror("archive");
X		return(0);
X	}
X
X	/* go back to the beginning of the file */
X	if (lseek(fd,0,0) == -1)
X	{
X		perror("archive cleanup seek");
X		return(0);
X	}
X
X	if (!WriteChunkHeader(fd,ID_CAT,(filesize - sizeof(ChunkHeader))))
X	{
X		perror("archive cleanup");
X		return(0);
X	}
X
X	return(1);
X}
X
X/* the copy buffer cleanup routine, it frees the copy buffer memory if
X * the buffer has been allocated
X */
Xvoid cleanup_copy_buffer()
X{
X	if (copy_buffer)
X		FreeMem(copy_buffer,COPY_BUFFER_SIZE);
X}
X
X/* copychunkbytes
X *
X * copy nbytes from infd to outfd, subtracting that amount from
X * the number of filebytes left within the virtual chunk, as given
X * by the address of that variable
X */
Xcopychunkbytes(infd,outfd,nbytes,filebytes_left_ptr)
Xint infd, outfd;
Xlong nbytes, *filebytes_left_ptr;
X{
X	int copysize, odd;
X
X	/* if we haven't allocated copy_buffer, allocate it and add it's cleanup
X	 * routine (cleanup_copy_buffer) to the cleanup list */
X	if (!copy_buffer)
X	{
X		if ((copy_buffer = (char *)AllocMem(COPY_BUFFER_SIZE,MEMF_FAST)) == (char *)NULL)
X			panic("couldn't allocate copy buffer");
X
X		add_cleanup(cleanup_copy_buffer);
X	}
X
X	/* if nbytes of copying requested exceeds the virtual EOF (end of
X	 * the chunk's metachunk), truncate the chunk
X	 */
X	if (nbytes > *filebytes_left_ptr)
X	{
X		fprintf(stderr,"copychunkbytes: chunk size exceeds size of superchunk - truncating\n");
X		nbytes = *filebytes_left_ptr;
X	}
X
X	/* find out if the length of the chunk is odd or not - we'll need
X	 * it later to see if we need to write a pad byte
X	 */
X	 odd = (nbytes & 1);
X
X	/* do the copy, breaking it up into multiple COPY_BUFFER_SIZE sized
X	 * portions, if neccessary */
X	while (nbytes > 0)
X	{
X		copysize = (nbytes > COPY_BUFFER_SIZE) ? COPY_BUFFER_SIZE : nbytes;
X
X		if (read(infd,copy_buffer,copysize) != copysize)
X		{
X			perror("copybytes input");
X			fprintf(stderr,"archive is blown.\n");
X			close(infd);
X			return(0);
X		}
X		if (write(outfd,copy_buffer,copysize) != copysize)
X		{
X			perror("copybytes output");
X			fprintf(stderr,"archive is blown.\n");
X			close(infd);
X			return(0);
X		}
X		/* update bytes left in chunk and in chunk's metachunk */
X		nbytes -= copysize;
X		*filebytes_left_ptr -= copysize;
X	}
X
X	/* done with copy - if number of bytes copied was odd, read and
X	 * discard the pad byte, write out a pad byte and update the
X	 * virtual EOF (end of chunk) */
X	if (odd)
X	{
X		if (read(infd,copy_buffer,1) != 1)
X			perror("copychunkbytes: failed to skip input byte");
X		assert(*copy_buffer == '\0');
X		write(outfd,copy_buffer,1);
X		(*filebytes_left_ptr)--;
X	}
X}
X
X/* create an archive by opening it, writing a CAT header, and returning the
X * file descriptor if it succeeded or -1 otherwise
X */
Xint create_archive(archive_name,subtype)
Xchar *archive_name;
XULONG subtype;
X{
X	int archive_fd;
X
X	if ((archive_fd = open(archive_name, O_RDWR|O_CREAT|O_TRUNC)) == -1)
X	{
X		perror(archive_name);
X		return(-1);
X	}
X
X	if (!WriteCATheader(archive_fd))
X	{
X		fprintf("create_archive: couldn't write CAT chunkheader of new archive\n");
X		return(-1);
X	}
X
X	/* success, return the archive fd */
X	return(archive_fd);
X}
X
X/* end of create.c */
X
//END_OF_FILE
echo x - delete.c
sed 's/^X//' > delete.c << '//END_OF_FILE'
X/* iffar - IFF CAT archiver delete functions
X
X   By Karl Lehenbauer, version 1.2, release date 5/9/88.
X   This code is released to the public domain.
X   See the README file for more information.
X
X*/
X
X#include <exec/types.h>
X#include <exec/memory.h>
X#include <stdio.h>
X#include <fcntl.h>
X#include "assert.h"
X#include "iff.h"
X
Xextern ULONG nextchunk();
X
Xint delete_entries(archive_name,fnames,nfiles)
Xchar *archive_name;
Xchar *fnames[];
Xint nfiles;
X{
X	int old_archive_fd, new_archive_fd;
X	ULONG cat_type, chunkid, innerchunkid, subtype;
X	long chunksize, innerchunksize, filesize;
X	char textbuf[128], old_archive_name[128];
X	int i, delete_file, file_bytes;
X
X	extern int verbose;
X
X	/* rename the archive to its old name concatenated with ".old"
X	 */
X	sprintf(old_archive_name,"%s.old",archive_name);
X	unlink(old_archive_name);
X	rename(archive_name,old_archive_name);
X
X	if ((old_archive_fd = OpenCAT(old_archive_name,&cat_type,&filesize)) == -1)
X	{
X		fprintf(stderr,"Can't open archive '%s'\n",old_archive_name);
X		return(0);
X	}
X
X	if ((new_archive_fd = create_archive(archive_name,ID_MISC)) < 0)
X		return(0);
X
X	while ((chunkid = nextCATchunk(old_archive_fd,&subtype,&textbuf[0],&chunksize,&filesize)) != 0L)
X	{
X		/* if the chunk type isn't FORM, CAT or LIST, copy it across 
X		 * without looking at it */
X		 if (chunkid != ID_FORM && chunkid != ID_CAT && chunkid != ID_LIST)
X		 {
X		 	if (!WriteChunkHeader(new_archive_fd,chunkid,chunksize))
X				return(0);
X			copychunkbytes(old_archive_fd,new_archive_fd,chunksize,&filesize);
X		}
X
X		/* search to see if this chunk's name is one specified in fnames,
X		 * an array of pointer to char strings */
X
X		delete_file = 0;
X		for (i = 0; i < nfiles; i++)
X		{
X			if (!strnicmp(fnames[i],textbuf, 128))
X			{
X				delete_file = 1;
X				break;
X			}
X		}
X		/* if we did decide to delete it, skip it */
X		if (delete_file)
X		{
X			if (verbose)
X				fprintf(stderr,"deleting %s\n",textbuf);
X
X				if (!skipchunk(old_archive_fd,chunksize,&filesize))
X				{
X					fprintf(stderr,"delete: skipchunk failed\n");
X					return(0);
X				}
X		}
X		else	/* we want to copy it */
X		{
X			if (!WriteCATentry(new_archive_fd,textbuf,chunkid,subtype,chunksize))
X				return(0);
X
X			copychunkbytes(old_archive_fd,new_archive_fd,chunksize,&filesize);
X		}
X	}
X
X	/* write the right length in for the header */
X	rewrite_archive_header(new_archive_fd);
X
X	/* close the old and new archive files and return success */
X	close(old_archive_fd);
X	close(new_archive_fd);
X	return(1);
X}
X
X/* end of extract.c */
X
//END_OF_FILE
echo x - extract.c
sed 's/^X//' > extract.c << '//END_OF_FILE'
X/* iffar - IFF CAT archiver extractor functions
X
X   By Karl Lehenbauer, version 1.2, release date 5/9/88.
X   This code is released to the public domain.
X   See the README file for more information.
X
X*/
X
X#include <exec/types.h>
X#include <exec/memory.h>
X#include <stdio.h>
X#include <fcntl.h>
X#include "assert.h"
X#include "iff.h"
X
X#define ID_NAME MakeID('N','A','M','E')
X#define ID_AUTH MakeID('A','U','T','H')
X#define ID_ANNO MakeID('A','N','N','O')
X#define ID_Copyright MakeID('(','c',')',' ')
X
Xextern long lseek();
X
Xextern ULONG nextchunk();
X
Xint extract(archive_name,fnames,nfiles)
Xchar *archive_name;
Xchar *fnames[];
Xint nfiles;
X{
X	int archive_fd, outfd;
X	ULONG cat_type, chunkid, innerchunkid, subtype;
X	long chunksize, innerchunksize, filesize;
X	char textbuf[256], *extract_name;
X	long placeholder;
X	int do_extract, i;
X
X	extern int verbose;
X
X	if ((archive_fd = OpenCAT(archive_name,&cat_type,&filesize)) == -1)
X	{
X		fprintf(stderr,"Can't open archive '%s'\n",archive_name);
X		return(0);
X	}
X
X	while ((chunkid = nextCATchunk(archive_fd,&subtype,&textbuf[0],&chunksize,&filesize)) != 0L)
X	{
X		/* if the chunk type isn't FORM, CAT or LIST, skip it 'cuz it
X		 * isn't anything we know how to extract */
X		if (chunkid != ID_FORM && chunkid != ID_CAT && chunkid != ID_LIST)
X		{
X			if (!skipchunk(archive_fd,chunksize,&filesize))
X			{
X				perror(archive_fd);
X				fprintf(stderr,"extract: skipchunk failed\n");
X				return(0);
X			}
X			break;
X		}
X
X		/* if no extract names (nfiles == 0) were specified, extract all
X		 * files, so extract this one, else search to see if this name
X		 * is one specified in fnames, an array of pointers to char
X		 * strings */
X		do_extract = 0;
X		if (nfiles == 0)
X		{
X			do_extract = 1;
X			extract_name = textbuf;
X		}
X		else
X		{
X			for (i = 0; i < nfiles; i++)
X			{
X				if (!strnicmp(basename(fnames[i]),textbuf,128))
X				{
X					do_extract = 1;
X					extract_name = fnames[i];
X					break;
X				}
X			}
X		}
X		/* if we did decide to extract it, extract it */
X		if (do_extract)
X		{
X			if (verbose)
X				fprintf(stderr,"extracting %s\n",extract_name);
X
X			/* create, open and truncate the extract file */
X			if ((outfd = open(extract_name,O_RDWR|O_CREAT|O_TRUNC)) == -1)
X			{
X				perror(textbuf);
X			}
X			else
X			{
X				if (!WriteSuperChunkHeader(outfd,chunkid,subtype,chunksize))
X					return(0);
X
X				copychunkbytes(archive_fd,outfd,chunksize,&filesize);
X				close(outfd);
X			}
X
X			/* if they specified files on the command line, null them
X			 * out after we retrieve them, later we'll make a pass
X			 * through and complain about any we don't find with
X			 * their first bytes nulled out
X			 */
X			if (nfiles != 0)
X				*fnames[i] = '\0';
X		}
X		/* else we don't want to extract it, so skip it */
X		else if (!skipchunk(archive_fd,chunksize,&filesize))
X		{
X			perror(archive_fd);
X			fprintf(stderr,"extract: skipchunk failed\n");
X			return(0);
X		}
X	}
X
X	/* complain about any files named that we didn't extract */
X	for (i = 0; i < nfiles; i++)
X	{
X		if (*fnames[i] != '\0')
X			fprintf(stderr,"%s: no such archive entry\n",fnames[i]);
X	}
X
X	/* close the archive file and return success */
X	close(archive_fd);
X	return(1);
X}
X
X/* end of extract.c */
X
//END_OF_FILE
echo x - iff.c
sed 's/^X//' > iff.c << '//END_OF_FILE'
X/* iffar - IFF CAT archiver, IFF support functions
X
X   By Karl Lehenbauer, version 1.2, release date 5/9/88
X   This code is released to the public domain.
X   See the README file for more information.
X
X*/
X
X/* culled general purpose IFF file cracking routines for Karl's 
X * IFF Stuff by Karl Lehenbauer, based originally on public domain IFF 
X * code from Electronic Arts, 2/24/88
X */
X
X#include <exec/types.h>
X#include <exec/memory.h>
X#include <stdio.h>
X#include <fcntl.h>
X#include <ctype.h>
X#include "assert.h"
X
X#include "iff.h"
X
Xextern long lseek();
X
Xextern ULONG nextchunk();
X
X/* print a chunkID to stderr */
XPutID(id)
XID id;
X{
X    fprintf(stderr,"%c%c%c%c",
X	   (char)((id>>24L) & 0x7f),
X	   (char)((id>>16L) & 0x7f),
X	   (char)((id>>8)   & 0x7f),
X	   (char)(id        & 0x7f) );
X}
X
XUBYTE *MyAllocMem(bytes, type)
XULONG bytes, type;
X{
X	UBYTE *tmp;
X		tmp = AllocMem(bytes, type);
X	return tmp;
X}
X
X/* return chunktype of next chunk */
X/* every time nextchunk is executed and returns that it found a chunk,
X * either readchunk or skipchunk must be called and only one time!
X */
XULONG nextchunk(fd,chunksize,chunk_bytes_left)
Xint fd;
Xlong *chunksize, *chunk_bytes_left;
X{
X	int sawsize, i, blown = 0;
X	ChunkHeader mychunkheader;
X	char checkchar;
X
X	/* if chunk_bytes_left is zero, we obey it as a virtual EOF, so
X	 * return 0 */
X	 if (*chunk_bytes_left == 0)
X	 	return(0);
X
X	/* read the next chunk header */
X	if ((sawsize = read(fd,&mychunkheader,sizeof(mychunkheader))) != 
X		sizeof(mychunkheader))
X	{
X		if (sawsize != 0)
X			fprintf(stderr,"Something's wrong with nextchunk! (sawsize %d)\n", sawsize);
X		*chunksize = 0;
X		return(0);
X	}
X
X#ifdef MAJORDEBUG
X	fputs("nextchunk: next chunk '",stderr);
X	PutID(mychunkheader.ckID);
X	fprintf(stderr,"', size %d, parent bytes left %d\n",mychunkheader.ckSize,*chunk_bytes_left);
X#endif
X
X	*chunksize = mychunkheader.ckSize;
X
X	/* see if chunk ID looks OK */
X	for (i = 0; i < 4; i++)
X	{
X		checkchar = (mychunkheader.ckID >> (i * 8)) & 0xff;
X		if (!isprint(checkchar))
X		{
X			if (!blown)
X			{
X				blown = 1;
X				fprintf(stderr,"nextchunk: chunk ID contains an unprintable character (0x%x)\n",checkchar);
X			}
X			break;
X		}
X	}
X
X	/* see if chunk length is reasonable */
X	if ((mychunkheader.ckSize < 0) || (mychunkheader.ckSize > MAXCHUNKSIZE))
X	{
X		fprintf(stderr,"nextchunk: chunk length of %ld is unreasonable\n",mychunkheader.ckSize);
X		blown = 1;
X	}
X
X	if (blown)
X	{
X		fprintf(stderr,"nextchunk: I either got lost or the archive is blown\n");
X		return(0);
X	}
X
X	/* square up the bytes left in the chunk by the size of a chunk header,
X	 * eight bytes.  We leave it to the caller to subtract the size of the
X	 * body of the chunk by calling skipchunk or readchunk
X	 */
X	*chunk_bytes_left -= sizeof(mychunkheader);
X
X	if (*chunk_bytes_left < 0)
X	{
X		fprintf("nextchunk: chunk overran its parent by %d bytes\n",(0-*chunk_bytes_left));
X		*chunksize = 0;
X		*chunk_bytes_left = 0;
X		return(0);
X	}
X
X	return(mychunkheader.ckID);
X}
X
X/* read next chunk into buffer supplied, size must be value returned by
X * nextchunk
X * zero is returned on failure, one on success
X */
Xreadchunk(fd,buf,size,chunk_bytes_left)
Xint fd;
Xchar *buf;
XLONG size, *chunk_bytes_left;
X{
X	*chunk_bytes_left -= size;
X
X	if (*chunk_bytes_left < 0)
X	{
X		fprintf(stderr,"readchunk: chunk requested passed the end of its parent chunk\n");
X		*chunk_bytes_left = 0;
X		return(0);
X	}
X
X	if (read(fd,buf,size) != size) 
X	{
X		perror("smus file");
X		fputs("LoadSMUS: read of IFF chunk failed\n",stderr);
X		return(0);
X	}
X
X	/* odd-length chunks have a trailer byte - skip it */
X	if (size & 1)
X	{
X		lseek(fd,1L,1);
X		(*chunk_bytes_left)--;
X	}
X	return(1);
X}
X
X/* skip non-header portion of chunk, chunksize must have been returned
X * by nextchunk
X * returns 1 on success, 0 on failure
X */
Xskipchunk(fd,chunksize,chunk_bytes_left)
Xint fd;
XLONG chunksize, *chunk_bytes_left;
X{
X	*chunk_bytes_left -= chunksize;
X	if (chunksize & 1)
X		(*chunk_bytes_left)--;
X	if (*chunk_bytes_left < 0)
X	{
X		fprintf(stderr,"skipchunk: chunk size passes end of parent chunk's data by %d bytes\n",0 - *chunk_bytes_left);
X		return(0);
X	}
X	/* skip over chunk data and skip an extra byte if length is odd */
X	lseek(fd,(long)chunksize,1);
X	if (chunksize & 1)
X		lseek(fd,1L,1);
X	return(1);
X}
X
X/* OpenIFF
X * given file name, open the IFF file.
X * read the header, return failure if it's not a FORM
X * (someday we'll handle the more complex types)
X * read the form type, return failure if it's not the type requested
X * success, return the file descriptor
X */
X
Xint OpenIFF(fname,expected_formtype,length_ptr)
Xchar *fname;
XLONG expected_formtype;
XLONG *length_ptr;
X{
X	int iffile;
X	ChunkHeader chunkhead;
X	LONG formtype;
X
X	/* open the file */
X	if ((iffile = open(fname, O_RDONLY)) < 0)
X	{
X		fprintf(stderr,"OpenIFF: can't open IFF SMUS file %s\n",fname);
X		perror(fname);
X		return(-1);
X	}
X
X	/* get the length */
X	*length_ptr = lseek(iffile,0,2);
X	lseek(iffile,0,0);
X
X	/* read the header chunk */
X	if (read(iffile, &chunkhead, sizeof(chunkhead)) < 0)
X	{
X		fprintf(stderr,"OpenIFF: initial read from IFF file %s failed!\n",fname);
X		return(-1);
X	}
X
X	/* return if the header chunk doesn't say it's IFF FORM */
X	if (chunkhead.ckID != ID_FORM)
X	{
X		fprintf(stderr,"OpenIFF: File %s isn't IFF, is too complex, or doesn't start with FORM\n",fname);
X		return(-1);
X	}
X	/* fprintf(stderr,"OpenIFF: FORM found, size is %d\n",chunkhead.ckSize); */
X
X	/* read the form type */
X	read(iffile, &formtype, sizeof(formtype));
X
X	/* return if the form type isn't the type requested */
X	if (formtype != expected_formtype)
X	{
X		fprintf(stderr,"OpenIFF: File %s is IFF ");
X		PutID(formtype);
X		fprintf(stderr," rather than the requested ");
X		PutID(expected_formtype);
X		fprintf(stderr,"\n");
X		return(-1);
X	}
X	return(iffile);
X}
X
X/* read chunks until one of type chunktype is found or EOF
X * note that after a successful call to chunkuntil,
X * skipchunk or readchunk must be performed or the IFF reading
X * software will get lost on the next nextchunk
X * chunksize is returned on success, -1 otherwise
X * The caller should probably check the return explicitly for -1.
X * If checking only for less than zero, chunks larger than
X * two gigabytes will cause your code to break.
X */
X
XLONG chunkuntil(fd,chunktype,file_bytes_left)
Xint fd;
XULONG chunktype;
Xlong *file_bytes_left;
X{
X	ULONG currentchunk;
X	LONG chunksize;
X
X	while ((currentchunk = nextchunk(fd,&chunksize,file_bytes_left)) != NULL)
X	{
X		if (currentchunk == chunktype)
X			return(chunksize);
X		skipchunk(fd,chunksize,file_bytes_left);
X	}
X	return(0);
X}
X
X/* OpenCAT - Open an IFF CAT archive */
X
X/* OpenCAT
X * Open an IFF CAT archive, insuring that the file starts with an
X * IFF CAT header and that the length in the header is valid.
X * Return the CAT subtype, file descriptor and length, leaving the
X * file pointed at the start of the first subchunk
X */
X
Xint OpenCAT(archive_name,subtype_ptr,length_ptr)
Xchar *archive_name;
XULONG *subtype_ptr, *length_ptr;
X{
X	ChunkHeader mychunkheader;
X	int archive_fd;
X	long start_of_body, filesize;
X	long placeholder;
X
X	if ((archive_fd = open(archive_name,O_RDONLY)) == -1)
X	{
X		/* fprintf(stderr,"Can't open archive '%s'\n",archive_name); */
X		return(-1);
X	}
X
X	if (read(archive_fd,&mychunkheader,sizeof(mychunkheader)) != sizeof(mychunkheader))
X	{
X		perror(archive_name);
X		fprintf(stderr,"couldn't read chunk header\n");
X		return(-1);
X	}
X
X	if (mychunkheader.ckID != ID_CAT)
X	{
X		fprintf(stderr,"file '%s' is not an IFF CAT archive\n",archive_name);
X		return(-1);
X	}
X
X	if (read(archive_fd,subtype_ptr,sizeof(subtype_ptr)) != sizeof(subtype_ptr))
X	{
X		fprintf(stderr,"error reading archive header - subtype\n");
X		return(-1);
X	}
X
X	/* save location of current start of body */
X	if ((start_of_body = lseek(archive_fd,0,1)) == -1)
X	{
X		perror(archive_name);
X		return(-1);
X	}
X
X	/* seek to the end to get the size */
X	if ((filesize = lseek(archive_fd,0,2)) == -1)
X	{
X		perror(archive_name);
X		return(-1);
X	}
X
X	/* see if the shoe fits */
X	if ((filesize - sizeof(ChunkHeader)) != mychunkheader.ckSize)
X	{
X		fprintf(stderr,"archive %s's CAT chunk size does not equal the file's size.\n",archive_name);
X		fprintf(stderr,"I'm assuming it's blown.\n");
X		return(-1);
X	}
X
X	/* go back to the start of the IFF CAT archive's data */
X	if (lseek(archive_fd,start_of_body,0) == -1)
X	{
X		perror(archive_name);
X		return(-1);
X	}
X
X	/* it worked store filesize in location pointed to by 'length' 
X	 * and return the archive file's file descriptor
X	 */
X	*length_ptr = filesize;
X	return(archive_fd);
X}
X
X/* end of OpenCAT */
X
X/* nextcat - read header info for the next entry in an IFF CAT */
X
X/* nextCATchunk
X *
X * given fd, read into IFF file.
X * if we're not at a FORM, CAT or LIST, print the chunk type if verbose,
X *    then skip the chunk
X * if we are at a FORM, CAT or LIST, read the subtype and return it
X * via the argument subtype_ptr.
X * if the next chunk within the embedded FORM, CAT or LIST is IFAR,
X * read the text in the IFAR chunk (file name) and write it into space
X * pointed to by argument fname_ptr.
X * return the size of the chunk in argument chunk_length_ptr.
X * update the space left in the metachunk (usually the file) of argument
X * metachunk_length_ptr
X */
X
XULONG nextCATchunk(fd,subtype_ptr,fname_ptr,chunk_length_ptr,metachunk_length_ptr)
Xint fd;
XULONG *subtype_ptr;
Xchar *fname_ptr;
XLONG *chunk_length_ptr, *metachunk_length_ptr;
X{
X	ULONG cat_type, chunkid, innerchunkid;
X	long chunksize, innerchunkposition, innerchunksize, filesize;
X	int odd;
X
X	/* null out the returned subtype and fnam */
X	*subtype_ptr = 0L;
X	*fname_ptr = '\0';
X
X	if ((chunkid = nextchunk(fd,chunk_length_ptr,metachunk_length_ptr)) == 0L)
X		return(0L);
X
X	/* if the chunk type isn't FORM, CAT or LIST, return the chunkid
X	 */
X	if (chunkid != ID_FORM && chunkid != ID_CAT && chunkid != ID_LIST)
X		return(chunkid);
X
X	/* get the chunk subtype */
X	if (read(fd,subtype_ptr,4) != 4)
X	{
X		perror("reading subtype");
X		return(0);
X	}
X
X	/* reduce chunksize and metachunksize by the size of the subtype */
X	*chunk_length_ptr -= sizeof(ULONG);
X	*metachunk_length_ptr -= sizeof(ULONG);
X
X	/* sneak a peek into the embedded FORM, CAT or LIST to see
X	 * if the next chunk is an IFAR chunk */
X
X	 assert(*chunk_length_ptr > 0);
X
X	/* fetch the current location in the file - we'll restore it
X	 * if we don't find this next chunk to be an IFAR one
X	 */
X	innerchunkposition = lseek(fd,0L,1);
X
X	/* get the type and size of the inner chunk */
X	chunksize = *chunk_length_ptr;
X	innerchunkid = nextchunk(fd,&innerchunksize,&chunksize);
X
X	/* if it's not an fname chunk, seek back to the start of the
X	 * chunk and return the chunk id - master length should be OK
X	 */
X	if (innerchunkid != ID_IFAR)
X	{
X		lseek(fd,innerchunkposition,0);
X		return(chunkid);
X	}
X
X	odd = innerchunksize & 1;
X
X	/* read and zero-terminate the file name (contents of IFAR chunk) */
X	if (!readchunk(fd,fname_ptr,innerchunksize,&chunksize))
X	{
X		fprintf(stderr,"nextCATchunk: got into trouble reading chunk text\n");
X		return(0);
X	}
X	*(fname_ptr + innerchunksize) = '\0';
X
X	/* update the length of the chunk and its parent &  return the chunk id
X	 * (nextchunk normally handles updating the length but we used different
X	 * variables to make restoring (in case we don't find an IFAR chunk)
X	 * easier
X	 */
X	*chunk_length_ptr -= (sizeof(ChunkHeader) + innerchunksize);
X	*metachunk_length_ptr -= (sizeof(ChunkHeader) + innerchunksize);
X	if (odd)
X	{
X		(*chunk_length_ptr)--;
X		(*metachunk_length_ptr)--;
X	}
X	return(chunkid);
X}
X
X/* end of nextCATchunk */
X
X/* end of iff.c */
//END_OF_FILE
echo x - includes.c
sed 's/^X//' > includes.c << '//END_OF_FILE'
X/* iffar - archiver includes.c - for Manx +H +I options to precompile includes */
X
X/* iffar - IFF CAT archiver includes.c for include precompile
X
X   By Karl Lehenbauer, version 1.2, release date 5/9/88.
X   This code is released to the public domain.
X   See the README file for more information.
X
X*/
X
X#include <exec/types.h>
X#include <exec/memory.h>
X#include <exec/nodes.h>
X#include <exec/lists.h>
X/* #include <devices/audio.h> */
X#include <fcntl.h>
X#include <stdio.h>
X#include <functions.h>
X
X#include "assert.h"
X#include "iff.h"
X
X/* end of includes.c */
//END_OF_FILE
echo x - main.c
sed 's/^X//' > main.c << '//END_OF_FILE'
X/* iffar - IFF CAT archiver, main()
X
X   By Karl Lehenbauer, version 1.2, release date 5/9/88.
X   This code is released to the public domain.
X   See the README file for more information.
X
X*/
X
X/* iffar - iff 'cat' archiver, 3/26/88 */
X
X#include <stdio.h>
X#include "assert.h"
X
X/* options */
Xint verbose = 0;
Xint insert_after = 0;
Xint insert_before = 0;
Xint suppress_creation_message = 0;
X
X/* if insert_after or insert_before is set, this global has the relevant
X * name */
Xchar *location_modifier_name;
X
X/* variables */
Xint number_of_arguments;	/* number of args to the program, a global */
X
X/* this'll point to the command line's command char string */
Xchar *cmdkeys;
X
X/* this'll be the name of the archive file */
Xchar *archive_name;
X
X/* this'll contain the command */
Xchar command_key;
X
X/* if a command must have some names as arguments, must_have_names can be 
X * called to insure that arguments were supplied.
X * Note that what constitutes the presence of arguments varies whether
X * a positional modifier ('i', 'b' or 'a') was specified.
X */
Xmust_have_names()
X{
X	if ((insert_before || insert_after) && (number_of_arguments < 5))
X	{
X		fprintf(stderr,"a positioning element and at least one other element must be specified for\n");
X		fprintf(stderr,"the combination of options you have requested.\n");
X		usage();
X	}
X	if (number_of_arguments < 4)
X	{
X		fprintf(stderr,"at least one archive element must be specified for this option\n");
X		usage();
X	}
X}
X
Xheader()
X{
X	fprintf(stderr,"iffar - Hackercorp public domain IFF CAT archiver,\n\tVersion 1.4 (9/23/89), by Karl Lehenbauer\n\n");
X	fprintf(stderr,"This program maintains archives of IFF FORM and CAT files\n");
X	fprintf(stderr,"in a manner that complies with the IFF CAT specification.\n");
X}
X
X/* usage  print usage text and exit */
Xusage()
X{
X	fprintf(stderr,"\nUsage: iffar key [posname] afile name ...\n\n");
X	fprintf(stderr,"key can be one of the following:\n");
X	fprintf(stderr,"\td\tdelete\n\tr\treplace\n\tq\tquick append\n");
X	fprintf(stderr,"\tt\ttable of contents\n\tx\textract\n");
X	fprintf(stderr,"and zero or more of the following options:\n");
X	fprintf(stderr,"\tv\tverbose\n\ta\tafter\n\ti,b\tbefore\n");
X	fprintf(stderr,"\tc\tsuppress creation message\n");
X	cleanup();
X	exit(1);
X}
X
Xmain(argc,argv)
Xint argc;
Xchar *argv[];
X{
X	int i, j;
X	int archive_fd, files;
X	char *nameptr;
X	char **entryname_pointers;
X	int entrycount;
X
X	number_of_arguments = argc;
X
X	if (argc < 3)
X	{
X		header();
X		usage();
X	}
X
X	/* assign nice variable names to various command line arguments */
X	cmdkeys = argv[1];
X	command_key = *cmdkeys;
X	archive_name = argv[2];
X	location_modifier_name = (char *)NULL;
X	entryname_pointers = &argv[3];
X	entrycount = argc - 3;
X
X	/* figure out any modifiers specified to the main function requested */
X	for (i = 1; i < strlen(cmdkeys); i++)
X	{
X		switch(cmdkeys[i])
X		{
X			case 'v':				/* verbose selected, set verbose flag */
X				verbose = 1;
X				break;
X
X			/* 'after' option, make sure they've selected 'r' or 'm' as
X			 * the main command.  Also, make sure they haven't already
X			 * chosen the insert_before option.  After that, we're OK,
X			 * so twiddle parameters and break
X			 */
X			case 'a':
X				if (command_key != 'r' && command_key != 'm')
X				{
X					fprintf(stderr,"i, b and a modifiers are only good for r and m options\n");
X					usage();
X				}
X				if (insert_before)
X				{
X					fprintf(stderr,"you can't select 'insert before' and 'insert after'\n");
X					usage();
X				}
X				insert_after = 1;
X				location_modifier_name = argv[3];
X				entryname_pointers = &argv[4];
X				entrycount = argc - 4;
X				break;
X
X			/* 'insert before' option, make sure they've selected 'r' or
X			 * 'm' as the main command.  Also, make sure they haven't already
X			 * chosen the insert_after option.  After that, we're OK,
X			 * so twiddle parameters and break
X			 */
X			case 'i':				/* insert; before */
X			case 'b':
X				if (command_key != 'r' && command_key != 'm')
X				{
X					fprintf(stderr,"i, b and a modifiers are only good for r and m options\n");
X					usage();
X				}
X				if (insert_after)
X				{
X					fprintf(stderr,"you can't select 'insert before' and 'insert after'\n");
X					usage();
X				}
X				insert_before = 1;
X				location_modifier_name = argv[3];
X				entryname_pointers = &argv[4];
X				entrycount = argc - 4;
X				break;
X
X			case 'c':				/* supress creation message */
X				suppress_creation_message = 1;
X				break;
X
X			default:				/* unrecognized option */
X				fprintf(stderr,"option '%c' unrecognized\n",cmdkeys[i]);
X				usage();
X		}
X	}
X
X	/* now execute the major command */
X
X	switch(command_key)
X	{
X		case 'd':		/* delete */
X			must_have_names();
X			delete_entries(archive_name,entryname_pointers,entrycount);
X			break;
X
X		/* replace - if the archive doesn't exist, fall through to
X		 * quick append */
X		case 'r':
X			must_have_names();
X			if ((archive_fd = open(archive_name,O_RDONLY)) != -1)
X			{
X				close(archive_fd);
X				replace_entries(archive_name,entryname_pointers,entrycount);
X				break;
X			}
X		case 'q':		/* quick append */
X			must_have_names();
X			checknew(archive_name);
X			if ((archive_fd = open_quick_append(archive_name)) == -1)
X				break;
X			quickappend_entries(archive_fd,entryname_pointers,entrycount);
X			break;
X		case 't':		/* table of contents */
X			table_of_contents(archive_name);
X			break;
X
X		case 'm':		/* move */
X			fprintf(stderr,"move option not implemented\n");
X			must_have_names();
X			break;
X		case 'x':		/* extract */
X			extract(archive_name,entryname_pointers,entrycount);
X			break;
X
X		default:
X			fprintf(stderr,"requested command (%c) is invalid\n",command_key);
X			usage();
X	}
X	cleanup();
X	exit(0);
X}
X
X/* end of main.c */
//END_OF_FILE
echo "End of archive."
# end of archive.
exit 0
-- 
-- uunet!sugar!karl	"There is hopeful symbolism in the fact that 
-- 			 flags do not wave in a vacuum."  -- Arthur C. Clarke
-- Usenet access: (713) 438-5018