#ifdef _WINDOWS
#include <windows.h>
#endif
#include <string.h>
#include <malloc.h>
#include <string.h>
#include <stdlib.h>
#ifndef _WINDOWS
#include <mem.h>
#endif
#ifndef LUCID
#include <dos.h>
#include <fcntl.h>
#include <io.h>
#endif

#include "iw.h"
#include "list.h"
#include "patch.h"
#ifdef LUCID
#include "dx.h"
#define _dos_close _dx_close
#define _dos_open _dx_open
#define _dos_read _dx_read
#define getenv dx_getenv
#endif
#ifdef _WINDOWS
extern void win_close(HFILE hf);
extern int win_open(char __far *fname, unsigned mode, HFILE __far *hf);
extern int win_read(HFILE hf, void __far *diskbuff, unsigned size, unsigned __far *bytes_in);
extern void __far *win_malloc(unsigned size);
extern void win_free(void __far *);
#define _dos_close win_close
#define _dos_open win_open
#define _dos_read win_read
#define lseek _lseek
#define malloc win_malloc
#define free win_free
#endif
#if defined(__WATCOMC__) && defined(__FLAT__)
#define _fstrlen strlen
#define _fstrcpy strcpy
#define _fstrcat strcat
#define _fstricmp stricmp
#define _fstrnicmp strnicmp
#define _fstrtok strtok
#define _fmemcpy memcpy
#define _fmemset memset
#endif

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

struct patch_lib RFAR*plib=0;

static struct idx {
    struct header h;
    union ID id;
    unsigned char RFAR *p;
};
static struct idx RFAR*ids=0;
static int nids;

static struct iwu_fast_patch RFAR *find_patch(int program, int bank, struct patch_lib RFAR *RFAR *ppl)
{
    struct patch_lib RFAR *pl;
    struct iw_patch RFAR *p;
    struct node RFAR *fffn;
    struct fff_file RFAR *fff;
    int top, bottom, middle;
    struct iwu_fast_patch RFAR *tfpi, RFAR *bfpi, RFAR *mfpi, fpi;      /* search index */

    for (pl=plib; pl != 0; pl = pl->next) {
	for (fffn=pl->fff_list.head; fffn; fffn = fffn->next) {
	    fff = fffn->data;
	    fpi.id.h.program = program;
	    fpi.id.h.bank = bank;
	    top = 0;
	    tfpi = &fff->patches[top];
	    bottom = fff->npatches - 1;
	    bfpi = &fff->patches[bottom];
	    while (tfpi->id.bank_program <= fpi.id.bank_program &&
		    bfpi->id.bank_program >= fpi.id.bank_program) {
		middle = ((bottom - top) >> 1) + top;
		mfpi = &fff->patches[middle];
		if (fpi.id.bank_program <= mfpi->id.bank_program) {
		    bottom = middle;
		    bfpi = mfpi;
		} else {
		    top = middle+1;
		    tfpi = mfpi + 1;
		}
		if (tfpi->id.bank_program == fpi.id.bank_program) {
		    if (ppl) *ppl = pl;
		    return(tfpi);
		} else if (bfpi->id.bank_program == fpi.id.bank_program) {
		    if (ppl) *ppl = pl;
		    return(bfpi);
		}
		if (bottom - top <= 1) break;
	    }
	}
    }
    return(0);
}

struct iwu_fast_patch RFAR *iwu_find_patch(int program, int bank, struct patch_lib RFAR *RFAR *ppl)
{
    struct iwu_fast_patch RFAR *fpi;    /* search index */

    fpi = find_patch(program, bank, ppl);
    if (fpi == 0 && bank != 0) {
	fpi = find_patch(program, 0, ppl);
    }
    return(fpi);
}

void iwu_clear_marked_patches(void)
{
    struct iwu_fast_patch RFAR *fpi;    /* search index */
    struct patch_lib RFAR *pl;
    struct node RFAR *fffn;
    struct fff_file RFAR *fff;
    int i;

    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++) {
		fpi->flags &= ~IWU_PATCH_NEEDED;
	    }
	}
    }
    return;
}

