244 lines
7.3 KiB
C++
244 lines
7.3 KiB
C++
/*----------------------------------------------------------------------*
|
|
* IFFW.C Support routines for writing IFF-85 files. 12/02/85
|
|
* (IFF is Interchange Format File.)
|
|
*
|
|
* By Jerry Morrison and Steve Shaw, Electronic Arts.
|
|
* This software is in the public domain.
|
|
*
|
|
* Original version was for the Commodore-Amiga computer.
|
|
* This version is compatible with PC, OSX, PS3, Wii, iPhone. 10/26/2008
|
|
*----------------------------------------------------------------------*/
|
|
#include "iff.h"
|
|
|
|
|
|
|
|
|
|
/* ---------- IFF Writer -----------------------------------------------*/
|
|
|
|
/* A macro to test if a chunk size is definite, i.e. not szNotYetKnown.*/
|
|
#define Known(size) ( (size) != szNotYetKnown )
|
|
|
|
/* Yet another weird macro to make the source code simpler...*/
|
|
#define IfIffp(expr) {if (iffp == IFF_OKAY) iffp = (expr);}
|
|
|
|
/* ---------- OpenWIFF -------------------------------------------------*/
|
|
|
|
IFFP OpenWIFF(BPTR file, GroupContext *new0, int limit)
|
|
{
|
|
register GroupContext *newtmp = new0;
|
|
register IFFP iffp = IFF_OKAY;
|
|
|
|
newtmp->parent = NULL;
|
|
newtmp->clientFrame = NULL;
|
|
newtmp->file = file;
|
|
newtmp->position = 0;
|
|
newtmp->bound = limit;
|
|
newtmp->ckHdr.ckID = NULL_CHUNK; /* indicates no current chunk */
|
|
newtmp->ckHdr.ckSize = newtmp->bytesSoFar = 0;
|
|
|
|
if (0 > Seek(file, 0, OFFSET_BEGINNING)) /* Go to start of the file.*/
|
|
iffp = DOS_ERROR;
|
|
else if ( Known(limit) && IS_ODD(limit) )
|
|
iffp = CLIENT_ERROR;
|
|
return(iffp);
|
|
}
|
|
|
|
/* ---------- StartWGroup ----------------------------------------------*/
|
|
IFFP StartWGroup( GroupContext* parent, int groupType,int groupSize,int subtype, GroupContext * newtmp)
|
|
{
|
|
register IFFP iffp;
|
|
|
|
iffp = PutCkHdr(parent, groupType, groupSize);
|
|
IfIffp( IFFWriteBytes(parent, (char *)&subtype, sizeof(int)) );
|
|
IfIffp( OpenWGroup(parent, newtmp) );
|
|
return(iffp);
|
|
}
|
|
|
|
/* ---------- OpenWGroup -----------------------------------------------*/
|
|
IFFP OpenWGroup(GroupContext *parent0,GroupContext* new0)
|
|
{
|
|
register GroupContext *parent = parent0;
|
|
register GroupContext *newtmp = new0;
|
|
register int ckEnd;
|
|
register IFFP iffp = IFF_OKAY;
|
|
|
|
newtmp->parent = parent;
|
|
newtmp->clientFrame = parent->clientFrame;
|
|
newtmp->file = parent->file;
|
|
newtmp->position = parent->position;
|
|
newtmp->bound = parent->bound;
|
|
newtmp->ckHdr.ckID = NULL_CHUNK;
|
|
newtmp->ckHdr.ckSize = newtmp->bytesSoFar = 0;
|
|
|
|
if ( Known(parent->ckHdr.ckSize) ) {
|
|
ckEnd = newtmp->position + ChunkMoreBytes(parent);
|
|
if ( newtmp->bound == szNotYetKnown || newtmp->bound > ckEnd )
|
|
newtmp->bound = ckEnd;
|
|
};
|
|
|
|
if ( parent->ckHdr.ckID == NULL_CHUNK || /* not currently writing a chunk*/
|
|
IS_ODD(newtmp->position) ||
|
|
(Known(newtmp->bound) && IS_ODD(newtmp->bound)) )
|
|
iffp = CLIENT_ERROR;
|
|
return(iffp);
|
|
}
|
|
|
|
/* ---------- CloseWGroup ----------------------------------------------*/
|
|
IFFP CloseWGroup(GroupContext *old0)
|
|
{
|
|
register GroupContext *old = old0;
|
|
|
|
if ( old->ckHdr.ckID != NULL_CHUNK ) /* didn't close the last chunk */
|
|
return(CLIENT_ERROR);
|
|
|
|
if ( old->parent == NULL ) { /* top level file context */
|
|
GWriteFlush(old->file);
|
|
}
|
|
else {
|
|
old->parent->bytesSoFar += old->position - old->parent->position;
|
|
old->parent->position = old->position;
|
|
};
|
|
return(IFF_OKAY);
|
|
}
|
|
|
|
/* ---------- EndWGroup ------------------------------------------------*/
|
|
IFFP EndWGroup(GroupContext *old)
|
|
{
|
|
register GroupContext *parent = old->parent;
|
|
register IFFP iffp;
|
|
|
|
|
|
iffp = CloseWGroup(old);
|
|
IfIffp( PutCkEnd(parent) );
|
|
return(iffp);
|
|
}
|
|
|
|
/* ---------- PutCk ----------------------------------------------------*/
|
|
IFFP PutCk(GroupContext *context, int ckID,int ckSize,char *data)
|
|
{
|
|
register IFFP iffp = IFF_OKAY;
|
|
|
|
if ( ckSize == szNotYetKnown )
|
|
iffp = CLIENT_ERROR;
|
|
IfIffp( PutCkHdr(context, ckID, ckSize) );
|
|
IfIffp( IFFWriteBytes(context, data, ckSize) );
|
|
IfIffp( PutCkEnd(context) );
|
|
return(iffp);
|
|
}
|
|
|
|
/* ---------- PutCkHdr -------------------------------------------------*/
|
|
IFFP PutCkHdr(GroupContext *context0, int ckID, int ckSize)
|
|
{
|
|
register GroupContext *context = context0;
|
|
int minPSize = sizeof(ChunkHeader); /* physical chunk >= minPSize bytes*/
|
|
|
|
/* CLIENT_ERROR if we're already inside a chunk or asked to write
|
|
* other than one FORM, LIST, or CAT at the top level of a file */
|
|
/* Also, non-positive int values are illegal and used for error codes.*/
|
|
/* (We could check for other illegal IDs...)*/
|
|
if ( context->ckHdr.ckID != NULL_CHUNK || ckID <= 0 )
|
|
return(CLIENT_ERROR);
|
|
else if (context->parent == NULL) {
|
|
switch (ckID) {
|
|
case FORM: case LIST: case CAT: break;
|
|
default: return(CLIENT_ERROR);
|
|
}
|
|
if (context->position != 0)
|
|
return(CLIENT_ERROR);
|
|
}
|
|
|
|
if ( Known(ckSize) ) {
|
|
if ( ckSize < 0 )
|
|
return(CLIENT_ERROR);
|
|
minPSize += ckSize;
|
|
};
|
|
if ( Known(context->bound) &&
|
|
context->position + minPSize > context->bound )
|
|
return(CLIENT_ERROR);
|
|
|
|
context->ckHdr.ckID = ckID;
|
|
context->ckHdr.ckSize = ckSize;
|
|
context->bytesSoFar = 0;
|
|
{
|
|
context->ckHdr.ckSize = endianSwap32(context->ckHdr.ckSize);
|
|
if (0 > fwrite(&context->ckHdr, 1,sizeof(ChunkHeader),context->file))
|
|
return(DOS_ERROR);
|
|
context->ckHdr.ckSize = endianSwap32(context->ckHdr.ckSize);
|
|
}
|
|
context->position += sizeof(ChunkHeader);
|
|
return(IFF_OKAY);
|
|
}
|
|
|
|
/* ---------- IFFWriteBytes ---------------------------------------------*/
|
|
IFFP IFFWriteBytes(GroupContext *context0, char *data, int nBytes)
|
|
{
|
|
register GroupContext *context = context0;
|
|
|
|
if ( context->ckHdr.ckID == NULL_CHUNK || /* not in a chunk */
|
|
nBytes < 0 || /* negative nBytes */
|
|
(Known(context->bound) && /* overflow context */
|
|
context->position + nBytes > context->bound) ||
|
|
(Known(context->ckHdr.ckSize) && /* overflow chunk */
|
|
context->bytesSoFar + nBytes > context->ckHdr.ckSize) )
|
|
{
|
|
return(CLIENT_ERROR);
|
|
}
|
|
|
|
if (0 > fwrite(data, 1,nBytes,context->file))
|
|
return(DOS_ERROR);
|
|
|
|
context->bytesSoFar += nBytes;
|
|
context->position += nBytes;
|
|
return(IFF_OKAY);
|
|
}
|
|
|
|
/* ---------- PutCkEnd -------------------------------------------------*/
|
|
IFFP PutCkEnd(GroupContext *context0)
|
|
{
|
|
register GroupContext *context = context0;
|
|
WORD zero = 0; /* padding source */
|
|
|
|
if ( context->ckHdr.ckID == NULL_CHUNK ) /* not in a chunk */
|
|
return(CLIENT_ERROR);
|
|
|
|
if ( context->ckHdr.ckSize == szNotYetKnown ) {
|
|
/* go back and set the chunk size to bytesSoFar */
|
|
int offset = context->bytesSoFar+sizeof(int);
|
|
if ( 0 > GSeek(context->file,
|
|
-(offset),
|
|
OFFSET_CURRENT))
|
|
{
|
|
return(DOS_ERROR);
|
|
} else
|
|
{
|
|
context->bytesSoFar = endianSwap32(context->bytesSoFar);
|
|
if (0 > fwrite(&context->bytesSoFar, 1,sizeof(int),context->file))
|
|
return (DOS_ERROR);
|
|
context->bytesSoFar = endianSwap32(context->bytesSoFar);
|
|
if (0 > GSeek(context->file, context->bytesSoFar, OFFSET_CURRENT) )
|
|
return (DOS_ERROR);
|
|
}
|
|
|
|
|
|
}
|
|
else { /* make sure the client wrote as many bytes as planned */
|
|
if ( context->ckHdr.ckSize != context->bytesSoFar )
|
|
return(CLIENT_ERROR);
|
|
};
|
|
|
|
/* Write a pad byte if needed to bring us up to an even boundary.
|
|
* Since the context end must be even, and since we haven't
|
|
* overwritten the context, if we're on an odd position there must
|
|
* be room for a pad byte. */
|
|
if ( IS_ODD(context->bytesSoFar) ) {
|
|
if ( 0 > fwrite(&zero, 1,1,context->file) )
|
|
return(DOS_ERROR);
|
|
context->position += 1;
|
|
};
|
|
|
|
context->ckHdr.ckID = NULL_CHUNK;
|
|
context->ckHdr.ckSize = context->bytesSoFar = 0;
|
|
return(IFF_OKAY);
|
|
}
|
|
|