/* iffar - IFF CAT archiver output and file copy rouines

   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 from general purpose IFF file cracking routines for Karl's 
 * Audio 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 "assert.h"

#include "iff.h"

extern long lseek();

extern int verbose;

extern char *basename();

#define ID_MISC MakeID('M','I','S','C')

WriteCATheader(fd)
int fd;
{
	static ULONG dummy = ID_MISC;
	WriteChunk(fd,ID_CAT, &dummy, sizeof(dummy));
}

/* write a chunk header, that's the 4-byte chunk ID and a 4-byte 
 * chunk length 
 */
WriteChunkHeader(fd,chunktype,length)
int fd;
ULONG chunktype;
long length;
{
	ChunkHeader chunkheader;

	chunkheader.ckID = chunktype;
	chunkheader.ckSize = length;

	if (write(fd,&chunkheader,sizeof(chunkheader)) == -1)
	{
		perror("WriteChunkHeader");
		return(0);
	}
	return(1);
}

/* write a chunk that has a four character subtype, like FORM, CAT or LIST
*/
WriteSuperChunkHeader(fd,chunktype,subtype,length)
int fd;
ULONG chunktype, subtype;
long length;
{
	if (!WriteChunkHeader(fd,chunktype,length+sizeof(subtype)))
		return(0);
	return(write(fd,&subtype,sizeof(subtype)) != -1);
}

/* WriteCATentry
	This routine is given all of the info for a superchunk header and,
	in addition, a file name.  It writes the chunk header and a
	FNAM chunk containing the file name out to the fd provided.
*/
WriteCATentry(fd,fname,chunktype,subtype,length)
int fd;
char *fname;
ULONG chunktype, subtype;
long length;
{
	int fnamelen;
	int calc_chunk_length;

	/* Figure out the length of the file name.  Remember that
	 * it should be even.  (I should use a cool macro to force
	 * that, but I don't)
	 * Add the size of the FNAM chunk we're going to write to our
	 * calculated chunk length.
	 */
	fnamelen = strlen(fname);
	calc_chunk_length = fnamelen;
	if (fnamelen & 1)
		calc_chunk_length++;
	calc_chunk_length += length + sizeof(ChunkHeader);

	WriteSuperChunkHeader(fd,chunktype,subtype,calc_chunk_length);
	if (!WriteChunk(fd,ID_FNAM,fname,strlen(fname)))
		return(0);
	return(1);
}

/* write a chunk header and body, that's the 8-byte header and the data
 * to match the specified length
 */
WriteChunk(fd,chunktype,data,length)
int fd;
ULONG chunktype;
char *data;
long length;
{
	static char zero = '\0';

	if (!WriteChunkHeader(fd,chunktype,length))
		return(0);

	/* if there's a body, write it out */
	if (length != 0)
		if (write(fd,data,length) == -1)
		{
			perror("WriteChunk");
			return(0);
		}

	/* if the length is odd, write out an additional zero byte */
	if (length & 1)
		if (write(fd,&zero,1) == -1)
		{
			perror("WriteChunk");
			return(0);
		}
	return(1);
}

/* checknew - given fname, this routine prints "Creating IFF CAT archive "
 * and the fname if file fname does not exist
 */
checknew(fname)
char *fname;
{
	extern int suppress_creation_message;
	int fd;

	if (!suppress_creation_message)
	{
		if ((fd = open(fname,O_RDONLY)) < 0)
		{
			fprintf(stderr,"Creating IFF CAT archive '%s'\n",fname);
		}
		else
			close(fd);
	}
}

/* open_quick_append
 * open the archive for append, verifying that it is IFF, subtype CAT,
 * that the length in the header matches the length of the file and
 * such.  Note that this has been wrapped into a better OpenCAT routine
 * but I have not fixed open_quick_append to call it yet.
 */
int open_quick_append(fname)
char *fname;
{
	ChunkHeader mychunkheader;
	long filesize;
	int fd;

	/* If I can't open the archive read only, it doesn't exist, so
	 * create it
	 */
	if ((fd = open(fname,O_RDONLY)) < 0)
	{
		if ((fd = create_archive(fname,ID_MISC)) < 0)
		{
			perror(fname);
			return(-1);
		}
	}
	else
	{
		/* read the IFF header and validate we've got a CAT */
		if (read(fd,&mychunkheader,sizeof(mychunkheader)) != sizeof(mychunkheader))
		{
			perror(fname);
			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",fname);
			return(-1);
		}

		/* verify that the header's filesize matches the file's size */
		if ((filesize = lseek(fd,0,2)) == -1)
		{
			perror(fname);
			return(-1);
		}

		if ((filesize - sizeof(ChunkHeader)) != mychunkheader.ckSize)
		{
			fprintf(stderr,"archive %s's CAT chunk size does not equal the file's size.\n",fname);
			fprintf(stderr,"I'm assuming it's OK and using file size.\n");
		}

		/* ok, reopen the file for append and return the fd */
		close(fd);
		if ((fd = open(fname,O_RDWR|O_APPEND)) < 0)
		{
			perror(fname);
			return(-1);
		}
	}
	return(fd);
}

