
/* Copyright 1993 J. Remyn */
/* This program is freely distributable, but there is no warranty */
/* of any kind. */
/* version 0.3 */

#include <stdlib.h>
#include <stdio.h>
#ifdef INLINE_STRING
#include <linux/string.h>	/* noticeably faster; from kernel sources */
#else
#include <string.h>
#endif
#include <time.h>
#include <vga.h>

#include "general.h"
#include "fli.h"

#define NAMELEN 256

struct OPTIONS {
	char filename[NAMELEN];	/* filename given on command line */
	char fast;		/* 1 = play without delays */
	char verbose;		/* 1 = show info about flic file */
	char release;		/* 1 = don't keep frames in memory */
	char firstp;		/* 1 = process frames while loading */
	char blank;		/* 1 = keep black screen while loading */
	char speedf;		/* 1 = change fli.h.speed to options.speed */
	char stdinp;		/* 1 = take flic file from stdin */
	int speed;		/* speed gotten from command line */
	char playrepf;		/* 1 = play flic (options.playrep) times */
	int playrep;		/* nr of times to play animation */
} options;

struct FLI_FRAMECHUNK {
	long size;		/* Size of chunk data part */
	long file_offset;	/* Offset of chunk data part into flic file */
	int subchunks;		/* Number of subchunks in data part */
	void *cd;		/* Pointer to memory containing data part, */
				/* is NULL if data not kept in memory */
};

struct FLI {
	char filename[NAMELEN]; /* filename of fli file */
	char flc;		/* 0 = fli, 1 = flc */
	int mode;		/* vgalib mode number to be used */
	int paged;		/* 1 = paged screen mode */
	char playrepf;		/* 0 = play forever, 1 = use repcount */
	int playrep;		/* nr of times to play flic */
	int scr_width;		/* width of screen mode used */
	int scr_height;		/* guess */
	int current;		/* current frame index */
	long clock;		/* time of last processed frame in 1/100 sec */
	FILE *f;		/* file pointer of opened flic file */
	struct FLI_HEADER h;	/* 128 byte fli file header structure */
	struct FLI_FRAMECHUNK *frame;
				/* pointer to an allocated 'array' of 
				   <frames> FLI_FRAMECHUNK structs */
} fli;


int palette[3*256];		/* palette of fli, rgb values, 256 times... */


void dcd_color_64( void *data ) {
uchar start = 0;
int ops;
int count;
int i;
/*	puts( "color_64" ); */
	ops = *(short int *)data;
	(short int *)data += 1;
	while( ops-- > 0 ) {
		start += *(uchar *)data;
		(uchar *)data += 1;
		if( (count = (int)*(uchar *)data) == 0 )
			count = 256;
		(uchar *)data += 1;
		for( i=0; i<count; i++ ) {
			/* (s)vgalib requires a table of ints for setting a group
			 * of colors quickly, but we've got a table of chars :( 
			 * so we have to set each color individually (slow) */
			vga_setpalette( start + i, 
				*(uchar *)data,
				*((uchar *)data+1),
				*((uchar *)data+2)
			);
			(uchar *)data += 3;
		}
		start += count;
	}
}

void dcd_color_256( void *data ) {
uchar start = 0;
int ops;
int count;
int i;
/*	puts( "color_256" ); */
	ops = *(short int *)data;
	(short int *)data += 1; 
	while( ops-- > 0 ) {
		start += *(uchar *)data;
		(uchar *)data += 1;
		if( (count = (int)*(uchar *)data) == 0 )
			count = 256;
		(uchar *)data += 1;
		for( i=0; i<count; i++ ) {
			vga_setpalette( start + i, 
				*(uchar *)data >> 2,
				*((uchar *)data+1) >> 2,
				*((uchar *)data+2) >> 2
			);
			(uchar *)data += 3;
		}
		start += count;
	}
}

inline void *memsetw( unsigned short *target, unsigned short w, int count ) {
	while( count-->0 ) {
		*target++ = w;
	}
	return( target );
}

