/************************************************************************/
/* binload.c                                                            */
/* Atari binary load foramt file analyzer                               */
/* Preston Crow                                                         */
/* crow@cs.dartmouth.edu                                                */
/*                                                                      */
/* Public Domain                                                        */
/*                                                                      */
/* Version History                                                      */
/*  2 Jun 95  Version 1.0   Preston Crow <crow@cs.dartmouth.edu>        */
/*            Initial public release                                    */
/*  4 Jun 95  Version 2.0   Preston Crow <crow@cs.dartmouth.edu>        */
/*            Create fixed version of the file                          */
/*  7 Jun 95  Version 2.1   Preston Crow <crow@cs.dartmouth.edu>        */
/*            Use separate functions                                    */
/*            Merge overlapping and adjacent blocks                     */
/*  9 Jun 95  Version 2.2   Chad Wagner <cmwagner@gate.net>             */
/*            Ported to MS-DOS machines, should compile and work under  */
/*            MS-DOS, *** compile in COMPACT model. ***                 */
/************************************************************************/

#if defined(__MSDOS) || defined(__MSDOS__) || defined(_MSDOS) || \
	 defined(_MSDOS_)
#define MSDOS /* icky, icky, icky! */
#endif

/************************************************************************/
/* Include files							*/
/************************************************************************/
#include <stdio.h>
#include <stdlib.h>

#ifdef MSDOS
#include <alloc.h>
#else
#include <malloc.h>
#endif

/************************************************************************/
/* Constants and Macros							*/
/************************************************************************/
#define USAGE "Binload version 2.2\nAtari binary load file analysis and repair\nUsage:  binload sourcefile [destfile]\n"

#ifndef SEEK_SET /* should be in <stdio.h>, but some systems are lame */
#define SEEK_SET 0
#define SEEK_CUR 1
#define SEEK_END 2
#endif

/************************************************************************/
/* Data types								*/
/************************************************************************/
struct block {
	struct block *next;
	unsigned int start;
	unsigned int end;
};

/************************************************************************/
/* Function prototypes							*/
/************************************************************************/
int read_block(FILE *fin,FILE *fout,char *name);
void insert_block(unsigned int start,unsigned int end);
void write_blocks(FILE *fout);

/************************************************************************/
/* Global variables							*/
/************************************************************************/
struct block *blocks=NULL;
unsigned char *data; /* The full address space */
int run=0; /* True if load address specified */
long flen; /* Length of the file */

/************************************************************************/
/* main()								*/
/************************************************************************/
int main (int argc,char *argv[])
{
	FILE *fin,*fout;
	int c1,c2;

	--argc;++argv;

	if (!argc || argc>2) {
		fprintf(stderr,USAGE);
		exit(1);
	}

#ifdef MSDOS
	data = farmalloc(65536L);
#else
	data = malloc(65536L);
#endif
	if (!data) {
		fprintf(stderr,"couldn't allocate 64k for data.\n");
		exit(1);
	}
	fin=fopen(*argv,"rb");
	if (!fin) {
		fprintf(stderr,"%s:  Unable to open file\n\n%s",*argv,USAGE);
		exit(1);
	}
	c1=getc(fin);
	c2=getc(fin);
	if (c1 != c2 || c1 != 0xff) {
		printf("%s: Not an Atari 8-bit binary load format file\n",*argv);
		fclose(fin);
		exit(1);
	}
	printf("Binary file:  %s\n",*argv);

	fout=NULL;
	if (argc>1) {
		fout=fopen(argv[1],"wb");
		if (!fout) {
			fprintf(stderr,"%s:  Unable to open file\n\n%s",*argv,USAGE);
			exit(1);
		}
	}

	if (fout) { fputc(0xff,fout);fputc(0xff,fout); }

	fseek(fin,0,SEEK_END);
	flen=ftell(fin);
	fseek(fin,2,SEEK_SET);
	while (ftell(fin)<flen) {
		if (read_block(fin,fout,*argv)) break;
	}
	fclose(fin);
	if (run) insert_block(0x02e0,0x02e1);
	write_blocks(fout); /* Write all blocks since last init */
	if (fout) fclose(fout);
	return(0);
}

