#include <conio.h>
#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <direct.h>
#include <math.h>
#include <sys/stat.h>

#include "iw.h"
#include "patch.h"
#include "list.h"
#include "rommaker.h"
#include "sbosdata.h"

#define PROG_CHUNK	0
#define ENV_CHUNK	1024
int env_minor_id = 0;
#define DATA_CHUNK	1025

#define SCRATCH_BUF_SIZE  8192
unsigned char scratch[SCRATCH_BUF_SIZE];

enum { SREC, BINARY, DISK } rom_format = SREC;

static struct header file_header = { { 'F', 'F', 'F', 'F' }, 0L };
static struct header patch_header = { { 'P', 'T', 'C', 'H' }, 0L };
static struct header program_header = { { 'P', 'R', 'O', 'G' }, 0L };
static struct header layer_header = { { 'L', 'A', 'Y', 'R' }, 0L };
static struct header wave_header = { { 'W', 'A', 'V', 'E' }, 0L };
static struct header text_header = { { 'T', 'E', 'X', 'T' }, 0L };
static struct header data_header = { { 'D', 'A', 'T', 'A' }, 0L };
static struct header envp_header = { { 'E', 'N', 'V', 'P' }, 0L };
static struct header name_header = { { 'N', 'A', 'M', 'E' }, 0L };
static struct header sbos_header = { { 'S', 'B', 'O', 'S' }, 0L };
static struct header copyright_header = { { 'C', 'P', 'R', 'T' }, 0L };

static unsigned char *buff;
static unsigned long stack[200];
static unsigned stack_index=0;

#define MAX_BANKS	4
long split_location[MAX_BANKS];
long data_size[MAX_BANKS];
long data_start[MAX_BANKS];

void push(unsigned long n) { stack[stack_index++] = n; }
unsigned long pop(void) { return(stack[--stack_index]); }

static void seek_write_header(FILE *fp, unsigned long pos, struct header *h)
{
    unsigned long opos;

    opos = ftell(fp);
    fseek(fp, pos, 0L);
    fwrite(h, sizeof(struct header), 1, fp);
    fseek(fp, opos, 0L);
}

struct list data_list;
struct data_node {
    long astart;
    long aend;
    long nstart;
    char fname[132];
    unsigned char flags;
};

void InvertMSB(char *buf, int size, int format)
{
    short *s;
    int i;

    if (!(format & IW_WAVE_FORMAT_8BIT)) {
	s = (short *)buf;
	size >>= 1;
    }
    for (i=0; i<size; i++) {
	if (!(format & IW_WAVE_FORMAT_8BIT)) {
	    s[i] = s[i] ^ 0x8000;
	} else {
	    buf[i] = buf[i] ^ 0x80;
	}
    }
}

long copy_data(char *fname, char *dir, ULONG start, ULONG end, FILE *fpo, int flags)
{
    char path[132];
    FILE *fpi;
    ULONG todo,read,written,size,total=0;
    int ret;
    int i;

    if (fname[1] == ':') {
	strcpy(path, fname);
    } else {
	strcpy(path, dir);
	strcat(path, fname);
    }
    fpi = fopen(path, "rb");
    if (fpi == NULL) {
	printf("Can't copy data file (%s)\n", path);
	return(-1);
    }
    if (fseek(fpi, start, 0L)) {
	printf("Can't find data in file (%s)\n", path);
	fclose(fpi);
	return(-2);
    }
    size = end - start + 1;
    do {
	todo=(size>SCRATCH_BUF_SIZE)?SCRATCH_BUF_SIZE:size;
	read=fread(scratch,1,(unsigned short)todo,fpi);
	if (!(flags & IW_WAVE_FORMAT_SIGNED)) InvertMSB(scratch,read,flags);
	written=fwrite(scratch,1,(unsigned short)todo,fpo);
	size-=written;
	total+=written;
    } while( size>0 && (read==written && read==todo) );
    if (read != written || read != todo) {
	printf("Error copying file (%s)\n", path);
	fclose(fpi);
	return(-4);
    }
    fclose(fpi);
    return(total);
}

long copy_data_8(char *fname, char *dir, ULONG start, ULONG end, FILE *fpo, unsigned char flags)
{
    char path[132];
    FILE *fpi;
    ULONG todo,read,written,size,total=0;
    int ret;
    int i, j;

    if (fname[1] == ':') {
	strcpy(path, fname);
    } else {
	strcpy(path, dir);
	strcat(path, fname);
    }
    fpi = fopen(path, "rb");
    if (fpi == NULL) {
	printf("Can't copy data file (%s)\n", path);
	return(-1);
    }
    if (fseek(fpi, start, 0L)) {
	printf("Can't find data in file (%s)\n", path);
	fclose(fpi);
	return(-2);
    }
    size = end - start + 1;
    if (!(flags & IW_WAVE_FORMAT_8BIT)) {
	size >>= 1;
    }
    do {
	if (flags & IW_WAVE_FORMAT_8BIT) {
	    todo=(size>SCRATCH_BUF_SIZE)?SCRATCH_BUF_SIZE:size;
	    read=fread(scratch,1,(unsigned short)todo,fpi);
	    if (!(flags & IW_WAVE_FORMAT_SIGNED)) InvertMSB(scratch,read,flags);
	    written=fwrite(scratch,1,(unsigned short)todo,fpo);
	    size-=written;
	    total+=written;
	} else {
	    todo=(size>(SCRATCH_BUF_SIZE>>1))?(SCRATCH_BUF_SIZE>>1):size;
	    read=fread(scratch,1,(unsigned short)(todo<<1),fpi);
	    if (!(flags & IW_WAVE_FORMAT_SIGNED)) InvertMSB(scratch,read,flags);
	    for (j=1, i=0; j < read; i++, j += 2) scratch[i] = scratch[j];
	    read >>= 1;
	    written=fwrite(scratch,1,(unsigned short)todo,fpo);
	    size-=written;
	    total+=written;
	}
    } while( size>0 && (read==written && read==todo) );
    if (read != written || read != todo) {
	printf("Error copying file (%s)\n", path);
	fclose(fpi);
	return(-4);
    }
    fclose(fpi);
    return(total);
}