void dcd_delta_flc_paged( struct FLI *fli, void *data ) {
short int line;
short int nrlines;
int index;
short packets;
short type;
uchar lastbyte;
char setlastbyte;
int nextlineindex;
int page;
/*	puts( "delta flc" ); */
	/* get nr of lines in chunk */
	nrlines = *(short int *)data;
	(short int *)data += 1;
	line = 0;
	setlastbyte = 0;
	lastbyte = 0;	/* this is just to get rid of a compiler warning */
	index = 0;
	page = 0;
	nextlineindex = fli->scr_width;
	while( nrlines>0 ) { /* don't put nrlines-- here, the continues don't allow it */

		type = *(short *)data;
		(short *)data += 1;
		/* the 2 highest bits of type indicate how to interpret it */
		if( type<0 ) {
			if( type&0x4000 ) {
				/* add to the line number and act as if nothing
				 * happened.
				 * documentation says it's all right just to
				 * add the abs. value of the word, which is the
				 * same as subtracting the word as it is always
				 * negative.
				 */
				line -= type;
				index += fli->scr_width * (-type);
				nextlineindex += fli->scr_width * (-type);
				if( index>0xFFFF ) {
					page += index/0xFFFF;
					index &= 0xFFFF;
					nextlineindex &= 0xFFFF;
					vga_setpage( page );
				}
				continue;
			}
			/* the low byte contains a lastbyte (the last byte
			 * on a line in odd-width resolution flic files),
			 * this word is followed by the packet count
			 */
			setlastbyte = 1;
			lastbyte = (uchar)(type & 0x00FF);
			packets = *(short *)data;
			(short *)data += 1;
			/* packets can be 0 now if just the last byte
			 * changes for this line
			 */
		}
		if( type>=0 ) {
			/* the word contains the nr of packets
			 * we can just assign this to packets because the
			 * high bits are zero
			 */
			packets = type;
		}
		/* decode packets */
		if( nextlineindex>0xFFFF ) { /* page break comming up */
			while( packets-->0 ) {
				/* get & decode packet */
				index += *(uchar *)data;	/* skip */
				if( index > 0xFFFF ) {
					vga_setpage( ++page );
					index &= 0xFFFF;
					nextlineindex &= 0xFFFF;
				}
				(uchar *)data += 1;
				type = *(char *)data;
				(uchar *)data += 1;
				if( (char)type>=0 ) {
					/* copy type words */
					type <<= 1;
					if( index + type <= 0xFFFF ) {
						memcpy( graph_mem + index, data, type );
						index += type;
						(uchar *)data += type;
						continue;
					}
					memcpy( graph_mem + index, data, 0x10000 - index );
					vga_setpage( ++page );
					memcpy( graph_mem, data + (0x10000 - index), type - (0x10000 - index) );
					(uchar *)data += type;
					index = type - (0x10000 - index);
					nextlineindex &= 0xFFFF;
					continue;
				}
				type = -(char)type;
				if( index + type <= 0xFFFF ) {
					memsetw( (ushort *)(graph_mem + index), *(ushort *)data, type );
					index += type<<1;
					(ushort *)data += 1;
					continue;
				}
				memsetw( (ushort *)(graph_mem + index), *(ushort *)data, (0x10000 - index)>>1 );
				vga_setpage( ++page );
				memsetw( (ushort *)graph_mem, *(ushort *)data, (type - (0x10000 - index))>>1 );
				index = (index + (type<<1)) & 0xFFFF;
				nextlineindex &= 0xFFFF;
				(ushort *)data += 1;
				continue;
			}
		}
		else {
			while( packets-->0 ) {
				/* get & decode packet */
				index += *(uchar *)data;	/* skip */
				(uchar *)data += 1;
				type = *(char *)data;
				(uchar *)data += 1;
				if( (char)type>=0 ) {
					/* copy type words */
					type <<= 1;
					memcpy( graph_mem + index, data, type );
					index += type;
					(uchar *)data += type;
					continue;
				}
				type = -(char)type;
				memsetw( (ushort *)(graph_mem + index), *(ushort *)data, type );
				index += type<<1;
				(ushort *)data += 1;
			}
		}
		if( !setlastbyte ) {
			index = nextlineindex;
			nextlineindex += fli->scr_width;
			nrlines--;
			continue;
		}
		/* put lastbyte at end of line */
		*(graph_mem + nextlineindex - 1) = lastbyte;
		setlastbyte = 0;
		index = nextlineindex;
		nextlineindex += fli->scr_width;
		nrlines--;
	}
}


