/************************************************************************\
 * File Version Information
 * $Header: /Altair32v2/hexfile.c 23    11/08/06 9:21p Racini $
 ************************************************************************/
/************************************************************************\
  MITS Altair Emulator
  HEX file support code for integrated debugger

  Modifications from ihex.c for the Solace Sol-20 emulator
  Copyright (c) Jim Battle, 2000-2002

  Modifications for the Altair32  
  Copyright (c) 2001-2006 Richard A. Cini
  
  This code is based on "ihex.c", a program snippet from Paul Stoffregen.

Change Log:
  2001/12/02  RAC -- Began coding.
  2001/12/14  RAC -- RELEASE MARKER -- v2.1
  2002/01/31  RAC -- RELEASE MARKER -- v2.2
  2002/05/17  RAC -- Minor mods to hexfile_read to sync with Solace v3.1
  2002/07/07  RAC -- RELEASE MARKER -- v2.3
  2002/08/23  RAC -- RELEASE MARKER -- v2.30.10
  2002/11/15  RAC -- RELEASE MARKER -- v2.40.2100
  2003/04/26  RAC -- RELEASE MARKER -- v2.50.2045
  2004/07/30  RAC -- RELEASE MARKER -- v3.00.0135
  2006/05/12  RAC -- RELEASE MARKER -- v3.10.0200
  2006/11/15  RAC -- RELEASE MARKER -- v3.20.0400
\************************************************************************/
#include <windows.h>
#include <stdio.h>		// C-lib =
#include <string.h>
#include <ctype.h>
#include <malloc.h>
#include "altair32.h"
#include "hexfile.h"
#include "srcdbg.h"

#define HFILE_MAGIC  (0xA11BEEF)	// helps validate handle
#define HFILE_BUFSIZE 1024
extern void hexout( FILE *, int, int, int );

// ----------------- start of modified ihex.c code ------------------
/* Intel HEX read/write functions, Paul Stoffregen, paul@ece.orst.edu */
/* This code is in the public domain.  Please retain my name and */
/* email address in distributed copies, and let me know about any bugs */
/* I, Paul Stoffregen, give no warranty, expressed or implied for */
/* this software and/or documentation provided, including, without */
/* limitation, warranty of merchantability and fitness for a */
/* particular purpose. */


/* parses a line of intel hex code, stores the data in bytes[] */
/* and the beginning address in addr, and returns a 1 if the */
/* line was valid, or a 0 if an error occured.  The variable */
/* num gets the number of bytes that were stored into bytes[] */
int 
parse_hex_line(char *theline, int bytes[], int *addr, int *num, int *code)
{
    int sum, len, cksum;
    char *ptr;
    
    *num = 0;
    if (theline[0] != ':') return 0;
    if (strlen(theline) < 11) return 0;
    ptr = theline+1;
    if (!sscanf(ptr, "%02x", &len)) return 0;
    ptr += 2;
    if ( strlen(theline) < (size_t)(11 + (len * 2)) ) return 0;
    if (!sscanf(ptr, "%04x", addr)) return 0;
    ptr += 4;
      /* printf("Line: length=%d Addr=%d\n", len, *addr); */
    if (!sscanf(ptr, "%02x", code)) return 0;
    ptr += 2;
    sum = (len & 255) + ((*addr >> 8) & 255) + (*addr & 255) + (*code & 255);
    while(*num != len) {
	if (!sscanf(ptr, "%02x", &bytes[*num])) return 0;
	ptr += 2;
	sum += bytes[*num] & 255;
	(*num)++;
	if (*num >= 256) return 0;
    }
    if (!sscanf(ptr, "%02x", &cksum)) return 0;
    if ( ((sum & 255) + (cksum & 255)) & 255 ) return 0; /* checksum error */
    return 1;
}


/* produce intel hex file output... call this routine with */
/* each byte to output and it's memory location.  The file */
/* pointer fhex must have been opened for writing.  After */
/* all data is written, call with end=1 (normally set to 0) */
/* so it will flush the data from its static buffer */

/* caution, static buffering is used, so it is necessary */
/* to call it with end=1 when finsihed to flush the buffer */
/* and close the file */

#define MAXHEXLINE 32	/* the maximum number of bytes to put in one line */