short Normalize(short *value)
{
    short numShiftBits = 0;
    short msb, nextmsb;

    msb = (*value & 0x8000) >> 15;
    nextmsb = (*value & 0x4000) >> 14;
    while (msb == nextmsb) {
	*value <<= 1;
	numShiftBits ++;
	msb = (*value & 0x8000) >> 14;
	nextmsb = (*value & 0x4000) >> 14;
    }
    return(numShiftBits);
}
void Convert2ULAW(short *buf, int size, int format)
{
    short i, originalSample, newSample, X, YYY, ZZZZ, numberOfShiftBits;

    for (i=0; i<size; i++) {
	if (!(format & IW_WAVE_FORMAT_SIGNED)) {
	    originalSample = buf[i] ^ 0x8000;
	} else {
	    originalSample = buf[i];
	}
	originalSample >>=2;
	if (originalSample <= -8159) {
	    newSample = 0x00;
	} else if( originalSample >= 8159) {
	    newSample = 0x80;
	} else {
	    X = (originalSample & 0x2000) >> 6;
	    if (originalSample & 0x2000) {
		originalSample = -originalSample;
	    }
	    originalSample <<=2;
	    originalSample += 0x84;
	    numberOfShiftBits = Normalize(&originalSample);
	    originalSample ^= 0x4000;
	    ZZZZ = (originalSample & 0x3c00) >> 10;
	    YYY = (7- numberOfShiftBits) << 4;
	    newSample = X | YYY | ZZZZ;
	    newSample = ~newSample;
	}
	*((UCHAR *)buf+i) = newSample;
    }
}
long copy_data_ulaw(char *fname, char *dir, ULONG start, ULONG end, FILE *fpo, unsigned char flags)
{
    char path[132];
    FILE *fpi;
    ULONG todo,read,written,size,total=0;
    int ret;

    if (fname[1] == ':') {
	strcpy(path, fname);
    } else {
	strcpy(path, dir);
	strcat(path, fname);
    }
    fpi = fopen(path, "rb");
    if (fpi == NULL) {
	printf("Can't copy data file (%s)\n", path);
	return(-1);
    }
    if (fseek(fpi, start, 0L)) {
	printf("Can't find data in file (%s)\n", path);
	fclose(fpi);
	return(-2);
    }
    size = end - start + 1;
    if (!(flags & IW_WAVE_FORMAT_8BIT)) {
	size >>= 1;
    }
    do {
	if (flags & IW_WAVE_FORMAT_8BIT) {
	    todo=(size>SCRATCH_BUF_SIZE)?SCRATCH_BUF_SIZE:size;
	    read=fread(scratch,1,(unsigned short)todo,fpi);
	    if (!(flags & IW_WAVE_FORMAT_SIGNED)) InvertMSB(scratch,read,flags);
	    written=fwrite(scratch,1,(unsigned short)todo,fpo);
	    size-=written;
	    total+=written;
	} else {
	    todo=(size>(SCRATCH_BUF_SIZE>>1))?(SCRATCH_BUF_SIZE>>1):size;
	    read=fread(scratch,1,(unsigned short)(todo<<1),fpi);
	    read >>= 1;
	    Convert2ULAW((short *)scratch, read, flags);
	    written=fwrite(scratch,1,(unsigned short)todo,fpo);
	    size-=written;
	    total+=written;
	}
    } while( size>0 && (read==written && read==todo) );
    if (read != written || read != todo) {
	printf("Error copying file (%s)\n", path);
	fclose(fpi);
	return(-4);
    }
    fclose(fpi);
    return(total);
}

#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define MAX(a,b) ((a) > (b) ? (a) : (b))

int intersect(long a1, long a2, long b1, long b2)
{
    return((a1 >= b1 && a1 <= b2) ||
    	(a2 >= b1 && a2 <= b2) ||
	(b1 >= a1 && b1 <= a2) ||
	(b2 >= a1 && b2 <= a2));
}