void dcd_delta_flc( struct FLI *fli, void *data ) {
short int line;
short int nrlines;
char *index;
short packets;
short type;
int x;
uchar lastbyte;
char setlastbyte;
/*	puts( "delta flc" ); */
	if( fli->paged ) {
		dcd_delta_flc_paged( fli, data );
		return;
	}
	/* get nr of lines in chunk */
	nrlines = *(short int *)data;
	(short int *)data += 1;
	line = 0;
	setlastbyte = 0;
	lastbyte = 0;	/* this is just to get rid of a compiler warning */
	index = graph_mem;
	
	while( nrlines>0 ) { /* don't put nrlines-- here, the continues don't allow it */

		type = *(short *)data;
		(short *)data += 1;
		/* the 2 highest bits of type indicate how to interpret it */
		if( type<0 ) {
			if( type&0x4000 ) {
				/* add to the line number and act as if nothing
				 * happened.
				 * documentation says it's all right just to
				 * add the abs. value of the word, which is the
				 * same as subtracting the word as it is always
				 * negative.
				 */
				line -= type;
				index += fli->scr_width * (-type);
				continue;
			}
			/* the low byte contains a lastbyte (the last byte
			 * on a line in odd-width resolution flic files),
			 * this word is followed by the packet count
			 */
			setlastbyte = 1;
			lastbyte = (uchar)(type & 0x00FF);
			packets = *(short *)data;
			(short *)data += 1;
			/* packets can be 0 now if just the last byte
			 * changes for this line
			 */
		}
		if( type>=0 ) {
			/* the word contains the nr of packets
			 * we can just assign this to packets because the
			 * high bits are zero
			 */
			packets = type;
		}

		/* decode packets */
		x = 0;
		while( packets-->0 ) {
			/* get & decode packet */
			x += *(uchar *)data;
			(uchar *)data += 1;
			type = *(char *)data;
			(uchar *)data += 1;
			if( (char)type>=0 ) {
				/* copy ptype words */
				type <<= 1;
				memcpy( index + x, data, type );
				x += type;
				(uchar *)data += type;
				continue;
			}
			type = -(char)type;
			memsetw( (ushort *)(index + x), *(ushort *)data, type );
			x += type<<1;
			(ushort *)data += 1;
		}
		if( !setlastbyte ) {
			(uchar *)index += fli->scr_width;
			nrlines--;
			continue;
		}
		/* put lastbyte at end of line */
		*(uchar *)(index + fli->scr_width - 1) = lastbyte;
		setlastbyte = 0;
		(uchar *)index += fli->scr_width;
		nrlines--;
	}
}


void dcd_delta_fli( struct FLI *fli, void *data ) {
short int line;
short int nrlines;
char *index;
uchar packets;
int index_x;
char type;
/*	puts( "delta fli" ); */
	line = *(short int *)data;
	(short int *)data += 1;
	index = graph_mem + line * fli->scr_width;
	nrlines = *(short int *)data;
	(short int *)data += 1;
	while( nrlines-- > 0 ) {
		index_x = 0;
		packets = *(uchar *)data;
		(uchar *)data += 1;
		while( packets > 0 ) {
			index_x += *(uchar *)data;
			(uchar *)data += 1;
			type = *(char *)data;
			(char *)data += 1;
			if( type >= 0 ) {
				memcpy( index + index_x, data, type );
				index_x += type;
				(uchar *)data += type;
				packets--;
				continue;
			}
			memset( index + index_x, *(uchar *)data, -type );
			index_x -= type;
			(uchar *)data += 1;
			packets--;
		}
		index += fli->scr_width;
	}
}