void 
hexout(FILE *fhex, int byte, int memory_location, int end)
{
    static int byte_buffer[MAXHEXLINE];
    static int last_mem, buffer_pos, buffer_addr;
    static int writing_in_progress=0;
    register int i, sum;

    if (!writing_in_progress) {
	/* initial condition setup */
	last_mem = memory_location-1;
	buffer_pos = 0;
	buffer_addr = memory_location;
	writing_in_progress = 1;
	}

    if ( (memory_location != (last_mem+1)) || (buffer_pos >= MAXHEXLINE) \
    || ((end) && (buffer_pos > 0)) ) {
	/* it's time to dump the buffer to a line in the file */
	fprintf(fhex, ":%02X%04X00", buffer_pos, buffer_addr);
	sum = buffer_pos + ((buffer_addr>>8)&255) + (buffer_addr&255);
	for (i=0; i < buffer_pos; i++) {
	    fprintf(fhex, "%02X", byte_buffer[i]&255);
	    sum += byte_buffer[i]&255;
	}
	fprintf(fhex, "%02X\n", (-sum)&255);
	buffer_addr = memory_location;
	buffer_pos = 0;
    }

    if (end) {
	fprintf(fhex, ":00000001FF\n");  /* end of file marker */
	fclose(fhex);
	writing_in_progress = 0;
    }
	
    last_mem = memory_location;
    byte_buffer[buffer_pos] = byte & 255;
    buffer_pos++;
}

// ----------------- end of modified ihex.c code ------------------


// remove any combination of trailing CRs and LFs from buffer
static void
chop_crlf(char *p)
{
    int len = strlen(p);
    while (p[len-1] == '\n' || p[len-1] == '\r') {
	p[len-1] = '\0';
	len--;
    }
}


// skip whitespace
static void
skipwhite(char **p)
{
    while (**p == ' ' || **p == '\t')
	(*p)++;
}


// read in next line of file
// returns 1 if EOF, 0 otherwise
static int
get_nextline(hfile_t *hf)
{
    if (fgets(hf->buffer, HFILE_BUFSIZE, hf->fp) == NULL)
	return 1;

    hf->line++;
    hf->offset = hf->buffer;

    chop_crlf(hf->buffer);
    skipwhite(&(hf->offset));

    return 0;
}


hfile_t*
hexfile_hex_open(char *filename, int format, char *access)
{
    int mode;	// 0=read, 1=write
    hfile_t *hf;

    if (!filename || !access || format < 0 || format > 1)
	return NULL;

    if (!strcmp(access, "r"))
	mode = 0;
    else if (!strcmp(access, "w"))
	mode = 1;
    else
	return NULL;

    hf = (hfile_t*)malloc(sizeof(hfile_t));
    if (!hf)
	return NULL;

    hf->magic       = HFILE_MAGIC;
    hf->filename    = strdup(filename);
    hf->writeaccess = mode;
    hf->format      = format;
    hf->line        = 0;
    hf->state       = 0;
    hf->buffer      = (char*)malloc(HFILE_BUFSIZE);
    hf->fp          = fopen(filename, mode ? "w" : "r");

    if (!hf->filename || !hf->buffer || !hf->fp) {
	if (hf->filename) free(hf->filename);
	if (hf->buffer)   free(hf->buffer);
	if (hf->fp)       fclose(hf->fp);
	free(hf);
	return NULL;
    }

    return hf;
}


// read next byte of file.  trust parameters.
// in this fuction, we use hf->buffer not to hold the text line,
// but instead to hold the already parsed bytes.  this is because
// we check the line first to make sure the checksum is OK.
static int
hexfile_hex_readbyte(hfile_t *hf, int *addr, byte *data)
{
    int start;

    if (hf->line == 0)	// first line of file
	hf->buffer[0] = '\0';

    // read next line, if needed
    while (hf->buffer[0] == 0) {
	int bytes[256], stat, num, code, i;
	if (get_nextline(hf))
	    return HFILE_EOF;
	stat = parse_hex_line(hf->buffer, bytes, &start, &num, &code);
	if (stat == 0)
	    return HFILE_BAD_FORMAT;
	if (code == 1)
	    return HFILE_EOF;
	if (code == 2)
	    continue;			// start of file
	if (code != 0)
	    return HFILE_BAD_FORMAT;	// not data

	// buffer[0] contains the number of data bytes,
	// buffer[n] contains the n-1'th byte of the line.
	hf->buffer[0] = num;
	for(i=0; i<num; i++)
	    hf->buffer[i+1] = bytes[i];
	hf->state = start;
    }

    *addr = hf->state++;	// where it came from
    *data = hf->offset[1];	// fetch next byte

    hf->offset++;		// byte to fetch next time
    hf->buffer[0]--;		// one less char to read

    return HFILE_OK;
}


// write next byte of file.  trust parameters.
static int 
hexfile_hex_writebyte(hfile_t *hf, int addr, int data)
{
    hexout(hf->fp, data, addr, 0);
    return HFILE_OK;
}


