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