Commit message: Bug 739133 - (CVE-2017-17785) Heap overflow while parsing FLI files.

It is possible to trigger a heap overflow while parsing FLI files. The
RLE decoder is vulnerable to out of boundary writes due to lack of
boundary checks.

The variable "framebuf" points to a memory area which was allocated
with fli_header->width * fli_header->height bytes. The RLE decoder
therefore must never write beyond that limit.

If an illegal frame is detected, the parser won't stop, which means
that the next valid sequence is properly parsed again. This should
allow GIMP to parse FLI files as good as possible even if they are
broken by an attacker or by accident.

While at it, I changed the variable xc to be of type size_t, because
the multiplication of width and height could overflow a 16 bit type.

Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
CVE: CVE-2017-17785
CWE: CWE-787
Vulnerability description: In GIMP 2.8.22, there is a heap-based buffer overflow in the fli_read_brun function in plug-ins/file-fli/fli.c.

Project: GIMP

void fli_read_lc_2(FILE *f, s_fli_header *fli_header, unsigned char *old_framebuf, unsigned char *framebuf)
{
	unsigned short yc, lc, numline;
	unsigned char *pos;
	memcpy(framebuf, old_framebuf, fli_header->width * fli_header->height);
	yc=0;
	numline = fli_read_short(f);
	for (lc=0; lc < numline; lc++) {
		unsigned short pc, pcnt, lpf, lpn;
		size_t n, xc;
		pc=fli_read_short(f);
		lpf=0; lpn=0;
		while (pc & 0x8000) {
			if (pc & 0x4000) {
				yc+=-(signed short)pc;
			} else {
				lpf=1;lpn=pc&0xFF;
			}
			pc=fli_read_short(f);
		}
		yc=MIN(yc, fli_header->height);
		xc=0;
		pos=framebuf+(fli_header->width * yc);
		n=(size_t)fli_header->width * (fli_header->height-yc);
		for (pcnt=pc; pcnt>0; pcnt--) {
			unsigned short ps,skip;
			skip=fli_read_char(f);
			ps=fli_read_char(f);
			xc+=MIN(n-xc,skip);
			if (ps & 0x80) {
				unsigned char v1,v2;
				ps=-(signed char)ps;
				v1=fli_read_char(f);
				v2=fli_read_char(f);
				while (ps>0 && xc+1<n) {
					pos[xc++]=v1;
					pos[xc++]=v2;
					ps--;
				}
			} else {
				size_t len;
				len=MIN((n-xc)/2,ps);
				fread(&(pos[xc]), len, 2, f);
				xc+=len << 1;
			}
		}
		if (lpf) pos[xc]=lpn;
		yc++;
	}
}

Project: GIMP

void fli_write_black(FILE *f, s_fli_header *fli_header, unsigned char *framebuf)
{
	s_fli_chunk chunk;

	chunk.size=6;
	chunk.magic=FLI_BLACK;

	fli_write_long(f, chunk.size);
	fli_write_short(f, chunk.magic);
}

Project: GIMP

static void fli_write_short(FILE *f, unsigned short w)
{
	unsigned char b[2];
	b[0]=w&255;
	b[1]=(w>>8)&255;
	fwrite(&b,1,2,f);
}

Project: GIMP

void fli_read_copy(FILE *f, s_fli_header *fli_header, unsigned char *framebuf)
{
	fread(framebuf, fli_header->width, fli_header->height, f);
}

Project: GIMP

static void fli_write_long(FILE *f, unsigned long l)
{
	unsigned char b[4];
	b[0]=l&255;
	b[1]=(l>>8)&255;
	b[2]=(l>>16)&255;
	b[3]=(l>>24)&255;
	fwrite(&b,1,4,f);
}

Project: GIMP

