/* convert.c */
#include <stdio.h>
#include "convert.h"

extern int debug;	/* 1: print debugging messages */
extern int printing;	/* 1: print conversion statistics */

Hinfo *ahinfo();


/* hunk information */
Hinfo **hunk;		/* -> vector of ptrs to Hinfo structs */
Hinfo *firsthunk;	/* -> first hunk */
int nhunks;		/* #hunks in input file */
int curhunk;		/* current hunk# */
int hvalid;		/* state variable (to bump curhunk) */
long filpos;		/* position in input file */
long siz[3];		/* segment sizes */

/* relocation (fixup) information */
long rsize;		/* total size of all relocation information */
long *rbuf;		/* -> buffer for relocation information */
long *rpos;		/* current position in relocation info buffer */

char buf[BUFFERSIZE];	/* buffer for copying stuff */


/*
 * Convert AMIG* binary loadfile to GEMDOS .PRG executable.
 */
convert(ifd, ofd)
int ifd, ofd;
{
    /* init globals */
    hunk = (Hinfo **)NULL;
    firsthunk = (Hinfo *)NULL;
    hvalid = nhunks = curhunk = 0;
    filpos = 0L;
    rsize = 0L;

    pass1(ifd);
    if (curhunk < nhunks)
	panic("Bugcheck: not enough hunks in input body.");
    bind();

    if ((rbuf = (long *)malloc((int)rsize)) == NULL)
	allerr((int)rsize);
    rpos = rbuf;

    output(ifd, ofd);
    reloc(ifd, ofd);
    release();
}


/*
 * Parse hunks in AMIG* binary loadfile,
 * gather information about it.
 */
pass1(ifd)
int ifd;
{
    long lw, junk;
    Hinfo *h;

DEBUG("~ pass1()\n");

    for(;;)
    {
	if (readlong(ifd, &lw) == EOF)
	    break;

	switch ((int)lw)
	{
	    case HUNKUNIT:
	    case HUNKNAME:
		DEBUG("~\tHUNKUNIT or HUNKNAME\n");
		getlong(ifd, &lw);
		skip(ifd, lw * 4);
		break;

	    case HUNKCODE:
		chkhunk();
		h = hunk[curhunk];
		getlong(ifd, &lw);		/* get size (in longs) */
		lw *= 4;
		h->hsize = lw;
		h->hpos = filpos;
		h->htype = TEXT;
		skip(ifd, lw);
		if (debug) dumphinfo(h);
		break;

	    case HUNKDATA:
		chkhunk();
		h = hunk[curhunk];
		getlong(ifd, &lw);		/* get size (in longs) */
		lw *= 4;
		h->hsize = lw;
		h->hpos = filpos;
		h->htype = DATA;
		skip(ifd, lw);
		if (debug) dumphinfo(h);
		break;

	    case HUNKBSS:
		chkhunk();
		h = hunk[curhunk];
		getlong(ifd, &lw);		/* get size (in longs) */
		h->hsize = lw * 4;
		h->htype = BSS;
		if (debug) dumphinfo(h);
		break;

	    case HUNKR32:
		h = hunk[curhunk];
		for (;;)
		{
		    getlong(ifd, &lw);
		    if (!lw) break;
		    lw = (lw + 1) * 4;
		    h->hrsize += lw + 4;
		    skip(ifd, lw);
		}
		h->hrsize += 4;
		rsize += h->hrsize + 4;
		if(debug)printf("~\thrsize = %ld\n", h->hrsize);
		break;

	    case HUNKR16:
		panic("16-bit relocation not supported.");
		break;

	    case HUNKR8:
		panic("8-bit relocation not supported.");
		break;

	    case HUNKEXT:
		panic("External symbols not supported.");
		break;

	    case HUNKSYMBOL:
		panic("Symbols not supported.");
		break;

	    case HUNKDEBUG:
		panic("Debug blocks not supported.");
		break;

	    case HUNKEND:
		if (hvalid)
		{
		    hvalid = 0;
		    ++curhunk;
		}
		if(debug)printf("~HUNKEND, curhunk = %d\n", curhunk);
		break;

	    case HUNKHEADER:
		header(ifd);
		break;

	    case HUNKOVERLAY:
		panic("Overlays not supported.");
		break;

	    case HUNKBREAK:
		panic("Breaks (overlays) not supported.");
		break;
	}
    }
}


/*
 * Sanity check about hunk adjacency (which may not be a
 * problem) and hunk number range.
 */
chkhunk()
{
    if (hvalid)
	panic("Bugcheck: two adjacent hunks w/o HUNKEND!");
    if (curhunk >= nhunks)
	panic("Bugcheck: too many hunks!");
    hvalid = 1;
}


/*
 * Read hunk header,
 * get info for global vars.
 *
 */