#define COPY_BUFFER_SIZE 32768

char *copy_buffer = 0;

/* append_file_to_archive
 *
 * this routine copies IFF file named by fname to the CAT archive known
 * by it's open-for-append fd.
 */
append_file_to_archive(fname,archive_fd)
char *fname;
int archive_fd;
{
	char *basename_ptr;
	int bytes, i;
	int infd, fnamelen, basenamelen;
	ULONG chunkid, subtype;
	long chunksize, new_chunk_address;
	ULONG subchunkid;
	long subchunksize, placeholder, calculated_chunk_size, inputfilesize;

	/* seek to the end of the archive */
	lseek(archive_fd,0,2);

	/* open the file to appended to the archive, read only */
	if ((infd = open(fname,O_RDONLY)) == -1)
	{
		perror(fname);
		return(0);
	}

	/* get the filesize of the input file and relocate back to the start
	 * of it */
	inputfilesize = lseek(infd,0,2);
	lseek(infd,0,0);

	/* get the ID and size of the next chunk */
	if ((chunkid = nextchunk(infd,&chunksize,&inputfilesize)) == 0)
	{
		fprintf(stderr,"couldn't get header chunk from file %s\n",fname);
		close(infd);
		return(0);
	}

	/* if the header isn't CAT, FORM or LIST, don't copy it */
	if (chunkid != ID_CAT && chunkid != ID_FORM && chunkid != ID_LIST)
	{
		fprintf(stderr,"file %s is not an IFF CAT, FORM or LIST, ignored\n",fname);
		close(infd);
		return(0);
	}

	/* kludgily get the subtype - for FORMs, LISTs & CATs it's the
	 * first 4 bytes of the chunk data.  These are included in chunksize
	 */
	if (read(infd,&subtype,4) != 4)
	{
		perror("copy subtype");
		return(0);
	}
	inputfilesize -= 4;

	/* record where we are in the archive so we can rewrite the header
	 * which we'll need to do if we add a FNAM chunk */
	 new_chunk_address = lseek(archive_fd,0L,1) + 4;

	/* write in the chunk ID and the subtype - the program will
	 * rewrite the length when we know for sure how long the
	 * chunk is */
	if (!WriteChunk(archive_fd,chunkid,&subtype,4))
	{
		perror("append WriteChunk");
		return(0);
	}

	/* keep track of the size of the FORM, CAT or LIST we're reading
	 * through.  We start with 4, the size of subtype written above */
	calculated_chunk_size = 4;

	fnamelen = strlen(fname);

	/* if the filename includes a path, use only the base portion */
	basename_ptr = basename(fname);
	basenamelen = strlen(basename);

	/* write a FNAM chunk, it's the basename determined above,
	 * and our handle for it */
	if (!WriteChunk(archive_fd,ID_FNAM,basename_ptr,basenamelen))
		return(0);

	/* add size of the chunk header and the length of the chunk
	 * body to the calculated chunk size.  If the number is odd,
	 * increment it by one as the IFF spec requires odd-sized
	 * chunks to be one-null-padded to make them even.  Note
	 * that WriteChunk took care of it for the actual data written
	 */
	calculated_chunk_size += sizeof(ChunkHeader) + basenamelen;
	if (basenamelen & 1)
		calculated_chunk_size++;

	/* for all remaining chunks inside the FORM, LIST or CAT that
	 * we're adding to the archive, */
	while ((subchunkid = nextchunk(infd,&subchunksize,&inputfilesize)) != 0)
	{
		/* if it's an FNAM chunk, discard it */
		if (subchunkid == ID_FNAM)
			skipchunk(infd,subchunksize,&inputfilesize);
		else
		{
			calculated_chunk_size += subchunksize + sizeof(ChunkHeader);
			if (subchunksize & 1)
				calculated_chunk_size++;

			/* write the chunk header for the embedded chunk we're copying */
			if (!WriteChunkHeader(archive_fd,subchunkid,subchunksize))
				return(0);

			/* now copy the embedded chunk's data */
			copychunkbytes(infd,archive_fd,subchunksize,&inputfilesize);
		}
	}
	/* get current position in the archive, seek back to the header of the
	 * FORM, CAT or LIST we just copied (into the archive) and write in the 
	 * correct chunk size, then seek back to where we were
	 */

	placeholder = lseek(archive_fd,0L,1);
	lseek(archive_fd,new_chunk_address,0);
	if (write(archive_fd,&calculated_chunk_size,4) != 4)
	{
		perror("archive subheader rewrite");
		fprintf(stderr,"archive is blown.\n");
		close(infd);
		return(0);
	}
	/* return to previous place in archive, close file we copied and
	 * return 'success' */
	lseek(archive_fd,placeholder,0);
	close(infd);
	return(1);
}