void dcd_byte_run_paged( struct FLI *fli, void *data ) {
/* this one should work for fli->h.width smaller than fli->scr_width */
int lines;
int width;
char type;
int index;
int index_x;
int page = 0;
	lines = fli->h.height;
	width = fli->h.width;
	index = 0;
	while( lines-->0 ) {
		if( index>0xFFFF ) {
			vga_setpage( ++page );
			index &= 0xFFFF;
		}
		(uchar *)data += 1;	/* skip byte containing number of packets (which may be incorrect) */
		/* start a loop to decode packets until end of line is reached */
		index_x = 0;
		if( (index + width)>0xFFFF ) {
			/* then there's a page break in the next flic line */
			while ( index_x < width ) {
				type = *(char *)data++;
				if( type<0 ) {
					/* type now contains -nr of bytes to copy to video */
					if( (index - type)>0xFFFF ) { /* page break in packet */
						memcpy( graph_mem + index, (uchar *)data, 
							0x10000 - index 
						);
						page++;
						vga_setpage(page);
						memcpy(graph_mem, (uchar *)data + 0x10000 - index, 
							( index - type ) & 0xFFFF
						);
						index = (index-type)&0xFFFF;
						(uchar *)data -= type;
					}
					else {	/* page break not quite here yet */
						memcpy( graph_mem + index, (uchar *)data, -type );
						index -= type;
						(uchar *)data -= type;
					}
					index_x -= type;
				}
				else {
					if( (index+type)>0xFFFF ) { /* page break in this packet */
						memset( graph_mem + index, *(uchar *)data, 
							0x10000 - index 
						);
						page++;
						vga_setpage(page);
						memset(graph_mem, *(uchar *)data++, 
							( index + type ) & 0xFFFF
						);
						index = (index+type)&0xFFFF;
					}
					else { /* page break not in this packet */
						memset( graph_mem + index, *(uchar *)data++, type );
						index += type;
					}
					index_x += type;
				}
				index += fli->scr_width - width;
			}
		}
		else {
			while ( index_x < width ) {
				type = *(char *)data++;
				if( type<0 ) {
					/* type now contains nr of bytes to copy to video */
					memcpy( graph_mem + index, (uchar *)data, -type );
					index -= type;
					index_x -= type;
					(uchar *)data -= type;
				}
				else {
					memset( graph_mem + index, *(uchar *)data++, type );
					index += type;
					index_x += type;
				}
			}
			index += fli->scr_width - width;
		}
	}
}


void dcd_byte_run( struct FLI *fli, void *data ) {
int lines;
int width;
char type;
int index;
int index_x;
/* 	puts( "byte run" ); */
	if( fli->paged ) {
		dcd_byte_run_paged( fli, data );
		return;
	}
	lines = fli->h.height;
	width = fli->h.width;
	index = 0;
	while( lines-->0 ) {
		(uchar *)data += 1;	/* skip byte containing number of packets */
		/* start a loop to decode packets until end of line is reached */
		index_x = 0;
			while ( index_x < width ) {
				type = *(char *)data++;
				if( type<0 ) {
					/* type now contains nr of bytes to copy to video */
					memcpy( graph_mem + index + index_x, (uchar *)data, -type );
					index_x -= type;
					(uchar *)data -= type;
				}
				else {
					memset( graph_mem + index + index_x, *(uchar *)data++, type );
					index_x += type;
				}
			}
			index += fli->scr_width;
	}
}


void dcd_black( struct FLI *fli, void *data ) {
/*	puts( "black" ); */
	if( fli->paged ) {
		int size;
		int page = 0;
		
		size = fli->scr_width * fli->scr_height;
		while( size>0 ) {
			if( size>0x10000 ) {
				memset( graph_mem, 0, 0x10000 );
				vga_setpage( ++page );
				size -= 0x10000;
			}
			else {
				memset( graph_mem, 0, size );
				size = 0;
			}
		}
	}
	else {
		int l;
		uchar *index;
		
		l = fli->h.height;
		index = graph_mem;
		while( l-- > 0 ) {
			memset( index, 0, fli->h.width );
			index += fli->scr_width;
		}
	}
}