void fli_write_lc(FILE *f, s_fli_header *fli_header, unsigned char *old_framebuf, unsigned char *framebuf)
{
	unsigned long chunkpos;
	s_fli_chunk chunk;
	unsigned short yc, firstline, numline, lastline;
	unsigned char *linebuf, *old_linebuf;

	chunkpos=ftell(f);
	fseek(f, chunkpos+6, SEEK_SET);

	/* first check, how many lines are unchanged at the beginning */
	firstline=0;
	while ((memcmp(old_framebuf+(firstline*fli_header->width), framebuf+(firstline*fli_header->width), fli_header->width)==0) && (firstline<fli_header->height)) firstline++;

	/* then check from the end, how many lines are unchanged */
	if (firstline<fli_header->height) {
		lastline=fli_header->height-1;
		while ((memcmp(old_framebuf+(lastline*fli_header->width), framebuf+(lastline*fli_header->width), fli_header->width)==0) && (lastline>firstline)) lastline--;
		numline=(lastline-firstline)+1;
	} else {
		numline=0;
	}
	if (numline==0) firstline=0;

	fli_write_short(f, firstline);
	fli_write_short(f, numline);

	for (yc=0; yc < numline; yc++) {
		unsigned short xc, sc, cc, tc;
		unsigned long linepos, lineend, bc;
		linepos=ftell(f); bc=0;
		fseek(f, 1, SEEK_CUR);

		linebuf=framebuf + ((firstline+yc)*fli_header->width);
		old_linebuf=old_framebuf + ((firstline+yc)*fli_header->width);
		xc=0;
		while (xc < fli_header->width) {
			sc=0;
			while ((linebuf[xc]==old_linebuf[xc]) && (xc<fli_header->width) && (sc<255)) {
				xc++; sc++;
			}
			fli_write_char(f, sc);
			cc=1;
			while ((linebuf[xc]==linebuf[xc+cc]) && ((xc+cc)<fli_header->width) && (cc<120)) {
				cc++;
			}
			if (cc>2) {
				bc++;
				fli_write_char(f, (cc-1)^0xFF);
				fli_write_char(f, linebuf[xc]);
				xc+=cc;
			} else {
				tc=0;
				do {
					sc=0;
					while ((linebuf[tc+xc+sc]==old_linebuf[tc+xc+sc]) && ((tc+xc+sc)<fli_header->width) && (sc<5)) {
						sc++;
					}
					cc=1;
					while ((linebuf[tc+xc]==linebuf[tc+xc+cc]) && ((tc+xc+cc)<fli_header->width) && (cc<10)) {
						cc++;
					}
					tc++;
				} while ((tc<120) && (cc<9) && (sc<4) && ((xc+tc)<fli_header->width));
				bc++;
				fli_write_char(f, tc);
				fwrite(linebuf+xc, tc, 1, f);
				xc+=tc;
			}
		}
		lineend=ftell(f);
		fseek(f, linepos, SEEK_SET);
		fli_write_char(f, bc);
		fseek(f, lineend, SEEK_SET);
	}

	chunk.size=ftell(f)-chunkpos;
	chunk.magic=FLI_LC;

	fseek(f, chunkpos, SEEK_SET);
	fli_write_long(f, chunk.size);
	fli_write_short(f, chunk.magic);

	if (chunk.size & 1) chunk.size++;
	fseek(f,chunkpos+chunk.size,SEEK_SET);
}

Project: GIMP