int SaveFFF(int saveas8, int saveasulaw,char *rom_cfg_file)
{
    /* first step, go through entire patch library and create IDs */
    /* bottom up traversal of library tree, write records */
    char filename[132];
    struct node *dn, *dn1, *dn2;
    struct data_node *dln, *dln1;
    struct node *fffn;
    struct fff_file *fff;
    FILE *fp, *fp_rom, *fp_fff;
    struct iw_patch *pc;
    struct iw_layer *lc, *nlc;
    struct iw_wave *wc, *nwc;
    struct iw_data dc, *dcp;
    struct iw_envp *ec;
    struct iw_program progc;
    long plen, rom_start, rom_pos, rom_len, amount_written, last_file_start;
    int pi, li, wi, i, j;
    long was, wae;
    struct patch_lib *pl;
    struct iwu_fast_patch *fpi;
    struct header *ech;
    int still_intersections;
    struct header file_h, h;
#define temp_len 128
    char temp[temp_len];

    iwu_get_profile_string("rom configuration","file_type","srec",temp,temp_len,rom_cfg_file);
    if (stricmp(temp, "srec") == 0) {
	rom_format = SREC;
    } else if (stricmp(temp, "disk") == 0){
	rom_format = DISK;
    } else {
	rom_format = BINARY;
    }
    fp = fopen("THEROM.FFF", "wb");
    if (fp == NULL) {
	printf("Error creating THEROM.FFF file\n");
	return(1);
    }
    fp_rom = fopen("THEROM.DAT", "wb");
    if (fp_rom == NULL) {
	printf("Error creating THEROM.DAT file\n");
	return(2);
    }
    rom_pos = 0;	/* THErom.dat file location counter */
    for (pl=plib; pl != 0; pl = pl->next) {
	for (fffn=pl->fff_list.head; fffn; fffn = fffn->next) {
	    fff = fffn->data;
	    strcpy(filename, pl->patch_dir);
	    strcat(filename, pl->patch_filename);
	    env_minor_id = 0;
	    /* start the FFFF file */
	    patch_header.length = sizeof(struct iw_patch);
	    layer_header.length = sizeof(struct iw_layer);
	    wave_header.length = sizeof(struct iw_wave);
	    last_file_start = ftell(fp);
	    push(last_file_start);
	    fwrite(&file_header, sizeof(file_header), 1, fp);
	    /* go through original .FFF and write any copyright chunks */
	    fp_fff = fopen(filename, "rb");
	    if (fp_fff == NULL) {
		printf("Can't open FFFF file (%s)\n", filename);
		return(2);
	    }
	    if (fread(&file_h, sizeof(file_h), 1, fp_fff) != 1) {
		printf("can't read FFFF file (%s)\n", filename);
		return(3);
	    }
	    if (strncmp(file_h.tag, file_header.tag, sizeof(file_h.tag)) != 0) {
		printf("Not a valid FFFF file (%s)\n", filename);
		return(4);
	    }
	    /* find copyright chunk and write to beginning of rom file */
	    push(ftell(fp_fff));
	    while (fread(&h, sizeof(h), 1, fp_fff) == 1) {
		if (strncmp(h.tag, copyright_header.tag, sizeof(h.tag)) == 0) {
		    fwrite(&h, sizeof(h), 1, fp);
		    while (h.length--) {
			fputc(fgetc(fp_fff), fp);
		    }
		} else {
		    fseek(fp_fff, h.length, SEEK_CUR);
		}
	    }
	    fseek(fp_fff, pop(), SEEK_SET);
	    /* go through original .FFF and write all used envelope chunks */
	    for (fpi = fff->patches, i=0; i < fff->npatches; i++, fpi++) {
		for (li=0, lc=fpi->p->layers; li < fpi->p->nlayers; li++, lc=lc->id.p) {
		    if (lc->penv.p) {
			ec = lc->penv.p;
			ech = (struct header *)ec - 1;
			if (ech->tag[0]) { /* not written to disk yet */
			    ec->id.id.major_id = ENV_CHUNK;
			    ec->id.id.minor_id = env_minor_id++;
			    fwrite(ech, sizeof(envp_header), 1, fp);
			    fwrite(ec, ech->length, 1, fp);
			    ech->tag[0] = 0;
			}
			lc->penv.id.major_id = ec->id.id.major_id;
			lc->penv.id.minor_id = ec->id.id.minor_id;
		    }
		    if (lc->venv.p) {
			ec = lc->venv.p;
			ech = (struct header *)ec - 1;
			if (ech->tag[0]) { /* not written to disk yet */
			    ec->id.id.major_id = ENV_CHUNK;
			    ec->id.id.minor_id = env_minor_id++;
			    fwrite(ech, sizeof(envp_header), 1, fp);
			    fwrite(ec, ech->length, 1, fp);
			    ech->tag[0] = 0;
			}
			lc->venv.id.major_id = ec->id.id.major_id;
			lc->venv.id.minor_id = ec->id.id.minor_id;
		    }
		}
	    }
	    if (rom_format == DISK) {
		iwu_get_profile_string("rom configuration","rom_name_prefix","",temp,temp_len,rom_cfg_file);
		if (temp[0] == 0) {
		    printf("Can't find ROM file prefix in config file\n");
		    return(1);
		}
		sprintf(dc.filename, "%s.FFF", temp, i);
		data_header.length = strlen(dc.filename) + 5;
		fwrite(&data_header, sizeof(data_header), 1, fp);
		dc.id.id.major_id = DATA_CHUNK;
		dc.id.id.minor_id = 0;
		fwrite(&dc, data_header.length, 1, fp);
	    } else {
		/* reserve space in .FFF for up to 4 data chunks (1 chunk per ROM) */
		for (i=0; i < 4; i++) {
		    data_header.length = 9;
		    fwrite(&data_header, sizeof(data_header), 1, fp);
		    sprintf(dc.filename, "ROM%d", i);
		    dc.id.id.major_id = DATA_CHUNK;
		    dc.id.id.minor_id = i;
		    fwrite(&dc, data_header.length, 1, fp);
		}
	    }
	    /* collect info to condense THErom data file */
	    InitList(&data_list);
	    /* create list of all wave ranges in absolute bytes */
	    for (fpi = fff->patches, i=0; i < fff->npatches; i++, fpi++) {
		for (li=0, lc=fpi->p->layers; li < fpi->p->nlayers; li++, lc=lc->id.p) {
		    for (wi=0, wc=lc->waves; wi < lc->nwaves; wc = wc->id.p, wi++) {
			dln = malloc(sizeof(struct data_node));
			if (dln == 0) {
			    printf("Out of memory\n");
			    exit(0);
			}
			dln->flags = wc->format;
			dln->astart = wc->start;
			if (wc->format & IW_WAVE_FORMAT_8BIT) {
			    dln->aend = dln->astart + wc->size - 1;
			} else {
			    dln->aend = dln->astart + (wc->size<<1) - 1;
			}
			dcp = (struct iw_data *)wc->data_id.p;
			strcpy(dln->fname, dcp->filename);
			strupr(dln->fname);
			dn = malloc(sizeof(struct node));
			if (dn == 0) {
			    printf("Out of memory\n");
			    exit(0);
			}
			dn->data = dln;
			AddNode(&data_list, dn, 0, APPEND);
		    }
		}
	    }
	    /* merge waves that share space */
	    do {
		still_intersections = 0;
		for (dn = data_list.head; dn != 0; dn = dn->next) {
		    dln = dn->data;
		    for (dn1 = dn->next; dn1 != 0; dn1 = dn2) {
			dn2 = dn1->next;
			dln1 = dn1->data;
			if (strcmp(dln->fname, dln1->fname) == 0) {
			    if (intersect(dln->astart, dln->aend, dln1->astart, dln1->aend)) {
				dln->astart = MIN(dln->astart, dln1->astart);
				dln->aend = MAX(dln->aend, dln1->aend);
				DeleteNode(&data_list, dn1);
				free(dn1->data);
				free(dn1);
				still_intersections = 1;
			    }
			}
		    }
		}
	    } while (still_intersections);
	    /* calculate size of rom to draw percent complete meter */
	    rom_len = 0;
	    for (dn = data_list.head; dn != 0; dn = dn->next) {
		dln = dn->data;
		rom_len += dln->aend - dln->astart + 1;
	    }
	    /* create THEROM.DAT file */
	    rom_start = rom_pos;
	    for (dn = data_list.head; dn != 0; dn = dn->next) {
		dln = dn->data;
		/* write data */
		dln->nstart = rom_pos;
		if (saveas8) {
		    amount_written = copy_data_8(dln->fname,pl->patch_dir,dln->astart,dln->aend,fp_rom,dln->flags);
		} else if (saveasulaw) {
		    amount_written = copy_data_ulaw(dln->fname,pl->patch_dir,dln->astart,dln->aend,fp_rom,dln->flags);
		} else {
		    amount_written = copy_data(dln->fname,pl->patch_dir,dln->astart,dln->aend,fp_rom,dln->flags);
		}
		dln->flags |= IW_WAVE_FORMAT_SIGNED;
		if (amount_written <= 0) {
		    printf("Error writing THEROM.DAT file\n");
		    fclose(fp_rom);
		    return(9);
		}
		rom_pos += amount_written;
		if (rom_pos & 1) {
		    rom_pos++;
		    putc(0, fp_rom);
		}
		printf("%03ld   \r", ((rom_pos-rom_start) * 100+50) / rom_len);
	    }
	    /* go through every program - layer - wave and write chunks */
	    /* also fixup ID's and update offsets */
	    for (fpi = fff->patches, pi=0; pi < fff->npatches; pi++, fpi++) {
		push(ftell(fp));
		fwrite(&program_header, sizeof(program_header), 1, fp);
		plen = 0;
		progc.id.id.major_id = pi+1;
		progc.id.id.minor_id = 0;
		progc.version.id.major_id = IW_PATCH_VERSION_MAJOR;
		progc.version.id.minor_id = IW_PATCH_VERSION_MINOR;
		fwrite(&progc, sizeof(progc), 1, fp);
		plen += sizeof(progc);
		fwrite(&patch_header, sizeof(patch_header), 1, fp);
		plen += sizeof(patch_header);
		pc = fpi->p;
		pc->id.id.major_id = pi+1;
		pc->id.id.minor_id = 0;
		fwrite(pc, patch_header.length, 1, fp);
		plen += patch_header.length;
		for (li=0, lc=fpi->p->layers; li < fpi->p->nlayers; li++, lc=nlc) {
		    nlc = lc->id.p;
		    fwrite(&layer_header, sizeof(layer_header), 1, fp);
		    plen += sizeof(layer_header);
		    lc->id.id.major_id = li;
		    lc->id.id.minor_id = 0;
		    /* volume envelope id's already fixed up */
		    /* pitch envelope id's already fixed up */
		    fwrite(lc, layer_header.length, 1, fp);
		    plen += layer_header.length;
		    wc = lc->waves;
		    for (wi=0; wi < lc->nwaves; wi++, wc=nwc) {
			nwc = wc->id.p;
			fwrite(&wave_header, sizeof(wave_header), 1, fp);
			plen += sizeof(wave_header);
			wc->id.id.major_id = li;
			wc->id.id.minor_id = wi;
			/* fix offsets for wave chunk */
			for (dn = data_list.head; dn != 0; dn = dn->next) {
			    dln = dn->data;
			    dcp = (struct iw_data *)wc->data_id.p;
			    if (strcmp(dln->fname, dcp->filename) == 0) {
				was = wc->start;
				if (wc->format & IW_WAVE_FORMAT_8BIT) {
				    wae = was + wc->size - 1;
				} else {
				    wae = was + (wc->size<<1) - 1;
				}
				if (was >= dln->astart && wae <= dln->aend) {
				    if (saveas8 && !(wc->format & IW_WAVE_FORMAT_8BIT)) {
					wc->format |= IW_WAVE_FORMAT_8BIT|IW_WAVE_FORMAT_SIGNED;
					wc->start = (was - dln->astart)/2 + dln->nstart;
				    }
				    if (saveasulaw && !(wc->format & IW_WAVE_FORMAT_8BIT)) {
					wc->format |= IW_WAVE_FORMAT_8BIT|IW_WAVE_FORMAT_ULAW|IW_WAVE_FORMAT_SIGNED;
					wc->start = (was - dln->astart)/2 + dln->nstart;
				    }
				    if (!saveas8 && !saveasulaw) {
				        wc->format |= IW_WAVE_FORMAT_SIGNED;
					wc->start = was - dln->astart + dln->nstart;
				    }
				    break;
				}
			    }
			}
			/* point to first data chunk of the four - for now */
			wc->data_id.id.major_id = DATA_CHUNK;
			wc->data_id.id.minor_id = 0;
			fwrite(wc, wave_header.length, 1, fp);
			plen += wave_header.length;
		    }
		}
		program_header.length = plen;
		seek_write_header(fp, pop(), &program_header);
	    }
	    /* write .fff header */
	    file_header.length = ftell(fp) - sizeof(file_header) - last_file_start;
	    seek_write_header(fp, pop(), &file_header);
	    /* empty data list */
	    for (dn = data_list.head; dn != 0; dn = dn1) {
		dn1 = dn->next;
		free(dn->data);
		free(dn);
	    }
	}
    }

    if (rom_format != DISK) {
	// Now append the SBOS stuff to the end of the file
	append_sbos_space(fp,rom_cfg_file);
    }

    fclose(fp_rom);
    if (ftell(fp) & 1) {
        putc(0, fp);
    }
    fclose(fp);
    return(0);
}