static int load_patch(struct iwu_fast_patch RFAR *fpi, struct patch_lib RFAR *pl, char RFAR *diskbuff, int bsize, void (*free_memory_cb)(struct iwu_fast_patch RFAR *, int), int load_as_8)
{
    struct iw_patch RFAR *pc;
    struct iw_layer RFAR *lc;
    struct iw_wave RFAR *wc;
    struct iw_data RFAR *dc;
    ULONG len, mem, noffset, mlen;
    USHORT xfersize;
#if defined(LUCID)
    USHORT fp = 0;
    ULONG bytes_in;
#elif defined(_WINDOWS)
    HFILE fp=0;
    unsigned bytes_in;
#else
    int fp = 0;
    unsigned bytes_in;
#endif
    int li, wi, i, j;
    char text[80];
    char ltext[80];
    int ret;
    UCHAR dmac;

    pc = fpi->p;
    lc = pc->layers;
    for (li=0; li < pc->nlayers; li++, lc=lc->id.p) {
	wc = lc->waves;
	for (wi=0; wi < lc->nwaves; wi++, wc=wc->id.p) {
	    dc = (struct iw_data RFAR*)wc->data_id.p;
	    if (dc->filename[1] == ':') {
		_fstrcpy(text, dc->filename);
	    } else {
		_fstrcpy(text, pl->patch_dir);
		_fstrcat(text, dc->filename);
	    }
	    if (fp == 0 || (_fstricmp(text, ltext))) {
		if (fp) _dos_close(fp);
		if (_dos_open(text, O_RDONLY, &fp) != 0) {
		    return(IWU_PATCH_CANTOPEN);
		}
		_fstrcpy(ltext, text);
	    }
#ifdef LUCID
	    if (_dx_seek(fp, wc->start, SEEK_SET, &noffset)) {
		_dx_close(fp);
		return(IWU_PATCH_FILE_CORRUPT);
	    }
#else
	    if (lseek(fp, wc->start, SEEK_SET) == -1L) {
		_dos_close(fp);
		return(IWU_PATCH_FILE_CORRUPT);
	    }
#endif
	    len = wc->size;
	    if (!(wc->format & IW_WAVE_FORMAT_8BIT)) {
		len <<= 1;
	    }
	    if (load_as_8 && !(wc->format & IW_WAVE_FORMAT_8BIT)) {
		/* mlen is number of bytes to store in dram */
		mlen = len/2;
		wc->m_format = wc->format | IW_WAVE_FORMAT_8BIT;
	    } else {
		/* mlen is number of bytes to store in dram */
		mlen = len;
		wc->m_format = wc->format;
	    }
	    dmac = 0;
	    if (!load_as_8 && !(wc->format & IW_WAVE_FORMAT_8BIT)) {
		dmac |= IW_DMA_DATA_16;
	    }
	    if (!(wc->format & IW_WAVE_FORMAT_SIGNED)) {
		dmac |= IW_DMA_INVERT_MSB;
	    }
	    mem = iw_malloc(mlen);
	    if (mem == 0L) {
		if (free_memory_cb) (*free_memory_cb)(fpi, 1);
		mem = iw_malloc(mlen);
		if (mem == 0L) {
		    if (free_memory_cb) (*free_memory_cb)(fpi, 2);
		    _dos_close(fp);
		    return(IWU_PATCH_NOMEM);
		}
	    }
	    wc->m_start = mem;
	    while (len) {
		xfersize = (USHORT)((len < bsize) ? len : bsize);
		len -= xfersize;
		if (_dos_read(fp, diskbuff, xfersize, &bytes_in) != 0) bytes_in = 0;
		if (bytes_in != xfersize) {
		    _dos_close(fp);
		    return(IWU_PATCH_FILE_CORRUPT);
		}
		if (load_as_8 && !(wc->format & IW_WAVE_FORMAT_8BIT)) {
		    for (j=1, i=0; j < xfersize; i++, j += 2)
			diskbuff[i] = diskbuff[j];
		    xfersize /= 2;
		}
		iw_poke_block(diskbuff,mem,xfersize,dmac);
		mem += xfersize;
	    }
	}
    }
    if (fp) _dos_close(fp);
    fpi->flags |= IWU_PATCH_LOADED;
    return(IWU_PATCH_OK);
}

void iwu_load_marked_patches(char RFAR *diskbuff, int bsize, void (*free_memory_cb)(struct iwu_fast_patch RFAR *, int attempt), int load_as_8)
{
    struct iwu_fast_patch RFAR *fpi;    /* search index */
    struct patch_lib RFAR *pl;
    struct node RFAR *fffn;
    struct fff_file RFAR *fff;
    int i;
#ifdef ROM_DEBUG
    change_bank(7);
#endif
    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++) {
		if (fpi->flags & IWU_PATCH_NEEDED && !(fpi->flags & IWU_PATCH_LOADED)) {
		    load_patch(fpi, pl, diskbuff, bsize, free_memory_cb, load_as_8);
		}
	    }
	}
    }
    return;
}

void iwu_mark_patch(int program, int bank)
{
    struct iwu_fast_patch RFAR *fpi;    /* search index */
    struct patch_lib RFAR *pl;

    fpi = iwu_find_patch(program, bank, &pl);
    if (fpi) fpi->flags |= IWU_PATCH_NEEDED;
    return;
}

void iwu_unmark_patch(int program, int bank)
{
    struct iwu_fast_patch RFAR *fpi;    /* search index */
    struct patch_lib RFAR *pl;

    fpi = iwu_find_patch(program, bank, &pl);
    if (fpi) fpi->flags &= ~IWU_PATCH_NEEDED;
    return;
}

int iwu_unload_patch(int prog, int bank)
{
    struct iwu_fast_patch RFAR *ifp;
    struct patch_lib RFAR *pl;
    struct iw_patch RFAR *pc;
    struct iw_layer RFAR *lc;
    struct iw_wave RFAR *wc;
    int li, wi;
    int ret;

    ifp = iwu_find_patch(prog, bank, &pl);
    if (ifp == 0) return(IWU_PATCH_CANTFIND);
    if (ifp->flags & IWU_PATCH_LOCKED) return(IWU_PATCH_OK);
    if (ifp->flags & IWU_PATCH_LOADED) {
	pc = ifp->p;
	iw_midi_patch_removed(pc);
	lc = pc->layers;
	for (li=0; li < pc->nlayers; li++, lc=lc->id.p) {
	    wc = lc->waves;
	    for (wi=0; wi < lc->nwaves; wi++, wc=wc->id.p) {
		if (wc->m_start) {
		    iw_free(wc->m_start);
		    wc->m_start = 0;
		}
	    }
	}
	ifp->flags &= ~IWU_PATCH_LOADED;
    }
    return(IWU_PATCH_OK);
}