/* rewrite_archive_header - write (filesize - sizeof(ChunkHeader)) into
 * CAT archive's header, assumes file is open for write
 */
rewrite_archive_header(fd)
{
	long filesize;

	/* get filesize by seeking to EOF */
	if ((filesize = lseek(fd,0,2)) == -1)
	{
		perror("archive");
		return(0);
	}

	/* go back to the beginning of the file */
	if (lseek(fd,0,0) == -1)
	{
		perror("archive cleanup seek");
		return(0);
	}

	if (!WriteChunkHeader(fd,ID_CAT,(filesize - sizeof(ChunkHeader))))
	{
		perror("archive cleanup");
		return(0);
	}

	return(1);
}

/* the copy buffer cleanup routine, it frees the copy buffer memory if
 * the buffer has been allocated
 */
void cleanup_copy_buffer()
{
	if (copy_buffer)
		FreeMem(copy_buffer,COPY_BUFFER_SIZE);
}

/* copychunkbytes
 *
 * copy nbytes from infd to outfd, subtracting that amount from
 * the number of filebytes left within the virtual chunk, as given
 * by the address of that variable
 */
copychunkbytes(infd,outfd,nbytes,filebytes_left_ptr)
int infd, outfd;
long nbytes, *filebytes_left_ptr;
{
	int copysize, odd;

	/* if we haven't allocated copy_buffer, allocate it and add it's cleanup
	 * routine (cleanup_copy_buffer) to the cleanup list */
	if (!copy_buffer)
	{
		if ((copy_buffer = (char *)AllocMem(COPY_BUFFER_SIZE,MEMF_FAST)) == (char *)NULL)
			panic("couldn't allocate copy buffer");

		add_cleanup(cleanup_copy_buffer);
	}

	/* if nbytes of copying requested exceeds the virtual EOF (end of
	 * the chunk's metachunk), truncate the chunk
	 */
	if (nbytes > *filebytes_left_ptr)
	{
		fprintf(stderr,"copychunkbytes: chunk size exceeds size of superchunk - truncating\n");
		nbytes = *filebytes_left_ptr;
	}

	/* find out if the length of the chunk is odd or not - we'll need
	 * it later to see if we need to write a pad byte
	 */
	 odd = (nbytes & 1);

	/* do the copy, breaking it up into multiple COPY_BUFFER_SIZE sized
	 * portions, if neccessary */
	while (nbytes > 0)
	{
		copysize = (nbytes > COPY_BUFFER_SIZE) ? COPY_BUFFER_SIZE : nbytes;

		if (read(infd,copy_buffer,copysize) != copysize)
		{
			perror("copybytes input");
			fprintf(stderr,"archive is blown.\n");
			close(infd);
			return(0);
		}
		if (write(outfd,copy_buffer,copysize) != copysize)
		{
			perror("copybytes output");
			fprintf(stderr,"archive is blown.\n");
			close(infd);
			return(0);
		}
		/* update bytes left in chunk and in chunk's metachunk */
		nbytes -= copysize;
		*filebytes_left_ptr -= copysize;
	}

	/* done with copy - if number of bytes copied was odd, read and
	 * discard the pad byte, write out a pad byte and update the
	 * virtual EOF (end of chunk) */
	if (odd)
	{
		if (read(infd,copy_buffer,1) != 1)
			perror("copychunkbytes: failed to skip input byte");
		assert(*copy_buffer == '\0');
		write(outfd,copy_buffer,1);
		(*filebytes_left_ptr)--;
	}
}

/* create an archive by opening it, writing a CAT header, and returning the
 * file descriptor if it succeeded or -1 otherwise
 */
int create_archive(archive_name,subtype)
char *archive_name;
ULONG subtype;
{
	int archive_fd;

	if ((archive_fd = open(archive_name, O_RDWR|O_CREAT|O_TRUNC)) == -1)
	{
		perror(archive_name);
		return(-1);
	}

	if (!WriteCATheader(archive_fd))
	{
		fprintf("create_archive: couldn't write CAT chunkheader of new archive\n");
		return(-1);
	}

	/* success, return the archive fd */
	return(archive_fd);
}

/* end of create.c */