int fli_write_color(FILE *f, s_fli_header *fli_header, unsigned char *old_cmap, unsigned char *cmap)
{
	unsigned long chunkpos;
	unsigned short num_packets;
	s_fli_chunk chunk;
	chunkpos=ftell(f);
	fseek(f, chunkpos+8, SEEK_SET);
	num_packets=0;
	if (old_cmap==NULL) {
		unsigned short col_pos;
		num_packets=1;
		fli_write_char(f, 0); /* skip no color */
		fli_write_char(f, 0); /* 256 color */
		for (col_pos=0; col_pos<768; col_pos++) {
			fli_write_char(f, cmap[col_pos]>>2);
		}
	} else {
		unsigned short cnt_skip, cnt_col, col_pos, col_start;
		col_pos=0;
		do {
			cnt_skip=0;
			while ((col_pos<256) && (old_cmap[col_pos*3+0]==cmap[col_pos*3+0]) && (old_cmap[col_pos*3+1]==cmap[col_pos*3+1]) && (old_cmap[col_pos*3+2]==cmap[col_pos*3+2])) {
				cnt_skip++; col_pos++;
			}
			col_start=col_pos*3;
			cnt_col=0;
			while ((col_pos<256) && !((old_cmap[col_pos*3+0]==cmap[col_pos*3+0]) && (old_cmap[col_pos*3+1]==cmap[col_pos*3+1]) && (old_cmap[col_pos*3+2]==cmap[col_pos*3+2]))) {
				cnt_col++; col_pos++;
			}
			if (cnt_col>0) {
				num_packets++;
				fli_write_char(f, cnt_skip & 255);
				fli_write_char(f, cnt_col & 255);
				while (cnt_col>0) {
					fli_write_char(f, cmap[col_start++]>>2);
					fli_write_char(f, cmap[col_start++]>>2);
					fli_write_char(f, cmap[col_start++]>>2);
					cnt_col--;
				}
			}
		} while (col_pos<256);
	}

	if (num_packets>0) {
		chunk.size=ftell(f)-chunkpos;
		chunk.magic=FLI_COLOR;

		fseek(f, chunkpos, SEEK_SET);
		fli_write_long(f, chunk.size);
		fli_write_short(f, chunk.magic);
		fli_write_short(f, num_packets);

		if (chunk.size & 1) chunk.size++;
		fseek(f,chunkpos+chunk.size,SEEK_SET);
		return 1;
	}
	fseek(f,chunkpos, SEEK_SET);
	return 0;
}

Project: GIMP

static unsigned long fli_read_long(FILE *f)
{
	unsigned char b[4];
	fread(&b,1,4,f);
	return (unsigned long)(b[3]<<24) | (b[2]<<16) | (b[1]<<8) | b[0];
}

Project: GIMP

void fli_read_black(FILE *f, s_fli_header *fli_header, unsigned char *framebuf)
{
	memset(framebuf, 0, fli_header->width * fli_header->height);
}

Project: GIMP

void fli_write_header(FILE *f, s_fli_header *fli_header)
{
	fli_header->filesize=ftell(f);
	fseek(f, 0, SEEK_SET);
	fli_write_long(f, fli_header->filesize);	/* 0 */
	fli_write_short(f, fli_header->magic);	/* 4 */
	fli_write_short(f, fli_header->frames);	/* 6 */
	fli_write_short(f, fli_header->width);	/* 8 */
	fli_write_short(f, fli_header->height);	/* 10 */
	fli_write_short(f, fli_header->depth);	/* 12 */
	fli_write_short(f, fli_header->flags);	/* 14 */
	if (fli_header->magic == HEADER_FLI) {
		/* FLI saves speed in 1/70s */
		fli_write_short(f, fli_header->speed / 14);	/* 16 */
	} else {
		if (fli_header->magic == HEADER_FLC) {
			/* FLC saves speed in 1/1000s */
			fli_write_long(f, fli_header->speed);	/* 16 */
			fseek(f, 80, SEEK_SET);
			fli_write_long(f, fli_header->oframe1);	/* 80 */
			fli_write_long(f, fli_header->oframe2);	/* 84 */
		} else {
			fprintf(stderr, "error: magic number in header is wrong !\n");
		}
	}
}

Project: GIMP

static unsigned short fli_read_short(FILE *f)
{
	unsigned char b[2];
	fread(&b,1,2,f);
	return (unsigned short)(b[1]<<8) | b[0];
}

Project: GIMP

static unsigned char fli_read_char(FILE *f)
{
	unsigned char b;
	fread(&b,1,1,f);
	return b;
}

Project: GIMP