int iwu_load_patch(int prog, int bank, char RFAR *diskbuff, int bsize, void (*free_memory_cb)(struct iwu_fast_patch RFAR *fp, int attempt), int load_as_8)
{
    struct iwu_fast_patch RFAR *ifp;
    struct patch_lib RFAR *pl;

    ifp = iwu_find_patch(prog, bank, &pl);
    if (ifp == 0) return(IWU_PATCH_CANTFIND);
    if (ifp->flags & IWU_PATCH_LOADED) return(0);
    return(load_patch(ifp, pl, diskbuff, bsize, free_memory_cb, load_as_8));
}

#ifdef IWU_PATCH_EDIT

#define MAXENVPSIZE (sizeof(struct iw_envp) + 16 * (sizeof(struct iw_envp_record) + 16 * 2 * sizeof(unsigned short)))
struct list editable_patch_list = {0,0};

void unload_editable_patch(int prog, int bank)
{
    struct iwu_fast_patch RFAR *fpi;    /* search index */
    struct patch_lib RFAR *pl;
    struct node RFAR *n;

    fpi = find_patch(prog, bank, &pl);
    if (fpi == 0) return;
    if (fpi->flags & IWU_PATCH_EDITABLE) {
	fpi->flags &= ~IWU_PATCH_LOCKED;
	iwu_unload_patch(prog, bank);
	n = (struct node RFAR *)fpi - 1;
	DeleteNode(&editable_patch_list, n);
	free(n);
    }
}

/* _load_editable_patch finds the patch and makes a copy of the FFFF entry.
** The new FFFF entry is added to the special list of editable patches.
** The patch data is loaded.  iwu_find_patch() will return a pointer to the
** editable patch.  The flags field will have the IWU_PATCH_EDITABLE bit set.
** the patch->id.p pointer will point to the duplicate copy of the patch
** which can be modified and passed to iw_change_patch(patch, patch->id.p);
** The editable patch is locked in memory, and will survive calls to
** iwu_unload_patch(), and iwu_unload_all_patches().  Only
** iwu_unload_editable_patch() will remove editable patches.
*/
struct iwu_fast_patch RFAR *iwu_load_editable_patch(int prog, int bank, char RFAR *diskbuff, int bsize, void (*free_memory_cb)(struct iwu_fast_patch RFAR *fp, int attempt), int load_as_8)
{
    struct iwu_fast_patch RFAR *fpi;    /* search index */
    struct patch_lib RFAR *pl;
    struct node RFAR *n;
    int sizeof_patch;
    char RFAR *pbuf;
    struct iw_patch RFAR *pc, RFAR *dpc;/* copy pc to destination pc */
    struct iw_layer RFAR *lc, RFAR *dlc, RFAR *llc;
    struct iw_wave RFAR *wc, RFAR *dwc, RFAR *lwc;
    struct iw_data RFAR *dc, RFAR *ddc;
    struct iw_envp RFAR *ec, RFAR *dec;
    struct iw_envp_record RFAR *er, RFAR *der;
    struct header RFAR *ph;
    int wi, li, ei, rval;       /* wave, layer, and envelope counters */