header(fd)
int fd;
{
    long lw, *p;
    long htabsize, firsthunk, lasthunk;
    unsigned int i, j;

    /*
     * Skip library names.
     */
    for (;;)
    {
	getlong(fd, &lw);
	if (!lw) break;
	skip(fd, lw*4);
    }

    /* more random header info */
    getlong(fd, &htabsize);
    getlong(fd, &firsthunk);
    getlong(fd, &lasthunk);
    if (debug)
	printf("~\thtabsize = %ld\n\tfirsthunk = %ld\n\tlasthunk = %ld\n",
		htabsize, firsthunk, lasthunk);

    /* alloc space for hunk database */
    nhunks = (unsigned)(lasthunk - firsthunk + 1);
    hunk = (Hinfo **)malloc(nhunks * sizeof(Hinfo));
    if (hunk == NULL)
	allerr(nhunks * sizeof(Hinfo));

    for (j = 0; j < nhunks; ++j)
    {
	hunk[j] = ahinfo();
	hunk[j]->hunkno = j;
	getlong(fd, &hunk[j]->hsize);
	if(debug) printf("~\t%d hsize = %ld ($%lx)\n",
		j, hunk[j]->hsize, hunk[j]->hsize);
    }
}


/*
 * Compute hunk order and starting addresses.
 */
bind()
{
    int typ, hnk;
    long addr;
    Hinfo *hptr;

DEBUG("~ bind()\n");
    addr = 0L;
    hptr = firsthunk;
    for (typ = 0; typ < 3; ++typ)
    {
	siz[typ] = 0L;
	for (hnk = 0; hnk < nhunks; ++hnk)
	    if (hunk[hnk]->htype == typ)
	    {
		if (firsthunk == (Hinfo *)NULL)
		    hptr = firsthunk = hunk[hnk];
		    else {
			hptr->hnext = hunk[hnk];
			hptr = hunk[hnk];
		    }
		siz[typ] += hptr->hsize;
		hptr->haddr = addr;
		addr += hptr->hsize;
		if(debug)
		    printf("~\thunk[%d]->haddr = $%lx\n", hnk, hptr->haddr);
	    }
    }

    if(printing)
	printf("text %ld ($%lx), data %ld ($%lx), bss %ld ($%lx)\n",
		siz[0], siz[0], siz[1], siz[1], siz[2], siz[2]);
}


/*
 * Generate the GEMDOS output file.
 */
output(ifd, ofd)
int ifd, ofd;
{
    int i;
    Hinfo *hu;

DEBUG("~ output()\n");
    /*
     * Write .PRG header.
     */
    writeword(ofd, 0x601a);
    for (i = 0; i < 3; i++)
	writelong(ofd, siz[i]);
    for (i = 0; i < 7; i++)
	writeword(ofd, 0x0000);

    for (hu = firsthunk; hu != NULL; hu = hu->hnext)
	if (hu->htype == TEXT || hu->htype == DATA)
	{
	    lseek(ifd, hu->hpos, 0);
	    randw(ifd, hu->hsize, ofd);

	    if (hu->hrsize)
	    {
		lseek(ifd, 4L, 1);
		*rpos++ = hu->hunkno;
		if (read(ifd, rpos, (int)hu->hrsize) != (int)hu->hrsize)
		    panic("Reloc info read error.");
if(debug)printf("~ relocsiz = %ld\n", hu->hrsize);
		rpos += hu->hrsize >> 2;
	    }
	}
}


/*
 * Generate relocation information
 * (at the end of the output file).
 */