void fli_write_frame(FILE *f, s_fli_header *fli_header, unsigned char *old_framebuf, unsigned char *old_cmap, unsigned char *framebuf, unsigned char *cmap, unsigned short codec_mask)
{
	s_fli_frame fli_frame;
	unsigned long framepos, frameend;
	framepos=ftell(f);
	fseek(f, framepos+16, SEEK_SET);

	switch (fli_header->frames) {
		case 0: fli_header->oframe1=framepos; break;
		case 1: fli_header->oframe2=framepos; break;
	}

	fli_frame.size=0;
	fli_frame.magic=FRAME;
	fli_frame.chunks=0;

	/*
	 * create color chunk
	 */
	if (fli_header->magic == HEADER_FLI) {
		if (fli_write_color(f, fli_header, old_cmap, cmap)) fli_frame.chunks++;
	} else {
		if (fli_header->magic == HEADER_FLC) {
			if (fli_write_color_2(f, fli_header, old_cmap, cmap)) fli_frame.chunks++;
		} else {
			fprintf(stderr, "error: magic number in header is wrong !\n");
		}
	}

#if 0
	if (codec_mask & W_COLOR) {
		if (fli_write_color(f, fli_header, old_cmap, cmap)) fli_frame.chunks++;
	}
	if (codec_mask & W_COLOR_2) {
		if (fli_write_color_2(f, fli_header, old_cmap, cmap)) fli_frame.chunks++;
	}
#endif
	/* create bitmap chunk */
	if (old_framebuf==NULL) {
		fli_write_brun(f, fli_header, framebuf);
	} else {
		fli_write_lc(f, fli_header, old_framebuf, framebuf);
	}
	fli_frame.chunks++;

	frameend=ftell(f);
	fli_frame.size=frameend-framepos;
	fseek(f, framepos, SEEK_SET);
	fli_write_long(f, fli_frame.size);
	fli_write_short(f, fli_frame.magic);
	fli_write_short(f, fli_frame.chunks);
	fseek(f, frameend, SEEK_SET);
	fli_header->frames++;
}

Project: GIMP

void fli_write_brun(FILE *f, s_fli_header *fli_header, unsigned char *framebuf)
{
	unsigned long chunkpos;
	s_fli_chunk chunk;
	unsigned short yc;
	unsigned char *linebuf;

	chunkpos=ftell(f);
	fseek(f, chunkpos+6, SEEK_SET);

	for (yc=0; yc < fli_header->height; yc++) {
		unsigned short xc, t1, pc, tc;
		unsigned long linepos, lineend, bc;
		linepos=ftell(f); bc=0;
		fseek(f, 1, SEEK_CUR);
		linebuf=framebuf + (yc*fli_header->width);
		xc=0; tc=0; t1=0;
		while (xc < fli_header->width) {
			pc=1;
			while ((pc<120) && ((xc+pc)<fli_header->width) && (linebuf[xc+pc] == linebuf[xc])) {
				pc++;
			}
			if (pc>2) {
				if (tc>0) {
					bc++;
					fli_write_char(f, (tc-1)^0xFF);
					fwrite(linebuf+t1, 1, tc, f);
					tc=0;
				}
				bc++;
				fli_write_char(f, pc);
				fli_write_char(f, linebuf[xc]);
				t1=xc+pc;
			} else {
				tc+=pc;
				if (tc>120) {
					bc++;
					fli_write_char(f, (tc-1)^0xFF);
					fwrite(linebuf+t1, 1, tc, f);
					tc=0;
					t1=xc+pc;
				}
			}
			xc+=pc;
		}
		if (tc>0) {
			bc++;
			fli_write_char(f, (tc-1)^0xFF);
			fwrite(linebuf+t1, 1, tc, f);
			tc=0;
		}
		lineend=ftell(f);
		fseek(f, linepos, SEEK_SET);
		fli_write_char(f, bc);
		fseek(f, lineend, SEEK_SET);
	}

	chunk.size=ftell(f)-chunkpos;
	chunk.magic=FLI_BRUN;

	fseek(f, chunkpos, SEEK_SET);
	fli_write_long(f, chunk.size);
	fli_write_short(f, chunk.magic);

	if (chunk.size & 1) chunk.size++;
	fseek(f,chunkpos+chunk.size,SEEK_SET);
}