// read next byte of file.  trust parameters.
// return 0 on success, !=0 on error
static int
hexfile_ent_readbyte(hfile_t *hf, int *addr, byte *data)
{
    int val, any;
    char ch;

    if (hf->line == 0) {

	// fetch first line
	if (get_nextline(hf))
	    return HFILE_BAD_FORMAT;

	// first line must be "EN[A-Z]* [0-9A-F]+"
	// the 2nd word is the start load address
	if (strncmp(hf->buffer,"EN",2) != 0)
	    return HFILE_BAD_FORMAT;
	if (sscanf(hf->buffer,"%*s %x", &hf->state) != 1)
	    return HFILE_BAD_FORMAT;

	// fetch second line
	if (get_nextline(hf))
	    return HFILE_BAD_FORMAT;
    }

    // parse hex number
    val = 0x0000;
    any = 0;
    while(1) {

	if (!any) {
	    skipwhite(&(hf->offset));
	    if (hf->offset[0] == '/') {
		return HFILE_EOF;
	    } else if (hf->offset[0] == '\0') {
		if (get_nextline(hf))
		    return HFILE_BAD_FORMAT;	// expected "/" first
		continue;
	    }
	}

	ch = hf->offset[0];
	if (ch >= '0' && ch <= '9') {
	    val = val*16 + ch-'0';
	    any = 1;
	} else if (ch >= 'a' && ch <= 'f') {
	    val = val*16 + ch-'a'+10;
	    any = 1;
	} else if (ch >= 'A' && ch <= 'F') {
	    val = val*16 + ch-'A'+10;
	    any = 1;
	} else if (ch == ':') {
	    // reset address marker
	    hf->offset++;	// skip ':'
	    hf->state = (val & 0xFFFF);
	    val = 0x0000;
	    any = 0;
	    continue;
	} else
	    break;

	(hf->offset)++;
ASSERT(hf->offset < &(hf->buffer[HFILE_BUFSIZE]));
    } // while(parse hex number)

    if (!any)
	return HFILE_BAD_FORMAT;
    
    *addr = hf->state;
    *data = (val & 0xFF);

    hf->state++;

    return HFILE_OK;
}


// write next byte of file.  trust parameters.
static int
hexfile_ent_writebyte(hfile_t *hf, int addr, int data)
{
    // create first line
    if (hf->line == 0) {
	fprintf(hf->fp, "ENTER %04X\n", addr);
	hf->line++;
	hf->offset = hf->buffer;
	hf->state = 0;
    }

    // if first byte of line, print header
    if (hf->state == 0)
	fprintf(hf->fp, "%04X:", addr);

    fprintf(hf->fp, " %02X", data);
    hf->state++;

    if (hf->state == 16) {
	fprintf(hf->fp, "\n");
	hf->state = 0;
    }

    return HFILE_OK;
}


// close file. trust parameters.
static int
hexfile_ent_writeclose(hfile_t *hf)
{
    fprintf(hf->fp, "/\n");
    return HFILE_OK;
}


// byte at a time interface, read or write one byte
// return 0 on success, something else on failure
int
hexfile_hex_read(hfile_t *hf, int *addr, byte *data)
{
    int stat;

    if (!hf || (hf->magic != HFILE_MAGIC) || (hf->writeaccess) || !addr || !data)
	return HFILE_BAD_FORMAT;

    switch (hf->format) {

	case 0:	// intel hex format
	    stat = hexfile_hex_readbyte(hf, addr, data);
	    break;

	case 1:	// sol ent format
	    stat = hexfile_ent_readbyte(hf, addr, data);
	    break;

	default:
	    stat = HFILE_BAD_HANDLE;
	    break;
    }

    return stat;
}


// byte at a time interface, read or write one byte
// return 0 on success, something else on failure
int
hexfile_hex_write(hfile_t *hf, int addr, byte data)
{
    int stat = 0;

    if (!hf || (hf->magic != HFILE_MAGIC) || (!hf->writeaccess))
	return 1;

    switch (hf->format) {

    case 0:	// intel hex format
        stat = hexfile_hex_writebyte(hf, addr, data);
        break;

    case 1:	// sol ent format
        stat = hexfile_ent_writebyte(hf, addr, data);
        break;

    default:
        stat = 1;
        break;
    }

    return stat;
}


// close file. trust parameters.
static int
hexfile_hex_writeclose(hfile_t *hf)
{
    hexout(hf->fp, 0, 0, 1);
    return HFILE_OK;
}


// byte at a time interface, close file
// return 0 on success, something else on failure
int
hexfile_hex_close(hfile_t *hf)
{
    int stat = 0;

    if (!hf || (hf->magic != HFILE_MAGIC))
	return 1;

    // finish off pending operations
    switch (hf->format) {

	case 0:
	    if (hf->writeaccess)
		stat = hexfile_hex_writeclose(hf);
	    break;

	case 1:
	    if (hf->writeaccess)
		stat = hexfile_ent_writeclose(hf);
	    break;

	default:
	    stat = 1;
	    break;
    }

    fclose(hf->fp);

    // free handle resources
    hf->magic = 0xDEADDEAD;
    free(hf->filename);
    free(hf->buffer);
    free(hf);

    return stat;
}