reloc(ifd, ofd)
int ifd, ofd;
{
    int cmplong();
    long cvt2long();
    long *rp, *drp;
    char *crp, c;
    int i, hno, nel;
    long count, base, lw, addr;
DEBUG("~ reloc()\n");

if (debug)
  {
    i = 0;
    for (crp = (char *)rbuf; crp < (char *)rpos; crp += 4)
    {
	if (!i) printf("~ ");
	printf("%02x%02x%02x%02x",
	    crp[0]&0xff, crp[1]&0xff, crp[2]&0xff, crp[3]&0xff);
	if (++i >= 8)
	{
	    printf("\n");
	    i = 0;
	} else putchar(' ');
    }
    putchar('\n');
  }

    /*
     * Go back into the .PRG file and add
     * hunk starting-offsets to the longwords
     * that must be fixed up.
     * 28L is the size of the GEMDOS .PRG header.
     */
    for (rp = rbuf; rp < rpos;)
    {
	hno = *rp++;
if(debug)printf("~	hno = %d\n", hno);
	for (;;)
        {
	    *rp = cvt2long(*rp);
	    if (!(count = *rp++)) break;
	    *rp = cvt2long(*rp);
	    base = hunk[(int)*rp++]->haddr;
if(debug)printf("~	    count = %ld, base = $%0lx\n", count, base);
	    while (count--)
	    {
		*rp = cvt2long(*rp);
if(debug)printf("~	    seek(ifd) = %ld, seek(ofd) = %ld ",
			hunk[hno]->hpos + *rp,
			hunk[hno]->haddr + *rp + 28L);
		lseek(ifd, hunk[hno]->hpos + *rp, 0);
		lseek(ofd, hunk[hno]->haddr + *rp++ + 28L, 0);
		readlong(ifd, &lw);
		lw += base;
		writelong(ofd, lw);
if(debug)printf("lw = $%0lx\n", lw);
	    }
	}
    }


    /*
     * Compute and sort fixup offsets.
     */
    nel = 0;
    drp = rbuf;
    for (rp = rbuf; rp < rpos;)
    {
	hno = *rp++;
	for (;;)
	{
	    if (!(count = *rp++)) break;
	    ++rp;
	    while (count--)
	    {
		*drp++ = hunk[hno]->haddr + *rp++;
		++nel;
	    }
	}
    }
    rpos = drp;
    qsort(rbuf, nel, 4, cmplong);

/* print sorted info */
if(debug)
  {
    i = 0;
    for (rp = rbuf; rp < rpos; ++rp)
    {
	if (!i) printf("~ ");
	printf("%08lx", *rp);
	if (++i >= 8)
	{
	    printf("\n");
	    i = 0;
	} else putchar(' ');
    }
    putchar('\n');
  }


    /*
     * Write GEMDOS relocation information
     */
    lseek(ofd, 0L, 2);				/* to end of file */
    rp = rbuf;
    while (rp < rpos)
    {
	/* write offset to first fixup */
	if (rp == rbuf)
	{
	    writelong(ofd, *rp);
	    addr = *rp++;
if(debug)printf("$%lx\n", addr);
	    continue;
	}

	if (addr >= *rp) panic("Bad relocation information.");
	c = 254;
	lw = *rp - addr;
	addr = *rp++;
if(debug)printf("$%lx:", lw);
	while (lw > 254)
	{
	    write(ofd, &c, 1);
	    lw -= 254L;
if(debug)putchar('+');
	}
        c = (char)lw;
	write(ofd, &c, 1);
if(debug)printf("$%x\n", (int)lw);
    }

    /*
     * Terminate relocation list
     */
    c = 0;
    write(ofd, &c, 1);
}


/*
 * Compare two longs
 * for qsort().
 */
cmplong(l1, l2)
long *l1, *l2;
{
    return *l1 - *l2;
}


/*
 * Allocate (and initialize) a Hinfo node.
 */
Hinfo *ahinfo()
{
    Hinfo *h;

    if (curhunk >= nhunks)
	panic("curhunk >= nhunks, too many hunks!");

    if ((h = (Hinfo *)malloc(sizeof(Hinfo))) == NULL)
	allerr(sizeof(Hinfo));
    h->hsize = 0L;
    h->hpos = 0L;
    h->haddr = 0L;
    h->hrsize = 0L;
    h->htype = -1;
    h->hrel = (long *)NULL;
    h->hnext = (Hinfo *)NULL;

    return h;
}


/*
 * Release memory used by hunk database.
 */
release()
{
    int i;

    for (i = 0; i < nhunks; ++i)
    {
	if (hunk[i]->hrel != NULL)
	    free(hunk[i]->hrel);
	free(hunk[i]);
    }
    free(hunk);
}


/*
 * Skip some of the input file
 */
skip(fd, count)
int fd;
long count;
{
    filpos += count;
    lseek(fd, count, 1);
}


/*
 * Out of memory (malloc() didn't work);
 * complain and die.
 */
allerr(size)
int size;
{
    fprintf (stderr, "malloc(%d) failed..\n", size);
    panic("Allocation failure, heap exhausted, I give up!\n");
}


/*
 * Print information about an Hinfo node.
 */
dumphinfo(h)
Hinfo *h;
{
    printf("~\thsize = %ld ($%lx), hpos = %ld, haddr = %ld ($%lx)\n",
		h->hsize, h->hsize, h->hpos, h->haddr, h->haddr);
    printf("~\thtype = %d ", h->htype);
    printf("hrel = $%lx, hnext = $%lx\n",
		(long)h->hrel, (long)h->hnext);
}


/*
 * Convert 68000 long to local long
 */
long cvt2long(lw)
long lw;
{
#ifdef AMIGA
    return (lw);
#else
    char *out, *in;
    long olw;

    /*
     * 8086/VAX conversion
     */
    in = (char *)&lw;
    out = (char *)&olw;
    out[0] = in[3];
    out[1] = in[2];
    out[2] = in[1];
    out[3] = in[0];
    return olw;
#endif	/* AMIGA */
}