Project: GIMP

void fli_read_brun(FILE *f, s_fli_header *fli_header, unsigned char *framebuf)
{
	unsigned short yc;
	unsigned char *pos;
	for (yc=0; yc < fli_header->height; yc++) {
		unsigned short pc, pcnt;
		size_t n, xc;
		pc=fli_read_char(f);
		xc=0;
		pos=framebuf+(fli_header->width * yc);
		n=(size_t)fli_header->width * (fli_header->height-yc);
		for (pcnt=pc; pcnt>0; pcnt--) {
			unsigned short ps;
			ps=fli_read_char(f);
			if (ps & 0x80) {
				unsigned short len;
				for (len=-(signed char)ps; len>0 && xc<n; len--) {
					pos[xc++]=fli_read_char(f);
				}
			} else {
				unsigned char val;
				size_t len;
				len=MIN(n-xc,ps);
				val=fli_read_char(f);
				memset(&(pos[xc]), val, len);
				xc+=len;
			}
		}
	}
}

Project: GIMP

void fli_read_header(FILE *f, s_fli_header *fli_header)
{
	fli_header->filesize=fli_read_long(f);	/* 0 */
	fli_header->magic=fli_read_short(f);	/* 4 */
	fli_header->frames=fli_read_short(f);	/* 6 */
	fli_header->width=fli_read_short(f);	/* 8 */
	fli_header->height=fli_read_short(f);	/* 10 */
	fli_header->depth=fli_read_short(f);	/* 12 */
	fli_header->flags=fli_read_short(f);	/* 14 */
	if (fli_header->magic == HEADER_FLI) {
		/* FLI saves speed in 1/70s */
		fli_header->speed=fli_read_short(f)*14;		/* 16 */
	} else {
		if (fli_header->magic == HEADER_FLC) {
			/* FLC saves speed in 1/1000s */
			fli_header->speed=fli_read_long(f);	/* 16 */
		} else {
			fprintf(stderr, "error: magic number is wrong !\n");
			fli_header->magic = NO_HEADER;
		}
	}

	if (fli_header->width == 0)
	  fli_header->width = 320;

	if (fli_header->height == 0)
	  fli_header->height = 200;
}

Project: GIMP

void fli_read_color_2(FILE *f, s_fli_header *fli_header, unsigned char *old_cmap, unsigned char *cmap)
{
	unsigned short num_packets, cnt_packets, col_pos;
	num_packets=fli_read_short(f);
	col_pos=0;
	for (cnt_packets=num_packets; cnt_packets>0; cnt_packets--) {
		unsigned short skip_col, num_col, col_cnt;
		skip_col=fli_read_char(f);
		num_col=fli_read_char(f);
		if (num_col == 0) {
			for (col_pos=0; col_pos<768; col_pos++) {
				cmap[col_pos]=fli_read_char(f);
			}
			return;
		}
		for (col_cnt=skip_col; (col_cnt>0) && (col_pos<768); col_cnt--) {
			cmap[col_pos]=old_cmap[col_pos];col_pos++;
			cmap[col_pos]=old_cmap[col_pos];col_pos++;
			cmap[col_pos]=old_cmap[col_pos];col_pos++;
		}
		for (col_cnt=num_col; (col_cnt>0) && (col_pos<768); col_cnt--) {
			cmap[col_pos++]=fli_read_char(f);
			cmap[col_pos++]=fli_read_char(f);
			cmap[col_pos++]=fli_read_char(f);
		}
	}
}

Project: GIMP