char s0_rec = 0;
char s7_rec = 0;
char srec_len = 0;
unsigned long srec_addr = 0;
unsigned long srec_filepos = 0;
unsigned char srec_csum = 0;
char srec[132];
char *srecp;
FILE *srec_fp;

int puthex(int num)
{
    char c;
    char hex[2];

    c = (num>>4) & 0xF;
    if (c > 9) {
	hex[0] = c - 10 + 'A';
    } else {
	hex[0] = c + '0';
    }
    c = num & 0x0f;
    if (c > 9) {
	hex[1] = c - 10 + 'A';
    } else {
	hex[1] = c + '0';
    }
    srec_csum += (unsigned char)(num & 0xFF);
    if (putc(hex[0], srec_fp) == EOF) return(-1);
    if (putc(hex[1], srec_fp) == EOF) return(-1);
    return(0);
}

long srec_ftell(void)
{
    return(srec_filepos);
}

int srec_flush(void)
{
    int i, leave=0;

    if (s0_rec) {
	srecp = srec;
	if (putc('S', srec_fp) == EOF) return(-1);	// S0
	if (putc('0', srec_fp) == EOF) return(-1);
	srec_csum = 0;
	if (puthex(3)) return(-1);
	if (puthex(0)) return(-1);
	if (puthex(0)) return(-1);
	srec_csum = 255 - srec_csum;
	if (puthex(srec_csum)) return(-1);
	if (putc('\n', srec_fp) == EOF) return(-1);
	s0_rec = 0;
	leave=1;
    }
    if (s7_rec) {
	srecp = srec;
	if (putc('S', srec_fp) == EOF) return(-1);
	if (putc('7', srec_fp) == EOF) return(-1);
	srec_csum = 0;
	if (puthex(3)) return(-1);
	if (puthex(0)) return(-1);
	if (puthex(0)) return(-1);
	srec_csum = 255 - srec_csum;
	if (puthex(srec_csum)) return(-1);
	if (putc('\n', srec_fp) == EOF) return(-1);
	s7_rec = 0;
	leave=1;
    }
    if (leave) return(0);
    if (putc('S', srec_fp) == EOF) return(-1);
    if (putc('3', srec_fp) == EOF) return(-1);
    srec_csum = 0;
//    if (puthex(srec_len + 3)) return(-1);
    if (puthex(srec_len + 5)) return(-1);
    if (puthex((srec_addr >> 24) & 0xFF)) return(-1);
    if (puthex((srec_addr >> 16) & 0xFF)) return(-1);
    if (puthex((srec_addr >> 8) & 0xFF)) return(-1);
    if (puthex((srec_addr) & 0xFF)) return(-1);
    srec_addr += srec_len;
    for (i=0; i < srec_len; i++) {
	if (puthex(srec[i])) return(-1);
//	srec_csum = 255 - srec_csum;
    }
    
    srec_csum = 255 - srec_csum;

    if (puthex(srec_csum)) return(-1);
    srec_len = 0;
    srecp = srec;
    if (putc('\n', srec_fp) == EOF) return(-1);
    return(0);
}

