/*	gc_n64_usb : Gamecube or N64 controller to USB adapter firmware
	Copyright (C) 2007-2015  Raphael Assenat <raph@raphnet.net>

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <string.h>
#include "hexdump.h"

static unsigned char chk(unsigned char *buf, int len)
{
	int i;
	unsigned char r = 0;

	for (i=0; i<len; i++) {
		r += buf[i];
	}
	return r;
}

/* \return The highest address written to, or negative on errors.
 */
int load_ihex(const char *file, unsigned char *dstbuf, int bufsize)
{
	FILE *fptr;
	char linebuf[550];
	unsigned char databuf[2+1+255+1];
	int ret = 0;
	int line = 0;
	int eof_seen = 0;
	unsigned int max_address = 0;
	unsigned int offset = 0;

	fptr = fopen(file, "r");
	if (!fptr) {
		perror("fopen");
		return -1;
	}

	do {
		if (fgets(linebuf, sizeof(linebuf), fptr)) {
			unsigned int data_count;
			unsigned int address;
			int input_nibbles, input_bytes;
			int i;

			line++;

			if (linebuf[0] != ':') {
				fprintf(stderr, "Ignored invalid line %d\n", line);
				continue;
			}

			if (eof_seen) {
				fprintf(stderr, "extra data after EOF record in hex file\n");
				ret = -7;
				goto err;
			}

			// :10 0000 00 92C064C7ABC0AAC0A9C0A8C0A7C0A6C0 00
			//  ^  ^    ^  ^                                ^-- Checksum
			//  |  |    |  +----- Data [data_count]
			//  |  |    +---- Record type
			//  |  +------ Address
			//  +----- data_count
			//

			input_nibbles = strlen(linebuf) - 1;

			for (input_bytes=0,i=0; i<input_nibbles; i+=2) {
				unsigned int byte;
				if (1 != sscanf(linebuf + 1 + i, "%02x", &byte)) {
					break;
				}
				databuf[input_bytes] = byte;
				input_bytes++;
			}

			//printf("Input bytes: %d\n", input_bytes);
			//printHexBuf(databuf, input_bytes);

			// Validate the record checksum
			if (chk(databuf, input_bytes)) {
				fprintf(stderr, "Bad checksum at line %d\n", line);
				ret = -4;
				goto err;
			}

			// Data length sanity check
			data_count = databuf[0];
			if (input_bytes != 1+2+1+data_count+1) {
				fprintf(stderr, "Invalid record (less data than expected) at line %d\n", line);
				ret = -5;
				goto err;
			}

			address = databuf[1]<<8 | databuf[2];

			switch(databuf[3])
			{
				case 0x00: // Data
					if (address + offset + data_count > bufsize) {
						fprintf(stderr, "hex file too large\n");
						ret = -6;
						goto err;
					}
					if (address + offset + data_count > max_address) {
						max_address = address + offset + data_count;
					}
					memcpy(dstbuf + address + offset, databuf + 4, data_count);
					break;

				case 0x01: // EOF
					eof_seen = 1;
					break;

				case 0x04: // Extended linear address
					if (data_count != 2) {
						fprintf(stderr, "ihex parser: Malformatted 0x04 record at line %d\n", line);
						ret = -8;
						goto err;
					}
					offset = (databuf[4] << 24) | (databuf[5] << 16);
					//printf("OFfset: 0x%08x\n", offset);
					break;

				case 0x03: // Start segment address
				case 0x05: // Start linear address
					// Ignored
					break;

				default:
				case 0x02: // Extended segment address
					fprintf(stderr, "ihex parser: Unimplemented record type 0x%02x at line %d\n", databuf[3], line);
					ret = -2;
					goto err;
			}

		}
	} while (!feof(fptr));

	fclose(fptr);
	return max_address;

err:
	fclose(fptr);
	return ret;
}