    fpi = find_patch(prog, bank, &pl);
    if (fpi == 0) return(fpi);
    if (fpi->flags & IWU_PATCH_EDITABLE) return(fpi);
    /* found the patch, now find the size of the FFFF entry */
    pc = fpi->p;
    sizeof_patch = sizeof(struct iw_patch) + sizeof(struct iwu_fast_patch);
    lc = pc->layers;
    for (li=0; li < pc->nlayers; li++, lc=lc->id.p) {
	sizeof_patch += sizeof(struct iw_layer);
	sizeof_patch += MAXENVPSIZE; /* volume envelope */
	sizeof_patch += MAXENVPSIZE; /* pitch envelope */
	wc = lc->waves;
	for (wi=0; wi < lc->nwaves; wi++, wc=wc->id.p) {
	    sizeof_patch += sizeof(struct iw_wave);
	    sizeof_patch += sizeof(struct iw_data);
	    dc = (struct iw_data RFAR*)wc->data_id.p;
	}
    }
    /* now make two copies of the patch */
    pbuf = malloc(sizeof_patch + sizeof_patch + sizeof(struct iwu_fast_patch) + sizeof(struct node));
    n = (struct node RFAR *)pbuf;
    n->data = (void RFAR *)(n+1);
    fpi = (struct iwu_fast_patch RFAR *)n->data;
    fpi->id.h.program = prog;
    fpi->id.h.bank = bank;
    fpi->flags = IWU_PATCH_EDITABLE;
    fpi->p = (struct iw_patch RFAR *)(fpi+1);
    dpc = fpi->p;
    _fmemcpy(dpc, pc, sizeof(struct iw_patch));
    dpc->layers = (struct iw_layer RFAR *)(dpc+1);
    lc = pc->layers;
    llc = 0;
    dlc = dpc->layers;
    for (li=0; li < pc->nlayers; li++, lc=lc->id.p) {
	if (llc != 0) {
	    llc->id.p = dlc;
	}
	_fmemcpy(dlc, lc, sizeof(struct iw_layer));
	dlc->waves = (struct iw_wave RFAR *)(dlc+1);
	wc = lc->waves;
	lwc = 0;
	dwc = dlc->waves;
	for (wi=0; wi < lc->nwaves; wi++, wc=wc->id.p) {
	    if (lwc != 0) {
		lwc->id.p = dwc;
	    }
	    _fmemcpy(dwc, wc, sizeof(struct iw_wave));
	    dwc->m_start = 0;
	    dwc->m_format = wc->format;
	    lwc = dwc;
	    dwc->id.p = 0;
	    dwc = dwc+1;
	}
	dlc->id.p = 0;
	llc = dlc;
	if (wi == 0) {
	    dlc = dlc+1;
	} else {
	    dlc = (struct iw_layer RFAR *)dwc;
	}
    }
    if (li == 0) {
	dec = (struct iw_envp RFAR *)(dlc + 1);
    } else {
	dec = (struct iw_envp RFAR *)dlc;
    }
    /* copy envelope and data chunks to new FFFF entry */
    lc = pc->layers;
    dlc = dpc->layers;
    for (li=0; li < pc->nlayers; li++, lc=lc->id.p, dlc=dlc->id.p) {
	_fmemset(dec, 0, MAXENVPSIZE);
	if (lc->penv.p) {
	    dlc->penv.p = dec;
	    ec = (struct iw_envp RFAR *)lc->penv.p;
	    _fmemcpy(dec, ec, sizeof(struct iw_envp));
	    er = (struct iw_envp_record RFAR *)(ec+1);
	    der = (struct iw_envp_record RFAR *)(dec+1);
	    ph = ((struct header RFAR *)((char RFAR *)(er-1)+1)-1);
	    _fmemcpy(der, er, (unsigned short)ph->length);
	}
	dec = (struct iw_envp RFAR *)((char RFAR *)dec+MAXENVPSIZE);
	if (lc->venv.p) {
	    dlc->venv.p = dec;
	    ec = (struct iw_envp RFAR *)lc->venv.p;
	    _fmemcpy(dec, ec, sizeof(struct iw_envp));
	    er = (struct iw_envp_record RFAR *)(ec+1);
	    der = (struct iw_envp_record RFAR *)(dec+1);
	    ph = ((struct header RFAR *)((char RFAR *)(er-1)+1)-1);
	    _fmemcpy(der, er, (unsigned short)ph->length);
	}
	ddc = (struct iw_data RFAR *)((char RFAR *)dec+MAXENVPSIZE);
	wc = lc->waves;
	dwc = dlc->waves;
	for (wi=0; wi < lc->nwaves; wi++, wc=wc->id.p, dwc=dwc->id.p) {
	    dc = (struct iw_data RFAR *)wc->data_id.p;
	    dwc->data_id.p = ddc;
	    _fstrcpy(ddc->filename, dc->filename);
	    ddc++;
	}
	dec = (struct iw_envp RFAR *)ddc;
    }
    dpc->id.p = dec;
    _fmemcpy(dec, dpc, sizeof_patch);
    dpc = (struct iw_patch RFAR *)dec;
    dpc->layers = (struct iw_layer RFAR *)((char RFAR *)dpc->layers + sizeof_patch);
    dlc = dpc->layers;
    for (li=0; li < dpc->nlayers; li++, dlc=dlc->id.p) {
	if (dlc->id.p) {
	    dlc->id.p = (char RFAR *)dlc->id.p + sizeof_patch;
	}
	if (dlc->penv.p) {
	    dlc->penv.p = (char RFAR *)dlc->penv.p + sizeof_patch;
	}
	if (dlc->venv.p) {
	    dlc->venv.p = (char RFAR *)dlc->venv.p + sizeof_patch;
	}
	dlc->waves = (struct iw_wave RFAR *)((char RFAR *)dlc->waves + sizeof_patch);
	dwc = dlc->waves;
	for (wi=0; wi < lc->nwaves; wi++, wc=wc->id.p, dwc=dwc->id.p) {
	    if (dwc->id.p) {
		dwc->id.p = (char RFAR *)dwc->id.p + sizeof_patch;
	    }
	    if (dwc->data_id.p) {
		dwc->data_id.p = (char RFAR *)dwc->data_id.p + sizeof_patch;
	    }
	}
    }
    AddNode(&editable_patch_list, n, 0, APPEND);
    fpi->flags |= IWU_PATCH_LOCKED;
    rval = load_patch(fpi, pl, diskbuff, bsize, free_memory_cb, load_as_8);
    if (rval != IWU_PATCH_OK) {
	unload_editable_patch(prog, bank);
	return(0);
    }
    return(fpi);
}
#endif

void iwu_unload_all_patches(void)
{
    struct iwu_fast_patch RFAR *fpi;    /* search index */
    struct patch_lib RFAR*pl;
    struct node RFAR *fffn;
    struct fff_file RFAR *fff;
    int i;

    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++) {
		if (fpi->flags & IWU_PATCH_LOCKED) continue;
		if (fpi->flags & IWU_PATCH_LOADED) {
		    iwu_unload_patch(fpi->id.h.program, fpi->id.h.bank);
		}
	    }
	}
    }
}

