#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

unsigned short htoi(unsigned char inchar);

unsigned short state;
#define IDLE			0
#define COMMENT		1
#define BRACKET		2
#define DATA_1			3
#define DATA_2			4
#define ADDRESS_1		5
#define ADDRESS_2		6
#define CSUM_1			7
#define CSUM_2			8
#define CHECK_LINE_SUM_1	9
#define CHECK_LINE_SUM_2	10
#define DONE			11

void put(unsigned char inbyte, unsigned long addr);

int fdout;

int main(int argc, char **argv)
{
	unsigned long	check_sum,i;
	int             r;
	int		fdin;
	int		ignore = 0;
	unsigned char 	inch;
	unsigned long	temp, temp2, sum, base, csum, r_csum;
	unsigned short	sindex,qindex,length;

	if (argc != 2) {
		fprintf(stderr, "Usage: %s firmware.flr\n", argv[0]);
		fprintf(stderr, "\nCreates out.bin with a memory image of the loaded firmware\n");
		exit(EXIT_FAILURE);
	}

	fdin = open(argv[1], O_RDONLY);
	if (fdin == -1) {
		perror("open(firmware.flr)");
		exit(EXIT_FAILURE);
	}

	fdout = open("out.bin", O_WRONLY|O_CREAT|O_TRUNC, 0644);
	if (fdout == -1) {
		perror("open(out.bin)");
		exit(EXIT_FAILURE);
	}

	state=IDLE;

	while (1)
		{
		r = read(fdin, &inch, 1);
		if (r == -1) {
				perror("read");
				close(fdout);
				close(fdin);
				exit(EXIT_FAILURE);
		} else if (r == 0) {
				close(fdout);
				close(fdin);
				exit(EXIT_SUCCESS);
		}
		if ((state != CHECK_LINE_SUM_1)
		     && (state != CHECK_LINE_SUM_2))
			sum += inch;	// this gets initialized as needed.

		switch (state)
			{
			case IDLE:
				switch (inch)
					{
					case 'X':
						close(fdout);
						close(fdin);
						exit(EXIT_SUCCESS);
					case '[':
						state = BRACKET;
						sum=inch; //start the line sum
						break;
					case ';':
						state = COMMENT;
						break;
					default:
						break;
					}// switch inch
				break;
			case COMMENT:
				switch (inch)
					{
					case '\r':
					case '\n':
						state=IDLE;
						if (!ignore)
							putchar('\n');
						ignore = 0;
						break;
					default:
						if (!ignore)
							putchar(inch);
						break;

					}// switch inch
				break;
			case BRACKET:
				switch (inch)
					{
					case 'D':
						state=DATA_1;
						break;
					case 'A':
						state=ADDRESS_1;
						break;
					case 'C':
						state=CSUM_1;
						break;
					default:
						printf("?bad type");
						state=COMMENT; // dump the line
						break;

					}// switch inch
				break;
			case ADDRESS_1:
				base=0;		// init address value
				csum=0;		// init checksum
				sindex=0;
				state=ADDRESS_2;
				// fall through
			case ADDRESS_2:
				temp = htoi(inch);	// get the byte..
				if (temp>0xF)
					{
					printf("?bad addr");
					state=COMMENT;
					break;
					}
				base <<= 4;
				base |= temp&0xF;
				sindex++;
				if (sindex == 6) {
					printf("Base: 0x%08x\n", base);
					state = CHECK_LINE_SUM_1;
				}
				break;
			case DATA_1:
				sindex=0; 
				qindex=0; //init char-in-quad count
				if ((inch<0x80) || (inch>0xB0))
					{
					printf("?bad length");
					state=COMMENT;
					break;
					}
				length=inch&0x3F;
				state=DATA_2;
				temp=0; // init the place to put the data.
				break;
			case DATA_2:
				if ((inch<0x80) || (inch>0xBF))
					{
					printf("?bad data");
					state=COMMENT;
					break;
					}
				
				if (qindex==0)
					temp=0;	// init data holder
				temp |= (long)(inch&0x3f) << (6*qindex);
				qindex++;
				// see if we're done with the quad..
				if (qindex == 4)
					{
					// write the data
					for (i=0;i<3;i++)
						{
						// don't write the bytes
						// off the end of a partial
						// record.
						if (length != 0)
							{
							csum += (long)(temp&0xff);
							put((int)temp&0xFF,base);
							temp = temp>>8;
							base++;
							length--;
							}
						}
					qindex=0;
					}
				if (length == 0)
					state=CHECK_LINE_SUM_1;
				break;  // state DATA_2
			case CHECK_LINE_SUM_1:
				temp=0;
				temp2 = htoi(inch);
				if (temp2>0xF)
					{
					printf("?bad csum");
					state=COMMENT;
					break;
					}
				temp = (temp2&0xF)<<4; 
				state=CHECK_LINE_SUM_2;
				break;
			case CHECK_LINE_SUM_2:
				temp2 = htoi(inch);
				if (temp2>0xF)
					{
					printf("?bad csum");
					state=COMMENT;
					break;
					}
				temp |= temp2&0xF;
				// time to verify the sum..
				if ((temp&0xFF) != (sum&0xFF))
					{
					printf("?csum fail");
					state=COMMENT;
					break;
					}
				state=DONE;
				break;
			case DONE:
				// this must be the end-of-line!
				switch (inch)
					{
					case '\r':
					case '\n':
						state=IDLE;
						break;
					default:
						printf("?too long");
						state=COMMENT;
						break;
					}
				break;
			case CSUM_1:
				// this is a overall-checksum record.
				sindex=0;
				state=CSUM_2;
				r_csum=0;
				// fall through
			case CSUM_2:
				temp = htoi(inch);	// get the byte..
				if (temp>0xF)
					{
					printf("?bad csum");
					state=COMMENT;
					break;
					}
				r_csum = r_csum<<4;
				r_csum |= temp&0xF;
				sindex++;
				if (sindex == 8)
					{
					// we cheat here. The checksum record
					// is itself checksummed, but we 
					// ignore that.
					// we'll always ignore the rest
					// of the line, in fact..
					state=COMMENT;
					ignore=1;
					if (r_csum != csum)
						{
						printf("? csum fail");
						break;
						}
					break;
					}
				break;

			}// switch state


		}// while not feof()


}

unsigned short htoi(unsigned char achar)
{

	unsigned char inchar;
	inchar = achar;

	if (inchar>0x60)
		inchar -= 0x40;	// uppercase

	// returns FFFF if the conversion fails.
	if (inchar<'0')
		return(0xFFFF);
	if (inchar>'F')
		return(0xFFFF);
	if (inchar>'9' && inchar<'A')
		return(0xFFFF);
	if (inchar <= '9')
		return((inchar-'0') & 0xF);
	  else
		return(0xA + ((inchar-'A') & 0xF));

}

void put(unsigned char inbyte, unsigned long addr)
{
	lseek(fdout, addr, SEEK_SET);
	if (write(fdout, &inbyte, 1) != 1) {
		perror("write");
		exit(EXIT_FAILURE);
	}
}