// determine the type not by the file suffix, but by reading
// the first line of the file and guessing.
// returns 0 if HEX, 1 if ENT, -1 if file error.
int
hexfile_guessformat(char *filename)
{
    int buflen, len, addr, type, format, data;
    char buf[1024];
    FILE *fp;

    fp = fopen(filename, "r");
    if (fp == NULL) {
	sprintf(buf, "couldn't open '%s'\n",filename);
	//WinAlert(buf);
	return -1;
    }

    fgets(buf, sizeof(buf), fp);
    fclose(fp);

    // strip trailing cr/lf's
    chop_crlf(buf);
    buflen = strlen(buf);

    // check for intel hex format
    format = 1;	// assume ENT
    if (sscanf(buf, ":%02x%04x%02x%02x", &len, &addr, &type, &data) == 4) {
	if ((buflen == (11 + (len * 2))) && (type < 0x06)) {
	    // it looks like an intel hex file
	    format = 0;	// intel HEX
	}
    }

    return format;
}


// read a binary file in either "ENter" or intel "hex" format.
// we determine the type not by the file suffix, but by reading
// the first line of the file and guessing.
int
hexfile_read(char *filename, int report, byte *memory)
{
    int stat, format, any, addr, start;
    byte data;
    hfile_t *hf;
    char buf[80];

    format = hexfile_guessformat(filename);
    if (format < 0)
	return HFILE_FILE_ERROR;

    hf = hexfile_hex_open(filename, format, "r");
    if (hf == NULL) {
	sprintf(buf, "'%s' is not readable\n",filename);
	//WinAlert(buf);
	return HFILE_FILE_ERROR;
    }

    any = 0;
    stat = hexfile_hex_read(hf, &addr, &data); // get first byte
    start = addr;
    while (stat == HFILE_OK) {
	memory[addr] = data;
	any = 1;
	stat = hexfile_hex_read(hf, &addr, &data);
    }

    if (report) {
	if ((stat == HFILE_EOF) && any)
	    sprintf(buf, "program load address is $%04X", start);
	else if ((stat == HFILE_EOF) && !any)
	    strcpy(buf, "Empty file");
	else
	    sprintf(buf, "file format error on line %d", hf->line);
	//WinNote(buf, 5);
    }

    hexfile_hex_close(hf);

    return HFILE_OK;
}


// write a binary file in either "ENter" or intel "hex" format.
// format=0 is HEX (Intel); format=1 is ENT (not used in A32).
int
hexfile_hexdump(char *filename, int format, int start, int end, byte *memory)
{
    char buf[80];
    int addr, stat;
    hfile_t *hf;

    hf = hexfile_hex_open(filename, format, "w");
    if (hf == NULL) {
	sprintf(buf, "'%s' is not writable\n",filename);
	//WinAlert(buf);
	return HFILE_FILE_ERROR;
    }

    for(addr=start; addr <= end; addr++) {
	stat = hexfile_hex_write(hf, addr, memory[addr]);
	if (stat != HFILE_OK)
	    break;
    }

    hexfile_hex_close(hf);

    return stat;
}

// read the specified hex file, but if available, load the
// corresponding .prn file as an overlay.
int
hexfile_read_overlay(char *filename, int report, byte *memory)
{
    int rv = hexfile_read(filename, report, memory);
    if (rv == HFILE_OK) {

	int len = strlen(filename);
	char *modname;
	int repl = 0;
	int n;

	// see if the filename ends with a .??? suffix
	// replace trailing .??? with .prn if it exists
	modname = (char *)malloc(len + 4);
	strcpy(modname, filename);
	for(n=len-1; (n >= 0) && (n >= len-4) && !repl; n--) {
	    switch (modname[n]) {
		case '\\':
		case ':':
		    // oops
		    break;
		case '.':
		    strcpy(&modname[n], ".PRN");
		    repl = 1;	// replaced
		    break;
		default:
		    break;
	    }
	}
	if (!repl)
	    strcpy(&modname[len], ".PRN");

	// now try to load the overlay.
	// if the load fails, silently ignore it.
#if 1
	(void)OvlParse(modname, 1);	// 1=yes, autoloaded
	OvlHitInit();			// rebuild overlay index table
#else
	// dbg_interp("overlay %modname");		// interpret it
#endif

	free(modname);
    }

    return rv;
}

/*  end of file: hexfile.c  */