static int find_index(union ID RFAR *id, struct idx RFAR *ids, int nids)
{
    int i;

    if (id->id.major_id == 0 && id->id.minor_id == 0) return(1);
    for (i=0; i < nids; i++) {
	if (_fmemcmp(&id->id, &ids[i].id, sizeof(union ID)) == 0) {
	    id->p = ids[i].p;
	    return(1);
	}
    }
    return(0);
}

#define MAX_BANKS 4

static unsigned long rom_pos;
static long rom_fixup[MAX_BANKS];
static int rom_open(char RFAR *name)
{
    struct rom_hdr rh;
    unsigned long pos;
    int bank, i;
    unsigned char csum, RFAR *cp;

#ifdef ROM_DEBUG
    char diskbuff[1024];
    int fp;
    unsigned bytes_in;
    unsigned long addr;

    if (_dos_open("gm1_1_0.rom", O_RDONLY, &fp) != 0) {
	printf("Can't open file to copy to DRAM\n");
	return(IWU_PATCH_CANTOPEN);
    }
    addr = 8UL * 1024UL * 1024UL;
    while (_dos_read(fp, diskbuff, 1024, &bytes_in) == 0 && bytes_in) {
	iw_poke_block(diskbuff,addr,bytes_in,0);
	addr += bytes_in;
    }
    _dos_close(fp);
#endif
    /* search all 4 banks looking for correct rom */
    for (rom_pos=0, bank=0; bank < MAX_BANKS; bank++, rom_pos += (4UL*1024UL*1024UL)) {
	iw_peek_rom((unsigned char RFAR *)&rh, rom_pos, sizeof(struct rom_hdr));
	if (_fstrnicmp(rh.iwave, "INTRWAVE", 8) != 0) continue;
	/* test rom header checksum */
	csum = 0;
	cp = (unsigned char RFAR *)&rh;
	for (i=0; i < 511; i++) csum += *cp++;
	csum = 256 - csum;
	if (csum != *cp) continue;
	/* correct rom? */
	if (_fstrnicmp(rh.series_name, name, 16) == 0 && rh.series_number == 0) break;
    }
    if (bank == MAX_BANKS) return(IWU_PATCH_CANTOPEN);
    rom_pos += sizeof(struct rom_hdr);
    /* go through all four banks and update address fixup table */
    _fmemset(rom_fixup, 0, sizeof(rom_fixup));
    for (pos=0, bank=0; bank < MAX_BANKS; bank++, pos += (4UL*1024UL*1024UL)) {
	iw_peek_rom((unsigned char RFAR *)&rh, pos, sizeof(struct rom_hdr));
	if (_fstrnicmp(rh.iwave, "INTRWAVE", 8) != 0) continue;
	/* test rom header checksum */
	csum = 0;
	cp = (unsigned char RFAR *)&rh;
	for (i=0; i < 511; i++) csum += *cp++;
	csum = 256 - csum;
	if (csum != *cp) continue;
	/* correct rom? */
	if (_fstrnicmp(rh.series_name, name, 16) == 0) {
	    rom_fixup[rh.series_number] = pos - ((long)rh.series_number * (4L*1024L*1024L));
	}
    }
    return(IWU_PATCH_OK);
}
static void rom_read(void RFAR *addr, unsigned short len)
{
    iw_peek_rom((unsigned char RFAR *)addr, rom_pos, len);
    rom_pos += len;
}
static void rom_seek(long pos, int whence)
{
    if (whence == SEEK_SET) {
	rom_pos &= ~((4UL*1024UL*1024UL) - 1);
	rom_pos |= whence;
    } else if (whence == SEEK_CUR) {
	rom_pos += pos;
    }
}