void dcd_literal( struct FLI *fli, void *data ) {
int l;
/*	puts( "literal" ); */
	l = fli->h.height;
	if( fli->paged ) {
		/* bank switched video memory 
		 * (a reminder that the 64Kb segment nightmare is REAL :)
		 */
		int page;
		int index;
		int nextline;
		
		page = nextline = index = 0;
		while( l-- > 0 ) {
			nextline = index + fli->h.width;
			if( nextline>=0x10000 ) {
				memcpy( graph_mem + index, data, 0x10000 - index );
				(uchar *)data += 0x10000 - index;
				vga_setpage( ++page );
				memcpy( graph_mem, data, nextline &= 0xFFFF );
				(uchar *)data += nextline;
				index = nextline;
			}
			else {
				memcpy( graph_mem + index, data, fli->h.width );
				(uchar *)data += fli->h.width;
				index = nextline;
			}
		}
	}
	else {
		/* linear video memory */
		char *index;
		index = graph_mem;
		while( l-- > 0 ) {
			memcpy( index, data, fli->h.width );
			(uchar *)data += fli->h.width;
			index += fli->scr_width;
		}
	}
}

void dcd_pstamp( void *data ) {
/*	puts( "pstamp" ); */
}

void showhelp( void ) {
	puts( "FLI Player for Linux (version 0.2a) by John Remyn" );
	puts( "Usage:" );
	puts( " flip [switch] <filename>" );
	puts( "Valid switches are :" );
	puts( " -f          Switch off clock synchronization." );
	puts( " -v          Show information on flic file." );
	puts( " -? -h       Show help." );
	puts( " -a          Don't keep frames in memory." );
	puts( " -b          Process frames when they are loaded." );
	puts( " -c          Keep a blank screen while frames are being loaded." );
	puts( " -n <number> Play the animation sequence <n> times." );
	puts( " -s <delay>  Set delay between frames to <delay>*0.01 seconds." );
	puts( " -           Read flic file from standard input." );
	puts( "Press q to quit while playing." );
}
	
void parse_cmdln( int argc, char *argv[], struct OPTIONS *options ) {
int i,j;
	
	options->filename[0] = '\0';
	options->fast = 0;
	options->verbose = 0;
	options->release = 0;
	options->firstp = 0;
	options->blank = 0;
	options->speedf = 0;
	options->playrepf = 0;
	options->playrep = 1;
	options->speed = 0;
	options->stdinp = 0;
	for( i=1; i<argc; i++ ) {
		if( *argv[i]=='-' ) {
			char c;
			int k;
			/* argv[i] contains options */
			if( *(argv[i]+1)=='\0' ) {
				options->stdinp = 1;
			}
			j = 1;
			k = 1;
			while( (c = *(argv[i]+j))!='\0' ) {
				switch( c ) {
					case 'f' : options->fast = 1; break;
					case 'v' : options->verbose = 1; break;
					case 'a' : options->release = 1; break;
					case 'b' : options->firstp = 1; break;
					case 'c' : options->blank = 1; break;
					case '?' :
					case 'h' : showhelp(); exit( 0 ); break;
					case 'n' : options->playrepf = 1;
						   if( i+k<argc ) {
							options->playrep = abs( atoi( argv[i+k] ) );
							argv[i+k] = NULL;
						   }
						   k++;
						   break;
					case 's' : options->speedf = 1;
						   if( i+k<argc ) {
						   	options->speed = abs( atoi( argv[i+k] ) );
							argv[i+k] = NULL;
						   }
						   k++;
						   break;
					default  : showhelp(); 
						   puts( "Unknown option." );
						   exit( -1 );
				}
				j++;
			}
		}
		else {
			if( argv[i]!=NULL ) {
				/* argv[i] is the filename of the flic file */
				strncpy( options->filename, argv[i], NAMELEN );
			}
		}
	}
	if( options->filename[0]=='\0' && options->stdinp==0 ) {
		showhelp();
		puts( "No filename given." );
		exit( -1 );
	}
}