int srec_putc(int val)
{
    if (rom_format == SREC) {
	if (s0_rec) {
	    srec_len = 0;
	    if (srec_flush()) return(-1);
	}
	*srecp++ = val;
	srec_len++;
	srec_filepos++;
	if (srec_len == 32) return(srec_flush());
	return(0);
    } else {
	srec_filepos++;
	if (putc(val, srec_fp) == EOF) return(1);
	return(0);
    }
}

FILE *srec_fopen(char *name, char *mode)
{
    FILE *fp;
    char *cp;

    if (rom_format == SREC) {
	fp = fopen(name, "w");
    } else {
	fp = fopen(name, "wb");
    }
    if (fp == 0) return(0);
    srec_fp = fp;
    srec_addr = 0;
    srec_filepos = 0;
    srecp = srec;
    s0_rec = 1;	/* tell srec writer to start with s0 record */
    return(fp);
}

int srec_fwrite(void *ptr, unsigned reclen, unsigned nrecs)
{
    char *cp = (char *)ptr;
    unsigned short i;

    for (i=0; i < (reclen*nrecs); i++) {
	if (srec_putc(*cp++)) return(i);
    }
    return(i);
}

int srec_fclose(void)
{
    if (rom_format == SREC) {
	if (srec_len) if (srec_flush()) return(-1);
	s7_rec = 1;
	if (srec_flush()) return(-1);
    }
    fclose(srec_fp);
    return(0);
}