static int load_fff(struct patch_lib RFAR *pl)
{
    struct header file_h, pe_h, h, RFAR *ph;/* file headers */
    struct node RFAR *idxn, RFAR *nidxn;/* temporary index marks */
    struct iw_program RFAR*progc;       /* program chunk */
    struct iw_patch RFAR *pc, RFAR *lpc;/* patch chunk, and last patch chunk */
    struct iw_layer RFAR *lc, RFAR *llc;/* layer chunk and last layer chunk */
    struct iw_wave RFAR *wc, RFAR *lwc; /* wave chunk and last wave chunk */
    struct iw_name_chunk RFAR *nc;      /* name chunk and last name chunk */
    struct iwu_fast_patch fpi;          /* used for creating fast patch index */
    struct node RFAR *fffn;
    struct fff_file RFAR *fff;
    int wi, li;                         /* wave and layer counters */
#if defined(LUCID)
    unsigned short fp=-1;               /* ffff file handle */
    unsigned long amount_read;
#elif defined(_WINDOWS)
    HFILE fp=-1;                                /* ffff file handle */
    unsigned amount_read;
#else
    int fp=-1;                          /* ffff file handle */
    unsigned amount_read;
#endif
    int romio = 0;
    unsigned long id, id1;
    unsigned long pos,pos1,skip_len;    /* current position in ffff file */
    char RFAR *cp; 	              /* character pointer into ffff file */
    int i, j, k, return_val, idx;
    char text[80];

    InitList(&pl->fff_list);
    ids = 0;
    _fstrcpy(text, pl->patch_dir);
    _fstrcat(text, pl->patch_filename);
    if (_fstrnicmp(text, "rom", 3) == 0) {
	if (rom_open(&text[3]) != 0) {
	    return_val = IWU_PATCH_CANTOPEN;
	    goto error_rtn;
	}
	romio = 1;
    } else {
	romio = 0;
	if (_dos_open(text, O_RDONLY, &fp) != 0) {
	    return_val = IWU_PATCH_CANTOPEN;
	    goto error_rtn;
	}
    }
    while (1) { /* each file might contain many FFFF chunks */
	if (romio) {
	    rom_read(&file_h, sizeof(file_h));
	    if (_fstrncmp(file_h.tag, file_header.tag, sizeof(file_h.tag)) != 0) break;
	} else {
	    if (_dos_read(fp, &file_h, sizeof(file_h), &amount_read) != 0 ||
		amount_read != sizeof(file_h)) break;
	}
	if (_fstrncmp(file_h.tag, file_header.tag, sizeof(file_h.tag)) != 0) {
	    bad_patch:
	    return_val = IWU_PATCH_FILE_CORRUPT;
	    goto error_rtn;
	}
	/* skip to first patch, data, or envp chunk */
	skip_len = 0;
	while (skip_len < file_h.length) {
	    if (romio) {
		rom_read(&h, sizeof(h));
		amount_read = sizeof(h);
	    } else {
		if (_dos_read(fp, &h, sizeof(h), &amount_read) != 0) goto bad_patch;
	    }
	    if (amount_read != sizeof(h)) goto bad_patch;
	    if (_fstrncmp(h.tag, program_header.tag, sizeof(h.tag)) == 0) break;
	    if (_fstrncmp(h.tag, data_header.tag, sizeof(h.tag)) == 0) break;
	    if (_fstrncmp(h.tag, envp_header.tag, sizeof(h.tag)) == 0) break;
	    if (romio) {
		rom_seek(h.length,SEEK_CUR);
	    } else {
#ifdef LUCID
		if (_dx_seek(fp,h.length,SEEK_CUR,&amount_read)) goto bad_patch;
#else
		if (lseek(fp,h.length,SEEK_CUR)==-1L) goto bad_patch;
#endif
	    }
	    skip_len += h.length + sizeof(h);
	}
	if (skip_len == file_h.length) continue; /* FFF file contains no patches */
	fff = malloc(sizeof(struct fff_file));
	if (fff == 0) {
	    return_val = IWU_PATCH_NOMEM;
	    goto error_rtn;
	}
	fff->pbuf = 0L;
	fff->first_patch = 0;
	fff->npatches = 0;
	fff->plen = file_h.length - skip_len;
	if (fff->plen > 65535L) {
	    return_val = IWU_PATCH_FILE_TOO_LARGE;
	    goto error_rtn;
	}
	fff->pbuf = malloc((unsigned short)fff->plen);
	if (fff->pbuf == 0) {
	    return_val = IWU_PATCH_NOMEM;
	    goto error_rtn;
	}
	_fmemcpy(fff->pbuf, &h, sizeof(h));
	cp = fff->pbuf;
	if (romio) {
	    rom_read(cp+sizeof(h), (unsigned short)(fff->plen-sizeof(h)));
	} else {
	    if (_dos_read(fp, cp+sizeof(h), (unsigned short)(fff->plen-sizeof(h)), &amount_read) != 0 ||
		amount_read != fff->plen-sizeof(h)) {
		return_val = IWU_PATCH_FILE_CORRUPT;
		goto error_rtn;
	    }
	}
	/* create temporary ID array for converting ID's to pointers */
	/* count number of ID's in FFFF file */
	nids = 0;
	for (pos=0; pos < fff->plen; ) {
	    ph = (struct header RFAR*)cp;
	    pos += sizeof(*ph);
	    cp += sizeof(*ph);
	    if (_fstrncmp(ph->tag, program_header.tag, sizeof(program_header.tag)) != 0) {
		nids++;
	    }
	    cp += ph->length;
	    pos += ph->length;
	}
	/* allocate and fill temporary ID array */
	cp = fff->pbuf;
	if (nids) {
	    ids = malloc(sizeof(struct idx) * nids);
	    if (ids == 0) {
		return_val = IWU_PATCH_NOMEM;
		goto error_rtn;
	    }
	    idx = 0;
	    for (pos=0; pos < fff->plen; ) {
		ph = (struct header RFAR*)cp;
		pos += sizeof(*ph);
		cp += sizeof(*ph);
		if (_fstrncmp(ph->tag, program_header.tag, sizeof(program_header.tag)) != 0) {
		    _fmemcpy(&ids[idx].h, ph, sizeof(struct header));
		    ids[idx].p = cp;
		    _fmemcpy(&ids[idx].id, cp, sizeof(ids[idx].id));
		    idx++;
		}
		cp += ph->length;
		pos += ph->length;
	    }
	} else {
	    ids = 0;
	}
	/* convert ID's to c pointers */
	cp = fff->pbuf;
	lpc = 0;
	for (pos=0; pos < fff->plen; ) {
	    _fmemcpy(&h, cp, sizeof(h));
	    pos += sizeof(h);
	    cp += sizeof(h);
	    if (_fmemcmp(&h, "PROG", 4) == 0) {
		fff->npatches++;                /* count patches for fast index */
		progc = (struct iw_program RFAR*)cp;
		id = ((unsigned long)progc->version.id.major_id << 16) | progc->version.id.minor_id;
		id1 = ((unsigned long)IW_PATCH_VERSION_MAJOR<<16) | IW_PATCH_VERSION_MINOR;
		if (id < id1) {
		    return_val = IWU_PATCH_FILE_VERSION_MISMATCH;
		    goto error_rtn;
		}
		pos += sizeof(struct iw_program);
		cp += sizeof(struct iw_program);
		/* read patch header */
		_fmemcpy(&h, cp, sizeof(h));
		pos += sizeof(h);
		cp += sizeof(h);
		pc = (struct iw_patch RFAR*)cp;
		if (lpc == 0) {
		    fff->first_patch = pc;
		} else {
		    lpc->id.p = pc;
		}
		cp += h.length;
		pos += h.length;
		llc = 0;
		pc->layers = 0;
		for (li=0; li < pc->nlayers; li++) {
		    _fmemcpy(&h, cp, sizeof(h));
		    pos += sizeof(h);
		    cp += sizeof(h);
		    if (_fmemcmp(&h, "LAYR", 4) != 0) {
			bad_patch1:
			return_val = IWU_PATCH_FILE_CORRUPT;
			goto error_rtn;
		    }
		    lc = (struct iw_layer RFAR*)cp;
		    if (llc == 0) {
			pc->layers = lc;
		    } else {
			llc->id.p = lc;
		    }
		    if (find_index(&lc->penv, ids, nids) == 0) {
			return_val = IWU_PATCH_BAD_INDEX;
			goto error_rtn;
		    }
		    if (find_index(&lc->venv, ids, nids) == 0) {
			return_val = IWU_PATCH_BAD_INDEX;
			goto error_rtn;
		    }
		    cp += h.length;
		    pos += h.length;
		    lc->waves = 0L;
		    lwc = 0;
		    for (wi=0; wi < lc->nwaves; wi++) {
			_fmemcpy(&h, cp, sizeof(h));
			pos += sizeof(h);
			cp += sizeof(h);
			if (_fmemcmp(&h, "WAVE", 4) != 0) goto bad_patch1;
			wc = (struct iw_wave RFAR*)cp;
			if (lwc == 0) {
			    lc->waves = wc;
			} else {
			    lwc->id.p = wc;
			}
			if (romio) {
			    wc->m_start = wc->start + rom_fixup[wc->start>>22];
#ifdef ROM_DEBUG
			    wc->m_format = wc->format & ~IW_WAVE_FORMAT_ROM;
#else
			    wc->m_format = wc->format | IW_WAVE_FORMAT_ROM;
#endif
			} else {
			    if (find_index(&wc->data_id, ids, nids) == 0) {
				return_val = IWU_PATCH_BAD_INDEX;
				goto error_rtn;
			    }
			}
			lwc = wc;
			pos += h.length;
			cp += h.length;
		    }
		    if (lwc) lwc->id.p = 0;
		    llc = lc;
		}
		if (llc) llc->id.p = 0;
		lpc = pc;
	    } else {
		pos += h.length;
		cp += h.length;
	    }
	}
	if (lpc) lpc->id.p = 0;
	return_val = 0;
    error_rtn:
	/* free index list */
	if (ids) {
	    free(ids);
	    ids = 0;
	    nids = 0;
	}
	if (return_val == 0) {
	    /* create sorted index into patch list */
	    fff->patches = malloc(fff->npatches * sizeof(struct iwu_fast_patch));
	    if (fff->patches == 0) {
		return_val = IWU_PATCH_NOMEM;
	    } else {
		_fmemset(fff->patches, 0, sizeof(struct iwu_fast_patch) * fff->npatches);
		i=0; /* counter for patches inserted into list */
		/* generic insertion sort */
		for (pc=fff->first_patch; pc != 0; pc = pc->id.p) {
		    fpi.id.h.program = pc->program;
		    fpi.id.h.bank = pc->bank;
		    for (j=0; j <= i; j++) {
			if (fff->patches[j].p && fff->patches[j].id.bank_program < fpi.id.bank_program) continue;
			break;
		    }
		    for (k=i; k > j; k--) {
			if (k <= (fff->npatches-1)) fff->patches[k] = fff->patches[k-1];
		    }
		    fff->patches[j].id.bank_program = fpi.id.bank_program;
		    fff->patches[j].p = pc;
		    if (romio) fff->patches[j].flags |= IWU_PATCH_LOADED|IWU_PATCH_LOCKED;
		    i++;
		}
	    }
	}
	/* if error occurred free up memory before returning */
	if (return_val != 0) {
	    iwu_fff_unload(pl);
	    if (fp != -1) _dos_close(fp);
	    return(return_val);
	}
	fffn = malloc(sizeof(struct node));
	if (fffn == 0) return(IWU_PATCH_NOMEM);
	fffn->data = fff;
	AddNode(&pl->fff_list, fffn, NULL, PREPEND);
    }
    if (fp != -1) {
	_dos_close(fp);
	fp = -1;
    }
    return(0);
}