int open_fli( struct FLI *fli, struct OPTIONS *options ) {
	/* open file and read the header */
	if( !options->stdinp ) {
		fli->f = fopen( fli->filename, "rb" );
		if( !fli->f ) {
			return 0;
		}
	} 
	else {
		fli->f = stdin;
	}
	freadblock( fli->f, sizeof( fli->h.size      ), &(fli->h.size)      );
	freadblock( fli->f, sizeof( fli->h.type      ), &(fli->h.type)      );
	freadblock( fli->f, sizeof( fli->h.frames    ), &(fli->h.frames)    );
	freadblock( fli->f, sizeof( fli->h.width     ), &(fli->h.width)     );
	freadblock( fli->f, sizeof( fli->h.height    ), &(fli->h.height)    );
	freadblock( fli->f, sizeof( fli->h.depth     ), &(fli->h.depth)     );
	freadblock( fli->f, sizeof( fli->h.flags     ), &(fli->h.flags)     );
	freadblock( fli->f, sizeof( fli->h.speed     ), &(fli->h.speed)     );
	freadblock( fli->f, sizeof( fli->h.reserved0 ), &(fli->h.reserved0) );
	freadblock( fli->f, sizeof( fli->h.created   ), &(fli->h.created)   );
	freadblock( fli->f, sizeof( fli->h.creator   ), &(fli->h.creator)   );
	freadblock( fli->f, sizeof( fli->h.updated   ), &(fli->h.updated)   );
	freadblock( fli->f, sizeof( fli->h.updater   ), &(fli->h.updater)   );
	freadblock( fli->f, sizeof( fli->h.aspectx   ), &(fli->h.aspectx)   );
	freadblock( fli->f, sizeof( fli->h.aspecty   ), &(fli->h.aspecty)   );
	freadblock( fli->f, sizeof( fli->h.reserved1 ), &(fli->h.reserved1) );
	freadblock( fli->f, sizeof( fli->h.oframe1   ), &(fli->h.oframe1)   );
	freadblock( fli->f, sizeof( fli->h.oframe2   ), &(fli->h.oframe2)   );
	freadblock( fli->f, sizeof( fli->h.reserved2 ), &(fli->h.reserved2) );

	/* now check if it's a flc or a fli and take necessary precautions */
	if( (unsigned)fli->h.type==FLITYPE ) {
		/* it's a fli. convert as much as possible to flc */
		fli->flc = 0;
		/* convert frame rate from 1/70 to 1/100 */
		fli->h.speed *= 14;	/* 1/70 = 1.4/100 */
		fli->h.speed /= 10;
		/* ascpect ratio is ignored, but fix it anyway */
		fli->h.aspectx = 6;
		fli->h.aspecty = 5;
		/* can't convert frame offsets */
	}
	else if( (unsigned)fli->h.type==FLCTYPE ) {
		fli->flc = 1;
		fli->h.speed /= 10;	/* convert msec to 1/100 sec */
	}
	else {
		puts( "File type not recognized." );
		exit( -1 );
	}
	return 1;
};


void describe_fli( struct FLI *fli ) {
	printf( "Filename  %s\n",  fli->filename );
	printf( "size      %li\n", fli->h.size );
	printf( "type      %x\n",  fli->h.type );
	printf( "frames    %i\n",  fli->h.frames );
	printf( "width     %i\n",  fli->h.width );
	printf( "height    %i\n",  fli->h.height );
	printf( "depth     %i\n",  fli->h.depth );
	printf( "flags     %x\n",  fli->h.flags );
	printf( "speed     %li\n", fli->h.speed );
	printf( "aspectx   %i\n",  fli->h.aspectx );
	printf( "aspecty   %i\n",  fli->h.aspecty );
	printf( "oframe1   %li\n", fli->h.oframe1 );
	printf( "oframe2   %li\n", fli->h.oframe2 );
};

