/**
 * srec2bin - generate a binary image from a Motorola S record file.
 *
 * Adapted from convert.pas from the Motorola BBS
 *
 * This program is free software. See files in ../legal/* for info.
 **/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <ctype.h>

#define MAX_CHIPSIZE (1024 * 1024)		/* 1 Megabyte */
#define ERASED_VALUE 0xFF			/* value of unburned bytes */
#define OUTSPEC (O_WRONLY|O_TRUNC|O_CREAT)	/* output file spec */
#define INSPEC O_RDONLY				/* input file spec */

#define OWN_RD 0400
#define OWN_WR 0200
#define OWN_EX 0100
#define GRP_RD 0040
#define GRP_WR 0020
#define GRP_EX 0010
#define OTH_RD 0004
#define OTH_WR 0002
#define OTH_EX 0001
#define OUTPERM (OWN_RD | OWN_WR)		/* open/create permisions */

char *memimage;		/* eprom memory look-alike area */
int out_file = -1;	/* raw image file output */
int in_file = -1;	/* S-rec file input */
int chipsize = 0;	/* given eprom byte size */
unsigned int address;		/* current offset in eprom memory */
int offset;		/* start of eprom addresses */
char in_file_name[132];	/* s-rec filename */
char out_file_name[132];/* binary image filename */


/*1
*	Allocate the memory image area. This is where the raw
*	bytes are written before they go off to a file.
*/
int
init_data(void)
{
	register i;

	if (chipsize > MAX_CHIPSIZE)
	{
		fprintf(stderr,
			"chipsize of %d is too big. Use # <= %d\n",
			chipsize, MAX_CHIPSIZE);
		return(-1);
	}
	memimage = malloc(chipsize);
	if ( !memimage )
	{
		fprintf(stderr,
			"Could not allocate memory for chipsize of %d\n",
			chipsize);
		return(-2);
	}
	memset(memimage, ERASED_VALUE, chipsize);
	return(0);
}

/*1
*	Write to file the memory image.
*/
int
write_data_file(void)
{
	register i;

	if ( *out_file_name == (char)NULL )
		out_file = (int)stdout;
	else
	{
		out_file = open(out_file_name, OUTSPEC, OUTPERM);
		if (out_file == -1)
		{
			fprintf(stderr,
				"Could not open for write file '%s'\n",
				out_file_name);
			return(-1);
		}
	}
	for (i=0; i <= chipsize - 1024; i += 1024)
		write(out_file, &memimage[i], 1024);
	if (chipsize % 1024)
		write(out_file, &memimage[i], chipsize % 1024);
	close(out_file);
	out_file = -1;
	return(0);
}

/*1
*	The file had garbage or ended too soon.
*/
void
premature(void)
{
	fprintf(stderr, "Primature end of file. Exiting program.\n");
	write_data_file();
	if (in_file != -1)
		close(in_file);
	if (out_file != -1)
		close(out_file);
	exit(-99);
}

/*1
*	Get an ascii encoded hex byte and convert to a real byte.
*/
unsigned int
get_byte(int *calcsum)
{
	char nibblets[2];
	unsigned int hi, lo, retval;

	if (read(in_file, nibblets, 2) != 2)
		premature();
	/* should handle non-hex chars here someday */
	hi = nibblets[0] - 48;
	if (hi > 9)
		hi = hi - 7;
	lo = nibblets[1] - 48;
	if (lo > 9)
		lo = lo - 7;
	retval = (hi << 4) | (lo & 0xF);
	if ( calcsum )
		*calcsum += retval;
	return(retval);
}


/*1
*	Read in the S-records and load our local memory image.
*/
int
get_data(void)
{
	register i;
	char dummy_char;
	char checksum, length;
	int calcsum;
	int notdone;
	int bad_addr_flag = 0;
	unsigned int bad_addr_start = 0;
	unsigned int load_addr_start = 0;

	in_file = open(in_file_name, INSPEC);
	if ( in_file == -1 )
	{
		fprintf(stderr,
			"Could not open input file '%s'\n",
			in_file_name);
		return(-1);
	}
	for(notdone=1; notdone; )
	{
		if (read(in_file, &dummy_char, 1) != 1)
			break;
		if (read(in_file, &dummy_char, 1) != 1)
			premature();
		if (dummy_char = '1')
		{

			calcsum = 0;

			length = get_byte(&calcsum);

			address = get_byte(&calcsum) * 256;
			address += get_byte(&calcsum);
			load_addr_start = address;
			address -= offset;

			for(i=0, bad_addr_flag=0; i < length - 3; i++)
			{
				if (address >= 0 && address < chipsize)
					memimage[address] = get_byte(&calcsum);
				else
				{
					if ( !bad_addr_flag )
					{
						bad_addr_flag = 1;
						bad_addr_start = address + offset;
					}
					dummy_char = get_byte(&calcsum);
				}
				address++;
			}
			if (bad_addr_flag)
				fprintf(stderr,
					" *** warning: Invalid address at %xh\n",
					 bad_addr_start);
			checksum = get_byte(NULL);
			/* calcsum++; calcsum *= -1; */
			calcsum = ~calcsum & 0xFF;
			if ( (char)calcsum != checksum )
				fprintf(stderr,
					" *** warning: checksum mismatch start address %xh\n",
					load_addr_start);
		}
		for(;;)
		{	/* read till newline */
			if (read(in_file, &dummy_char, 1) != 1)
			{
				notdone = 0;
				break;
			}
			if (dummy_char == '\n')
				break;
		}
	}
	close(in_file);
	in_file = -1;
}

int
main(int argc, char **argv)
{
	char answer[132];
	char *tptr;

	if (argc > 3)
	{
		chipsize = atoi(argv[1]) * 1024;
		tptr = argv[2] + strlen(argv[2]) - 1;
		if ( *tptr == 'h' || *tptr == 'H')
		{
			if ( *tptr == 'h' )
				sscanf(argv[2], "%xh", &offset);
			else
				sscanf(argv[2], "%xH", &offset);
		}
		else
			offset = atoi(argv[2]);
		strcpy(in_file_name, argv[3]);
		if (argc > 4)
			strcpy(out_file_name, argv[4]);
		else
			*out_file_name = (char)NULL;
	}
	else
	{
		do
		{
			printf("Enter Prom size in K-bytes :");
			gets(answer);
			chipsize = atoi(answer);
		} while (chipsize < 1 && chipsize > (MAX_CHIPSIZE / 1024));
		chipsize = chipsize * 1024;
		printf("Offset in bytes (chip start address):");
		gets(answer);
		tptr = answer + strlen(answer) - 1;
		if ( *tptr == 'h' || *tptr == 'H')
		{
			if ( *tptr == 'h' )
				sscanf(answer, "%xh", &offset);
			else
				sscanf(answer, "%xH", &offset);
		}
		else
			offset = atoi(answer);
		printf("Input file name :");
		gets(in_file_name);
		printf("Output file name:");
		gets(out_file_name);
	}

	if (init_data() < 0)
		exit(-1);
	if (get_data() < 0)
		exit(-2);
	if (write_data_file() < 0)
		exit(-3);
	fprintf(stderr, "srec2bin: %s -> %s, rom size %dK, rom starting offset %xh\n",
		 in_file_name, (*out_file_name ? out_file_name : ""),
		 chipsize /  1024, offset);
	exit(0);
}