void fli_read_lc(FILE *f, s_fli_header *fli_header, unsigned char *old_framebuf, unsigned char *framebuf)
{
	unsigned short yc, firstline, numline;
	unsigned char *pos;
	memcpy(framebuf, old_framebuf, fli_header->width * fli_header->height);
	firstline = fli_read_short(f);
	numline = fli_read_short(f);
	if (numline > fli_header->height || fli_header->height-numline < firstline)
		return;

	for (yc=0; yc < numline; yc++) {
		unsigned short pc, pcnt;
		size_t n, xc;
		pc=fli_read_char(f);
		xc=0;
		pos=framebuf+(fli_header->width * (firstline+yc));
		n=(size_t)fli_header->width * (fli_header->height-firstline-yc);
		for (pcnt=pc; pcnt>0; pcnt--) {
			unsigned short ps,skip;
			skip=fli_read_char(f);
			ps=fli_read_char(f);
			xc+=MIN(n-xc,skip);
			if (ps & 0x80) {
				unsigned char val;
				size_t len;
				ps=-(signed char)ps;
				val=fli_read_char(f);
				len=MIN(n-xc,ps);
				memset(&(pos[xc]), val, len);
				xc+=len;
			} else {
				size_t len;
				len=MIN(n-xc,ps);
				fread(&(pos[xc]), len, 1, f);
				xc+=len;
			}
		}
	}
}

Project: GIMP

void fli_write_copy(FILE *f, s_fli_header *fli_header, unsigned char *framebuf)
{

	unsigned long chunkpos;
	s_fli_chunk chunk;
	chunkpos=ftell(f);
	fseek(f, chunkpos+6, SEEK_SET);
	fwrite(framebuf, fli_header->width, fli_header->height, f);
	chunk.size=ftell(f)-chunkpos;
	chunk.magic=FLI_COPY;

	fseek(f, chunkpos, SEEK_SET);
	fli_write_long(f, chunk.size);
	fli_write_short(f, chunk.magic);

	if (chunk.size & 1) chunk.size++;
	fseek(f,chunkpos+chunk.size,SEEK_SET);
}

Project: GIMP

void fli_read_color(FILE *f, s_fli_header *fli_header, unsigned char *old_cmap, unsigned char *cmap)
{
	unsigned short num_packets, cnt_packets, col_pos;
	col_pos=0;
	num_packets=fli_read_short(f);
	for (cnt_packets=num_packets; cnt_packets>0; cnt_packets--) {
		unsigned short skip_col, num_col, col_cnt;
		skip_col=fli_read_char(f);
		num_col=fli_read_char(f);
		if (num_col==0) {
			for (col_pos=0; col_pos<768; col_pos++) {
				cmap[col_pos]=fli_read_char(f)<<2;
			}
			return;
		}
		for (col_cnt=skip_col; (col_cnt>0) && (col_pos<768); col_cnt--) {
			cmap[col_pos]=old_cmap[col_pos];col_pos++;
			cmap[col_pos]=old_cmap[col_pos];col_pos++;
			cmap[col_pos]=old_cmap[col_pos];col_pos++;
		}
		for (col_cnt=num_col; (col_cnt>0) && (col_pos<768); col_cnt--) {
			cmap[col_pos++]=fli_read_char(f)<<2;
			cmap[col_pos++]=fli_read_char(f)<<2;
			cmap[col_pos++]=fli_read_char(f)<<2;
		}
	}
}

Project: GIMP

static void fli_write_char(FILE *f, unsigned char b)
{
	fwrite(&b,1,1,f);
}

Project: GIMP