int get_fligrmode( struct FLI *fli ) {
/* possible modes :
	320x200
	640x480
	800x600
	1024x768
*/
int nrofmodes = 4;
int m_widths[] = { 320, 640, 800, 1024 };
int m_heights[] = { 200, 480, 600, 768 };
int m_numbers[] = { G320x200x256, G640x480x256, G800x600x256, G1024x768x256 };
int selected = 0;
int mode;
	while( (fli->h.width > m_widths[selected]) && (selected<nrofmodes) )
		selected++;
	while( (fli->h.height > m_heights[selected]) && (selected<nrofmodes) ) 
		selected++;
/*	printf( "selected mode : %ix%i\n", m_widths[selected], m_heights[selected] ); */
	if( selected==nrofmodes ) {
		mode = -1;
		printf( "resolution not supported by flip\n" );
	}
	else {
		if( vga_hasmode( m_numbers[selected] ) ) { 
			mode = m_numbers[selected];
/*			printf( "mode is available on this graphics card\n" ); */
		} 
		else {
			mode = -1;
			printf( "mode is not available on this graphics card\n" );
		}
	}
	return mode;
}

void readchunkheader( FILE *f, struct FLI_CHUNK_HEADER *buf ) {
	freadblock( f, sizeof( buf->size     ), &buf->size );
	freadblock( f, sizeof( buf->type     ), &buf->type );
	freadblock( f, sizeof( buf->chunks   ), &buf->chunks );
	freadblock( f, sizeof( buf->reserved ), &buf->reserved );
}
	
void scan_to_frame( struct FLI *fli ) {
struct FLI_CHUNK_HEADER buf;
struct FLI_FRAMECHUNK *fc;
	do {
		readchunkheader( fli->f, &buf );
		if( buf.type==0xF1FA ) break;
		fseek( fli->f, buf.size - FLI_CHUNK_HEADERLEN, SEEK_CUR );
	} while ( buf.type!=0xF1FA );
	fc = fli->frame + fli->current;
	fc->size = buf.size - FLI_CHUNK_HEADERLEN;
	fc->subchunks = buf.chunks;
	fc->file_offset = ftell( fli->f );
	fc->cd = NULL;
}

void getframechunkdata( struct FLI *fli ) {
struct FLI_FRAMECHUNK *fc;
void *data;
	fc = fli->frame + fli->current;
	data = fc->cd;
	if( data )
		return;	/* frame chunk data already loaded */
	data = malloc( fc->size );
	if( !data ) {
		printf( "cannot allocate memory for frame data\n" );
		exit( 1 );
	}
	fseek( fli->f, fc->file_offset, SEEK_SET );
	freadblock( fli->f, fc->size, data );
	fc->cd = data;
}

void releaseframechunkdata( struct FLI *fli ) {
struct FLI_FRAMECHUNK *fc;
	fc = fli->frame + fli->current;
	if( fc->cd ) {
		free( fc->cd );
		fc->cd = NULL;
	}
}
	
void processframechunk( struct FLI *fli ) {
struct FLI_FRAMECHUNK *fc;
void *data;
int i;
	if( fli->paged ) {
		vga_setpage( 0 );
	}
	fc = fli->frame + fli->current;
	data = fc->cd;
	while( clock()<(fli->h.speed + fli->clock) ) { }
	fli->clock = clock();
	for( i=0; i<fc->subchunks; i++ ) {
		/* switch( chunktype ) */
		switch( *((short int *)((char *)data+4)) ) {
			case COLOR_256 : 
				dcd_color_256( (char *)data + 6 );
				break;
			case DELTA_FLC :
				dcd_delta_flc( fli, (char *)data + 6 );
				break;
			case COLOR_64 :
				dcd_color_64( (char *)data + 6 );
				break;
			case DELTA_FLI :
				dcd_delta_fli( fli, (char *)data + 6 );
				break;
			case BLACK :
				dcd_black( fli, (char *)data + 6 );
				break;
			case BYTE_RUN :
				dcd_byte_run( fli, (char *)data + 6 );
				break;
			case LITERAL :
				dcd_literal( fli, (char *)data + 6 );
				break;
			case PSTAMP :
				dcd_pstamp( (char *)data + 6 );
				break;
			default :
				puts( "unknown subchunk" );
		}
		(char *)data += *(long *)data;
	}
}
		
	