int iwu_fff_load(struct patch_lib RFAR *pl)
{
    int rval;

    if (pl == 0) {
	for (pl=plib; pl != 0; pl = pl->next) {
	    rval = load_fff(pl);
	    if (rval != 0) return(rval);
	}
    } else {
	return(load_fff(pl));
    }
    return(IWU_PATCH_OK);
}

void iwu_fff_unload(struct patch_lib RFAR *pl)
{
    struct node RFAR *fffn, *nfffn;
    struct fff_file RFAR *fff;

    if (pl) {
	for (fffn=pl->fff_list.head; fffn; fffn = nfffn) {
	    nfffn = fffn->next;
	    fff = fffn->data;
	    if (fff) {
		if (fff->pbuf) {
		    free((void *)fff->pbuf);
		    fff->pbuf = 0;
		}
		fff->first_patch = 0;
		if (fff->patches) {
		    free(fff->patches);
		    fff->patches = 0;
		}
		free(fff);
	    }
	    DeleteNode(&pl->fff_list, fffn);
	    free(fffn);
	}
    }
}

#define TBUFLEN 80
#define IWU_VENDOR_BUFLEN 80

int iwu_get_patch_config(void)
{
    char buf[TBUFLEN];
    char vendor_info[IWU_VENDOR_BUFLEN];
    char default_vendor[IWU_VENDOR_BUFLEN];
    char RFAR*iwstr, RFAR*cp;
    struct patch_lib RFAR*pl, RFAR*npl;
    int n;

    if (plib) {
	for (pl=plib; pl != NULL; pl = npl) {
	    npl = pl->next;
	    free(pl);
	}
	plib=NULL;
    }
    /* example: INTERWAVE=c:\iw.ini */
    if ((iwstr=getenv("INTERWAVE")) == 0L) {
	return(IWU_PATCH_NO_IW_ENVIRON);
    }
    /* example: [vendors] */
    /* example: default=forte */
    iwu_get_profile_string("vendors","default","",(char RFAR *)default_vendor,IWU_VENDOR_BUFLEN,iwstr);
    if (_fstrcmp(default_vendor, "") == 0) {
	return(IWU_PATCH_NO_DEFAULT_VENDOR);
    }
    /* example: [vendors] */
    /* example: forte=gm.fff */
    iwu_get_profile_string("vendors",default_vendor,"",buf,TBUFLEN,iwstr);
    if (_fstrcmp(buf, "") == 0) {
	return(IWU_PATCH_NO_VENDOR_PATCHES);
    }
    /* get list of patch libraries */
    cp = _fstrtok(buf, ",");
    while (cp) {
	while (*cp == ' ' || *cp == '\t') cp++;
	pl = (struct patch_lib RFAR*)malloc(sizeof(struct patch_lib));
	if (pl == NULL) {
	    return(IWU_PATCH_NOMEM);
	}
	_fstrncpy(pl->patch_filename, cp, IWU_PATCH_FILENAME_LEN);
	pl->next = plib;
	plib = pl;
	cp = _fstrtok(NULL, ",");
    }
    /* get directories for patch libraries */
    _fstrcpy(vendor_info, "vendor ");
    _fstrcat(vendor_info, default_vendor);
    for (pl=plib; pl != NULL; pl = pl->next) {
	/* example: [vendor forte] */
	/* example: gm.fff=c:\forte\patches */
	if (_fstrnicmp(pl->patch_filename, "rom", 3) != 0) {
	    iwu_get_profile_string(vendor_info,pl->patch_filename,"",pl->patch_dir,IWU_PATCH_DIR_LEN,iwstr);
	    if (_fstrcmp(pl->patch_dir, "") == 0) {
		return(IWU_PATCH_NO_DIR);
	    } else {
		n = _fstrlen(pl->patch_dir)-1;
		if (pl->patch_dir[n] != '\\') {
		    _fstrcat(pl->patch_dir, "\\");
		}
	    }
	} else {
	    pl->patch_dir[0] = 0;
	}
    }
    return(iwu_fff_load(0));
}

char *iwu_error_str(int error)
{
    switch (error) {
	case IWU_PATCH_OK:
	    return("patch file loaded successfully");
	case IWU_PATCH_NOMEM:
	    return("out of pc memory reading file");
	case IWU_PATCH_CANTOPEN:
	    return("Couldn't open patch file");
	case IWU_PATCH_FILE_CORRUPT:
	    return("Not an FFFF file or corrupt patch file");
	case IWU_PATCH_FILE_TOO_LARGE:
	    return("patch file too big");
	case IWU_PATCH_BAD_INDEX:
	    return("Bad index in patch file");
	case IWU_PATCH_NO_IW_ENVIRON:
	    return("can't find INTERWAVE environment variable");
	case IWU_PATCH_NO_DEFAULT_VENDOR:
	    return("Can't find default patch vendor");
	case IWU_PATCH_NO_VENDOR_PATCHES:
	    return("Can't find default vendor's patch files");
	case IWU_PATCH_NO_DIR:
	    return("can't find directory for patch file");
	case IWU_PATCH_FILE_VERSION_MISMATCH:
	    return("Patch files have different version than midi engine");
    }
    return("Unknown error");
}