/************************************************************************/
/* read_block()								*/
/************************************************************************/
int read_block(FILE *fin,FILE *fout,char *name)
{
	unsigned int start;
	unsigned int end;
	unsigned int length;
	int c1,c2;

	do {
		c1=fgetc(fin);
		c2=fgetc(fin);
		start=c2*256+c1;
		if (start == 0xffff) {
			printf("%s:  Unexpected second 0xffff format header\n",name);
		}
	} while (start == 0xffff);
	if (feof(fin)) {
		printf("%s:  Unexpected end of file in load range start address\n",name);
		return(1);
	}
	c1=fgetc(fin);
	c2=fgetc(fin);
	end=c2*256+c1;
	length=end-start+1;
	if (feof(fin)) {
		printf("%s:  Unexpected end of file in load range end address\n",name);
		return(1);
	}
	if (start==end && c1==c2) {
		printf("%s:  Apparent garbage fill at end of file (%ld bytes)\n",name,flen-ftell(fin)+4);
		return(1);
	}
	if (end<start) {
		printf("%s:  Start:  %u\tEnd:  %u\tLength  %u\n",name,start,end,length);
		printf("%s:  Error:  %ld bytes in file after invalid load range\n",name,flen-ftell(fin));
		return(1);
	}
	if (flen<ftell(fin)+length) {
		printf("%s:  Start:  %u\tEnd:  %u\tLength  %u\n",name,start,end,length);
		printf("\t\tTruncated file:  missing data in load block (%ld bytes missing)\n",ftell(fin)+length-flen);
		return(1);
	}
	/* Read in the data for this block */
	fread(&data[start],length,1,fin);

	/* Check for run address */
	if (start<=0x02e1 && start+length>=0x02e0) {
		if (start==0x02e1 || start+length==0x02e0) {
			printf("%s:  Warning:  Partial run address\n",name);
		}
		if (run) printf("%s:  Unexpected second run address\n",name);
		run=1;
		printf("%s:  Run at:  %u\n",name,data[0x02e0]+256*data[0x02e1]);
		if (start>=0x02e0 && end<=0x02e1) return(0);
		/* Other data in this block */
		if (start==0x02e0 || start==0x02e1) {
			/* Run and init in the same block--split */
			start=0x02e2;
			length=end-start+1;
		}
		else if (end==0x02e0 || end==0x02e1) {
			/* other stuff before the run address--split */
			end=0x02df;
			length=end-start+1;
		}
		else {
			/* Run address in the middle of the block */
			printf("%s:  Start:  %u\tEnd:  %u\tLength  %u\n",name,start,0x02df,0x02df-start+1);
			insert_block(start,0x02df);
			start=0x02e2;
			length=end-start+1;
		}
	}

	/* Check for init address */
	/* We know there's nothing before the address in the block, */
	/* as we would have split it off above as a run address.    */
	if (start<=0x02e3 && start+length>=0x02e2) {
		if (start==0x02e3 || start+length==0x02e2) {
			printf("%s:  Warning:  Partial init address\n",name);
		}
		printf("%s:  Init at:  %u\n",name,data[0x02e2]+256*data[0x02e3]);
		/* Other data in this block? */
		if (end > 0x02e3) {
			/* More stuff past init--split */
			printf("%s:  Start:  %u\tEnd:  %u\tLength  %u\n",name,0x02e4,end,length);
			insert_block(0x02e4,end);
			end=0x02e3;
			length=end-start+1;
		}
		insert_block(start,end);
		/* Write everything out to avoid cross-init merges */
		write_blocks(fout);
		return(0);
	}

	/* Print data block load message */
	printf("%s:  Start:  %u\tEnd:  %u\tLength  %u\n",name,start,end,length);
	insert_block(start,end);

	return(0);
}

/************************************************************************/
/* insert_block()							*/
/************************************************************************/
void insert_block(unsigned int start,unsigned int end)
{
	struct block *b,*bp;

	bp=NULL; /* previous block */
	b=blocks;
	while(b) {
		/* Check for merge */
		if (b->end+1 == start) {
			printf("\t\tBlock merges with a previous block\n");
			b->end=end;
			return;
		}
		if (b->start-1 == end) {
			printf("\t\tBlock merges with a previous block (unexpected ordering)\n");
			b->start=start;
			return;
		}
		/* Check for overlap */
		if (b->start <= start && end <= b->end) {
			printf("\t\tWarning:  Completely overlaps a previous block--merged\n");
			return;
		}
		if (b->start <= end && b->start >= start) {
			b->start=start;
			if (b->end<end) b->end=end;
			printf("\t\tWarning:  Partially overlaps a previous block--merged\n");
			return;
		}
		if (b->end <= end && b->end >= start) {
			b->end=end;
			if (b->start>start) b->start=start;
			printf("\t\tWarning:  Partially overlaps a previous block--merged\n");
			return;
		}
		bp=b;
		b=b->next;
	}
	/* Add this block to the end of the list */
	b=malloc(sizeof(*b));
	if(!b) {
		fprintf(stderr,"Unable to malloc memory--aborted\n");
	}
	b->start=start;
	b->end=end;
	b->next=NULL;
	if (bp) bp->next=b;
	else blocks=b;
}

/************************************************************************/
/* write_blocks()							*/
/************************************************************************/
void write_blocks(FILE *fout)
{
	struct block *b,*bp;

	b=blocks;
	while (b) {
		if (fout) {
			fputc(b->start&0xff,fout);
			fputc(b->start/0x100,fout);
			fputc(b->end&0xff,fout);
			fputc(b->end/0x100,fout);
			fwrite(&data[b->start],(b->end - b->start + 1),1,fout);
		}
		bp=b; /* To allow free call after last use */
		b=b->next;
		free(bp);
	}
	blocks=NULL;
}