void main( int argc, char *argv[] ) {
int quit = 0;
int playstartframe = 0;
	parse_cmdln( argc, argv, &options );
	strcpy( fli.filename, options.filename );

	/* open flic and get header information */	
	if( !open_fli( &fli, &options ) ) {
		puts( "cannot open fli file" );
		exit( 1 );
	}
	
	/* show header */
	if( options.verbose==1 ) {
		describe_fli( &fli );
	}

	/* optionally reduce delays to 0 */
	if( options.fast==1 ) {
		fli.h.speed = 0;
	}
	
	/* otionally set delays */
	if( options.speedf ) {
		fli.h.speed = options.speed;
	}

	/* optionally reduce delays to 0 (this overrides the set delay option) */
	if( options.fast==1 ) {
		fli.h.speed = 0;
	}
	
	fli.playrepf = options.playrepf;
	fli.playrep = options.playrep;
	if( fli.playrepf && fli.playrep<=0 ) {
		/* silly but might happen */
		exit( 0 );
	}
	
	/* determine graphics mode to use */
	vga_disabledriverreport();
	if ((fli.mode = get_fligrmode( &fli )) == -1)
		exit(-1);
	
	/* set mode */
	vga_setmode( fli.mode );
	fli.scr_width = vga_getxdim();
	fli.scr_height = vga_getydim();
	if( fli.scr_width*fli.scr_height>65536 ) 
		fli.paged = 1;
	else
		fli.paged = 0;

	fli.frame = (struct FLI_FRAMECHUNK *)malloc( fli.h.frames * sizeof( struct FLI_FRAMECHUNK ) );
	if( fli.frame==NULL ) {
		puts( "cannot allocate enough memory to store frame information\n" );
		exit( 1 );
	}

	fli.current = 0;
	fli.clock = clock();
	/* preloading */
	/* first the 1st frame which is a full screen frame */
	scan_to_frame( &fli );

	if( !options.blank ) {
		getframechunkdata( &fli );
		processframechunk( &fli );
		releaseframechunkdata( &fli );
		playstartframe = 1;
	}
	else {
		getframechunkdata( &fli );
		playstartframe = 0;
		if( options.release ) {
			releaseframechunkdata( &fli );
		}
	}
	fli.current++;
	while( fli.current<=fli.h.frames && !quit ) {
		scan_to_frame( &fli );
		getframechunkdata( &fli );
		if( options.firstp ) {
			processframechunk( &fli );
		}
		if( options.release ) {
			releaseframechunkdata( &fli );
		}
		fli.current++;
		if( !options.stdinp ) {
			if( getkey()=='q' ) {
				quit = 1;
			}
		}
	}
	
	if( options.firstp && options.playrepf ) {
		options.playrep--;
		if( options.playrep==0 ) {
			quit = 1;
		}
	}
		
	fli.current = playstartframe;
	while( !quit ) {
		getframechunkdata( &fli );
		processframechunk( &fli );
		if( options.release ) {
			releaseframechunkdata( &fli );
		}
		fli.current++;
		if( fli.current>fli.h.frames ) {
			fli.current = 1;
			if( options.playrepf ) {
				options.playrep--;
				if( options.playrep==0 ) {
					quit = 1;
				}
			}
		}
		if( !options.stdinp ) {
			if( getkey()=='q' ) {
				quit = 1;
			}
		}
	}
		
	/* restore textmode */
	vga_setmode( TEXT );

	/* close flic */
	if( !options.stdinp ) {
		fclose( fli.f );
	}
	fli.f = NULL;
};

	