int srec_copy_data(char *fname, char *dir, ULONG start, ULONG end)
{
    char path[132];
    FILE *fpi;
    ULONG todo,read,written,size,total=0;
    int ret;
    int i;

    if (fname[1] == ':') {
	strcpy(path, fname);
    } else {
	strcpy(path, dir);
	strcat(path, fname);
    }
    fpi = fopen(path, "rb");
    if (fpi == NULL) {
	printf("Can't copy data file (%s)\n", path);
	return(-1);
    }
    if (fseek(fpi, start, 0L)) {
	printf("Can't find data in file (%s)\n", path);
	fclose(fpi);
	return(-2);
    }
    size = end - start + 1;
    do {
	todo=(size>SCRATCH_BUF_SIZE)?SCRATCH_BUF_SIZE:size;
	read=fread(scratch,1,(unsigned short)todo,fpi);
	written=srec_fwrite(scratch,1,(unsigned short)todo);
	size-=written;
	total+=written;
    } while( size>0 && (read==written && read==todo) );
    if (read != written || read != todo) {
	printf("Error copying file (%s)\n", path);
	fclose(fpi);
	return(-4);
    }
    fclose(fpi);
    return(total);
}

int SaveBin(char *romcfg)
{
    char destination_prefix[128];
    long header_size = 512;
    long fff_size;
    long rom_bytes;
    long l, previous_split1, previous_split2, len;
    struct patch_lib *pl, *pln;
    struct node *dn, *dn1, *dn2;
    struct data_node *dln, *dln1;
    struct iw_patch *pc;
    struct iw_layer *lc, *nlc;
    struct iw_wave *wc, *nwc;
    struct iw_data dc, *dcp;
    struct stat statbuf;
    struct node *fffn;
    struct fff_file *fff;
    struct iwu_fast_patch *fpi;
    struct rom_hdr rh;
    struct header file_h, h, h1;
    int i, bank, ret;
    int pi, li, wi;
    int still_intersections;
    unsigned char *cp, c;
    long was, wae;
    FILE *fp, *fp_fff;
    char rom_name[128];
#define temp_len 128
    char temp[temp_len];

    iwu_get_profile_string("rom configuration","file_type","srec",temp,temp_len,romcfg);
    if (stricmp(temp, "srec") == 0) {
	rom_format = SREC;
    } else if (stricmp(temp, "disk") == 0){
	rom_format = DISK;
    } else {
	rom_format = BINARY;
    }
    iwu_get_profile_string("rom configuration","destination_dir","",temp,temp_len,romcfg);
    i = strlen(temp);
    if (i && temp[i-1] != '\\') {
	strcat(temp, "\\");
    }
    strcpy(destination_prefix, temp);
    iwu_get_profile_string("rom configuration","rom_name_prefix","",temp,temp_len,romcfg);
    if (temp[0] == 0) {
	printf("Can't find ROM file prefix in config file\n");
	return(1);
    }
    strcat(destination_prefix, temp);

    /* prepare rom header */
    memset(&rh, 0, sizeof(struct rom_hdr));
    strncpy(rh.iwave, "INTRWAVE", 8);
    rh.rom_hdr_revision = 0;
    iwu_get_profile_string("rom configuration","series_number","",temp,temp_len,romcfg);
    rh.series_number = atoi(temp);
    memset(temp, 0, temp_len);
    iwu_get_profile_string("rom configuration","series_name","",temp,temp_len,romcfg);
    strncpy(rh.series_name, temp, 16);
    memset(temp, 0, temp_len);
    iwu_get_profile_string("rom configuration","date","",temp,temp_len,romcfg);
    strncpy(rh.date, temp, 10);
    memset(temp, 0, temp_len);
    iwu_get_profile_string("rom configuration","vendor_revision_major","",temp,temp_len,romcfg);
    rh.vendor_revision_major = atoi(temp);
    memset(temp, 0, temp_len);
    iwu_get_profile_string("rom configuration","vendor_revision_minor","",temp,temp_len,romcfg);
    rh.vendor_revision_minor = atoi(temp);
    memset(temp, 0, temp_len);
    iwu_get_profile_string("rom configuration","rom_size","",temp,temp_len,romcfg);
    rh.rom_size = atol(temp);
    memset(temp, 0, temp_len);
    iwu_get_profile_string("rom configuration","copyright","",rh.copyright,128,romcfg);
    iwu_get_profile_string("rom configuration","vendor_name","",rh.vendor_name,64,romcfg);
    iwu_get_profile_string("rom configuration","rom_description","",rh.rom_description,128,romcfg);

    buff = malloc(65000);
    if (buff == 0) {
	printf("out of memory\n");
	return(9);
    }
    /* clear out patch library and than reload */
    for (pl=plib; pl != 0; pl = pln) {
	pln = pl->next;
	while ((fffn=pl->fff_list.head) != 0) {
	    fff = fffn->data;
	    free(fff->pbuf);
	    if (fff->npatches) free(fff->patches);
	    free(fff);
	    DeleteNode(&pl->fff_list, fffn);
	    free(fffn);
	}
	free(pl);
    }

    pl = (struct patch_lib *)malloc(sizeof(struct patch_lib));
    if (pl == NULL) {
	fprintf(stderr, "Out of memory\n");
	return(IWU_PATCH_NOMEM);
    }
    memset(pl, 0, sizeof(struct patch_lib));
    strcpy(pl->patch_filename, "THEROM.FFF");
    pl->next = 0;
    plib = pl;

    ret = iwu_fff_load(pl);
    if (ret != IWU_PATCH_OK) {
	fprintf(stderr, "Error: %s\n", iwu_error_str(ret));
	return(ret);
    }
    stat("THEROM.FFF", &statbuf);
    fff_size = statbuf.st_size;
    stat("THEROM.DAT", &statbuf);
    l = statbuf.st_size;
    printf("Original data size = %ld\n", l);
    if (rom_format == DISK) {
	for (bank=0; bank < MAX_BANKS; bank++) {
	    data_size[bank] = 0;
	    data_start[bank] = 0;
	    split_location[bank] = 0;
	}
    } else {
	for (bank=0; bank < MAX_BANKS; bank++) {
	    data_size[bank] = header_size;
	    data_start[bank] = header_size;
	    split_location[bank] = 0;
	}
    }
    bank = 0;
    data_size[bank] += fff_size;
    data_start[bank] += fff_size;
    /* go through all patches in each .FFF file and */
    /* create list of all wave ranges in absolute bytes. */
    /* Find all intersections in waves in order to create lists of */
    /* data which can be considered "atomic" and can move independantly */
    /* of other data. */

    InitList(&data_list);
    for (pl=plib; pl != 0; pl = pl->next) {
	for (fffn=pl->fff_list.head; fffn; fffn = fffn->next) {
	    fff = fffn->data;
	    for (fpi = fff->patches, i=0; i < fff->npatches; i++, fpi++) {
		for (li=0, lc=fpi->p->layers; li < fpi->p->nlayers; li++, lc=lc->id.p) {
		    for (wi=0, wc=lc->waves; wi < lc->nwaves; wc = wc->id.p, wi++) {
			dln = malloc(sizeof(struct data_node));
			if (dln == 0) {
			    printf("Out of memory\n");
			    exit(0);
			}
			dln->flags = wc->format;
			dln->astart = wc->start;
			if (wc->format & IW_WAVE_FORMAT_8BIT) {
			    dln->aend = dln->astart + wc->size - 1;
			} else {
			    dln->aend = dln->astart + (wc->size<<1) - 1;
			}
			dcp = (struct iw_data *)wc->data_id.p;
			strcpy(dln->fname, dcp->filename);
			strupr(dln->fname);
			dn = malloc(sizeof(struct node));
			if (dn == 0) {
			    printf("Out of memory\n");
			    exit(0);
			}
			dn->data = dln;
			AddNode(&data_list, dn, 0, APPEND);
		    }
		}
	    }
	}
    }
    /* merge waves that share space */
    do {
	still_intersections = 0;
	for (dn = data_list.head; dn != 0; dn = dn->next) {
	    dln = dn->data;
	    for (dn1 = dn->next; dn1 != 0; dn1 = dn2) {
		dn2 = dn1->next;
		dln1 = dn1->data;
		if (strcmp(dln->fname, dln1->fname) == 0) {
		    if (intersect(dln->astart, dln->aend, dln1->astart, dln1->aend)) {
			dln->astart = MIN(dln->astart, dln1->astart);
			dln->aend = MAX(dln->aend, dln1->aend);
			DeleteNode(&data_list, dn1);
			free(dn1->data);
			free(dn1);
			still_intersections = 1;
		    }
		}
	    }
	}
    } while (still_intersections);
    /* now each data node in list is independant of other nodes */
//    rom_bytes = (unsigned long)rom_size * 1024UL * 1024UL;
    rom_bytes = rh.rom_size;
    previous_split1 = -1;
    while ((data_size[bank] + l) > rom_bytes) {
	/* find location to split data */
	/* go through list and find maximum data size that fits in bank */
	for (dn = data_list.head; dn != 0; dn = dn->next) {
	    dln = dn->data;
	    if ((dln->aend-previous_split1+data_size[bank]) <= rom_bytes &&
		(dln->aend > split_location[bank]) &&
		(dln->aend > previous_split1)) {
		split_location[bank] = dln->aend;
	    }
	}
	l -= (split_location[bank] - previous_split1);
	data_size[bank] += split_location[bank] - previous_split1;
	previous_split1 = split_location[bank++];
    }
    data_size[bank] += l;
    split_location[bank] = l + previous_split1;
    for (bank=0; bank < MAX_BANKS && split_location[bank]; bank++) {
	printf("ROM%d data                    = %ld\n", bank, data_size[bank] - data_start[bank]);
	printf("ROM%d size                    = %ld\n", bank, data_size[bank]);
	printf("ROM%d split location          = %ld\n", bank, split_location[bank]);
	printf("\n");
    }
    /* write first rom bank header and FFF's */
    previous_split1 = -1;
    for (bank=0; bank < MAX_BANKS && split_location[bank]; bank++) {
	if (bank != 0) rh.series_number++;
	for (c=0, cp=(char *)&rh, i=0; i < 511; i++, cp++) c += *cp;
	rh.csum = 256 - c;
	if (rom_format == DISK) {
	    sprintf(rom_name, "%s.FFF", destination_prefix);
	} else {
	    sprintf(rom_name, "%s%d.ROM", destination_prefix,rh.series_number);
	}
	fp = srec_fopen(rom_name, "wb");
	if (fp == NULL) {
	    fprintf(stderr, "Can't create %s\n", rom_name);
	    return(1);
	} else {
	    printf("writing %s\n", rom_name);
	}
	if (rom_format != DISK) {
	    /* write rom header */
	    srec_fwrite(&rh, sizeof(struct rom_hdr), 1);
	}

	if (bank == 0) {
	    fp_fff = fopen("THEROM.FFF", "rb");
	    if (fp_fff == 0) {
		printf("can't open THEROM.FFF\n");
		return(3);
	    }
	    while (1) {
		if (fread(&file_h, sizeof(file_h), 1, fp_fff) != 1) break;
		srec_fwrite(&file_h, sizeof(file_h), 1);
		/* write all chunks from original FFFF to rom */
		len = 0;
		while (len < file_h.length) {
		    if (fread(&h, sizeof(h), 1, fp_fff) != 1) {
			bad_patch:
			fprintf(stderr, "error reading THEROM.FFF file\n");
			return(5);
		    }
		    len += sizeof(h);
		    srec_fwrite(&h, sizeof(h), 1);
		    if (fread(buff, h.length, 1, fp_fff) != 1) goto bad_patch;
		    len += h.length;

		    if (strncmp(h.tag, sbos_header.tag, sizeof(h.tag)) == 0) {
			write_sbos_data(srec_ftell(),buff,plib);
		    }

// LOOK FOR 'SBOS' chunk. Now whip thru loaded FFF file (plib) and get my data.
// Loaded FFF file is still OK. ID's are address so finding envs etc are easy.
//iwu_find_patch() to find all my patches (sine,melodics and percs) and
//get data for them.
// Also can fixup my start addresses using code like below ...

// load up 'buff' RAM and then seek back to the right place and write buff
// back out. This writes it to ROM0.DAT file.



		    if (strncmp(h.tag, program_header.tag, sizeof(h.tag)) == 0) {
			cp = buff;
			cp += sizeof(struct iw_program);
			/* read patch header */
			memcpy(&h1, cp, sizeof(h1));
			cp += sizeof(h1);
			pc = (struct iw_patch *)cp;
			cp += h1.length;
			for (li=0; li < pc->nlayers; li++) {
			    memcpy(&h1, cp, sizeof(h1));
			    cp += sizeof(h1);
			    if (memcmp(&h1, "LAYR", 4) != 0) goto bad_patch;
			    lc = (struct iw_layer *)cp;
			    cp += h1.length;
			    for (wi=0; wi < lc->nwaves; wi++) {
				memcpy(&h1, cp, sizeof(h1));
				cp += sizeof(h1);
				if (memcmp(&h1, "WAVE", 4) != 0) goto bad_patch;
// COPY THIS to find new wave info ...
				wc = (struct iw_wave *)cp;
				wc->format |= IW_WAVE_FORMAT_ROM;
				/* fix up wave chunk and point to correct data chunk */
				for (dn = data_list.head; dn != 0; dn = dn->next) {
				    dln = dn->data;
				    was = wc->start;
				    if (wc->format & IW_WAVE_FORMAT_8BIT) {
					wae = was + wc->size - 1;
				    } else {
					wae = was + (wc->size<<1) - 1;
				    }
				    if (was >= dln->astart && wae <= dln->aend) {
					/* now find out which chip data is in */
					i = 0;
					previous_split2 = -1;
					while (!(was > previous_split2 && wae <= split_location[i])) {
					    previous_split2 = split_location[i++];
					}
					wc->start = (wc->start - (previous_split2 + 1)) + (i * 4UL * 1024UL * 1024UL) + data_start[i];
					wc->data_id.id.major_id = DATA_CHUNK;
					wc->data_id.id.minor_id = i;
					break;
				    }
				}
// TO here .....
				cp += h1.length;
			    }
			}
		    }
		    srec_fwrite(buff, h.length, 1);
		}
	    }
	    if (srec_ftell() & 1) {
		srec_putc(0);
	    }
	    fclose(fp_fff);
	}
	/* finish writing out data */
	printf("copying from %ld to %ld (%ld bytes)\n", previous_split1+1, split_location[bank], split_location[bank]-previous_split1);
	srec_copy_data("THEROM.DAT", "", previous_split1+1, split_location[bank]);
	srec_fclose();
	previous_split1 = split_location[bank];
    }
    /* empty data list */
    for (dn = data_list.head; dn != 0; dn = dn1) {
	dn1 = dn->next;
	free(dn->data);
	free(dn);
    }
    /* free patch lib */
    iwu_fff_unload(plib);
    free(plib);

    free(buff);
    unlink("THEROM.FFF");
    unlink("THEROM.DAT");
    return(0);
}
