/* 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 */