int fli_write_color_2(FILE *f, s_fli_header *fli_header, unsigned char *old_cmap, unsigned char *cmap)
{
	unsigned long chunkpos;
	unsigned short num_packets;
	s_fli_chunk chunk;
	chunkpos=ftell(f);
	fseek(f, chunkpos+8, SEEK_SET);
	num_packets=0;
	if (old_cmap==NULL) {
		unsigned short col_pos;
		num_packets=1;
		fli_write_char(f, 0); /* skip no color */
		fli_write_char(f, 0); /* 256 color */
		for (col_pos=0; col_pos<768; col_pos++) {
			fli_write_char(f, cmap[col_pos]);
		}
	} else {
		unsigned short cnt_skip, cnt_col, col_pos, col_start;
		col_pos=0;
		do {
			cnt_skip=0;
			while ((col_pos<256) && (old_cmap[col_pos*3+0]==cmap[col_pos*3+0]) && (old_cmap[col_pos*3+1]==cmap[col_pos*3+1]) && (old_cmap[col_pos*3+2]==cmap[col_pos*3+2])) {
				cnt_skip++; col_pos++;
			}
			col_start=col_pos*3;
			cnt_col=0;
			while ((col_pos<256) && !((old_cmap[col_pos*3+0]==cmap[col_pos*3+0]) && (old_cmap[col_pos*3+1]==cmap[col_pos*3+1]) && (old_cmap[col_pos*3+2]==cmap[col_pos*3+2]))) {
				cnt_col++; col_pos++;
			}
			if (cnt_col>0) {
				num_packets++;
				fli_write_char(f, cnt_skip);
				fli_write_char(f, cnt_col);
				while (cnt_col>0) {
					fli_write_char(f, cmap[col_start++]);
					fli_write_char(f, cmap[col_start++]);
					fli_write_char(f, cmap[col_start++]);
					cnt_col--;
				}
			}
		} while (col_pos<256);
	}

	if (num_packets>0) {
		chunk.size=ftell(f)-chunkpos;
		chunk.magic=FLI_COLOR_2;

		fseek(f, chunkpos, SEEK_SET);
		fli_write_long(f, chunk.size);
		fli_write_short(f, chunk.magic);
		fli_write_short(f, num_packets);

		if (chunk.size & 1) chunk.size++;
		fseek(f,chunkpos+chunk.size,SEEK_SET);
		return 1;
	}
	fseek(f,chunkpos, SEEK_SET);
	return 0;
}

Project: GIMP

void fli_read_frame(FILE *f, s_fli_header *fli_header, unsigned char *old_framebuf, unsigned char *old_cmap, unsigned char *framebuf, unsigned char *cmap)
{
	s_fli_frame fli_frame;
	unsigned long framepos;
	int c;
	framepos=ftell(f);

	fli_frame.size=fli_read_long(f);
	fli_frame.magic=fli_read_short(f);
	fli_frame.chunks=fli_read_short(f);

	if (fli_frame.magic == FRAME) {
		fseek(f, framepos+16, SEEK_SET);
		for (c=0;c<fli_frame.chunks;c++) {
			s_fli_chunk chunk;
			unsigned long chunkpos;
			chunkpos = ftell(f);
			chunk.size=fli_read_long(f);
			chunk.magic=fli_read_short(f);
			switch (chunk.magic) {
				case FLI_COLOR:   fli_read_color(f, fli_header, old_cmap, cmap); break;
				case FLI_COLOR_2: fli_read_color_2(f, fli_header, old_cmap, cmap); break;
				case FLI_BLACK:	  fli_read_black(f, fli_header, framebuf); break;
				case FLI_BRUN:    fli_read_brun(f, fli_header, framebuf); break;
				case FLI_COPY:    fli_read_copy(f, fli_header, framebuf); break;
				case FLI_LC:      fli_read_lc(f, fli_header, old_framebuf, framebuf); break;
				case FLI_LC_2:    fli_read_lc_2(f, fli_header, old_framebuf, framebuf); break;
				case FLI_MINI:	  /* unused, skip */ break;
				default: /* unknown, skip */ break;
			}
			if (chunk.size & 1) chunk.size++;
			fseek(f,chunkpos+chunk.size,SEEK_SET);
		}
	} /* else: unknown, skip */
	fseek(f, framepos+fli_frame.size, SEEK_SET);
}