/*
 * convert.c - convert AmigaDOS "hunk"-format load file to
 *             memory image similar to a.out format
 *             Based on CVT Amiga->ST program
 *             which is Copyright (C) 1985 Landon M. Dyer.
 *
 *             Changes to CVT and everything new is Copyright 1986
 *             by Eric D. Black.  Permission is given to redistribute
 *             this program and its source code subject to the
 *             restrictions given in the file "unhunk.c"
 *
 */

#include <stdio.h>
#include "convert.h"
#include "hunk.h"
#include "bin.out.h"

#ifdef DBUG
#include <local/dbug.h>
#else
#include "dbugstubs.h"
#endif


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 */
long maxhunk;			/* size of biggest hunk found */
char *hunkbuf;			/* ptr to buffer for doing hunk relocation */
int curtype;			/* current hunk type: 0=Code, 1=Data, 2=BSS */
char *secname[] = {
	"CODE", "DATA", "BSS "
	};

/*
 * Macro to bump address up to next longword boundary
 */
#define ROUNDLONG(a) ((a + 3) & 0xfffffffc);


/*
 * Convert AMIG* binary loadfile to memory image file similar to a.out
 */
convert(ifd, ofd)
int ifd, ofd;
{
	DBUG_ENTER("convert");

    /* init globals */
    hunk = (Hinfo **)NULL;
    firsthunk = (Hinfo *)NULL;
    hvalid = nhunks = 0;
	curhunk = -1;
    filpos = 0L;
	maxhunk = 0L;

    pass1(ifd);			/* gather info on hunks, see what we have to do */
    if (curhunk < nhunks)
        panic("Bugcheck: not enough hunks in input body.");

    bind();				/* decide where each hunk will go */
    output(ifd, ofd);	/* spit out hunks in desired order */
    release();			/* free memory */

	DBUG_RETURN(0);
}


/*
 * Parse hunks in AMIG* binary loadfile,
 * gather information about it.
 *
 * Add error checks, handle symbol records   19Apr86 edb
 * Handle extra HUNK_ENDs which alink sometimes produces  20Apr86 edb
 */
pass1(ifd)
int ifd;
{
    long lw;
    Hinfo *h;

	DBUG_ENTER("pass1");

    for(;;)
    {
        if (readlong(ifd, &lw) == EOF) {
			DBUG_3("hunk", "Got EOF", 0);
            break;
		}

        switch ((int)lw)
        {
            case HUNK_UNIT:
            case HUNK_NAME:
                DBUG_3("hunk","%s",(lw==HUNK_UNIT)?"HUNK_UNIT":"HUNK_NAME");
                getlong(ifd, &lw);
                skip(ifd, lw);
                break;

            case HUNK_CODE:
				DBUG_2("hunk", "HUNK_CODE");
                chkhunk();
				curtype = TEXT;
                h = hunk[curhunk];
                getlong(ifd, &lw);              /* get size (in longs) */
				if (lw != h->hsize) {
					fprintf(stderr,
					    "Code hunk %d size %ld doesn't match header (%ld)\n",
						h->hunkno, lw, h->hsize);
					errflg++;
				}
                lw *= 4;
				if (maxhunk < lw) {
					maxhunk = lw;
					DBUG_3("hunk", "maxhunk=%ld", maxhunk);
				}
                h->hsize = lw;
                h->hpos = filpos;
                h->htype = TEXT;
                skip(ifd, lw);
				if (printing)
					printf("Hunk %d, type %s, size %ld\n",
						h->hunkno, secname[h->htype], h->hsize);
                DBUG_EXECUTE("dump", dumphinfo(h); );
                break;

            case HUNK_DATA:
				DBUG_2("hunk", "HUNK_DATA");
                chkhunk();
				curtype = DATA;
                h = hunk[curhunk];
                getlong(ifd, &lw);              /* get size (in longs) */
				if (lw != h->hsize) {
					fprintf(stderr,
					    "Data hunk %d size %ld doesn't match header (%ld)\n",
						h->hunkno, lw, h->hsize);
					errflg++;
				}
                lw *= 4;
				if (maxhunk < lw) {
					maxhunk = lw;
					DBUG_3("hunk", "maxhunk=%ld", maxhunk);
				}
                h->hsize = lw;
                h->hpos = filpos;
                h->htype = DATA;
                skip(ifd, lw);
				if (printing)
					printf("Hunk %d, type %s, size %ld\n",
						h->hunkno, secname[h->htype], h->hsize);
                DBUG_EXECUTE("dump", dumphinfo(h); );
                break;

            case HUNK_BSS:
				DBUG_2("hunk", "HUNK_BSS");
                chkhunk();
				curtype = BSS;
                h = hunk[curhunk];
                getlong(ifd, &lw);              /* get size (in longs) */
				if (lw != h->hsize) {
					fprintf(stderr,
					    "BSS hunk %d size %ld doesn't match header (%ld)\n",
						h->hunkno, lw, h->hsize);
					errflg++;
				}
                h->hsize = lw * 4;
                h->htype = BSS;
				if (printing)
					printf("Hunk %d, type %s, size %ld\n",
						h->hunkno, secname[h->htype], h->hsize);
                DBUG_EXECUTE("dump", dumphinfo(h); );
                break;

            case HUNK_RELOC32:
				DBUG_2("hunk", "HUNK_RELOC32");
                h = hunk[curhunk];
				if (!h->hrel) {
					h->hrel = filpos;
					DBUG_4("hunk", "hunk %d relinfo at %ld",curhunk,filpos);
				}
				for (;;) {				/* skip past reloc records */
					getlong(ifd, &lw);	/* # of offsets */
					DBUG_3("hunk", "%ld offsets", lw);
					if (!lw)
						break;			/* done */
					skip(ifd, (lw+1) * 4);
				}
                break;

            case HUNK_RELOC16:
				DBUG_2("hunk", "HUNK_RELOC16");
                panic("16-bit relocation not supported.");
                break;

            case HUNK_RELOC8:
				DBUG_2("hunk", "HUNK_RELOC8");
                panic("8-bit relocation not supported.");
                break;

            case HUNK_EXT:
				DBUG_2("hunk", "HUNK_EXT");
                panic("External symbols not supported.");
                break;

            case HUNK_SYMBOL:		/* ignore symbol records */
				DBUG_2("hunk", "HUNK_SYMBOL");
				do {
					getlong(ifd, &lw);
					DBUG_3("hunk", "typ/namlen=0x%lx", lw);
					if (lw)
						skip(ifd, ((lw & 0xffffffL) + 1) * 4);
				} while (lw);
                break;

            case HUNK_DEBUG:		/* ignore debug records */
				DBUG_2("hunk", "HUNK_DEBUG");
                getlong(ifd, &lw);      /* get size (in longs) */
                lw *= 4;
                skip(ifd, lw);
                break;

            case HUNK_END:
				DBUG_2("hunk", "HUNK_END");
                if (hvalid) {
                    hvalid = 0;
                    DBUG_3("dump", "end of hunk %d\n", curhunk);
				} else
					printf("Extra HUNK_END for %s hunk %d, ignored\n",
						(curtype>=0 && curtype<=2) ? secname[curtype]:"???",
						curhunk);
                break;

            case HUNK_HEADER:
				DBUG_2("hunk", "HUNK_HEADER");
                header(ifd);
                break;

            case HUNK_OVERLAY:
				DBUG_2("hunk", "HUNK_OVERLAY");
                panic("Overlays not supported.");
                break;

            case HUNK_BREAK:
				DBUG_2("hunk", "HUNK_BREAK");
                panic("Breaks (overlays) not supported.");
                break;

			default:
				DBUG_3("hunk", "Unknown hunk type: 0x%lx", lw);
				panic("Out of phase, lost in space...");
        }
    }
	curhunk++;			/* boundary adjustment */
	DBUG_4("hunk","%d hunks expected, %d found", nhunks, curhunk);
	DBUG_RETURN(0);
}


/*
 * Sanity check about hunk adjacency (which may not be a
 * problem) and hunk number range.
 *
 * Remove panic for (presumably) missing HUNK_END   23Apr86  edb
 */
chkhunk()
{
	DBUG_ENTER("chkhunk");
#if 0
    if (hvalid)
        panic("Bugcheck: two adjacent hunks w/o HUNK_END!");
#endif
	curhunk++;		/* bump hunk# here in case of extra HUNK_ENDs */
    if (curhunk >= nhunks)
        panic("Bugcheck: too many hunks!");
    hvalid = 1;
	DBUG_RETURN(0);
}


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

	DBUG_ENTER("header");

    /*
     * Skip library names.
     */
    for (;;)
    {
        getlong(fd, &lw);
		DBUG_3("hunk", "namlen=%d", lw);
        if (!lw) break;
        skip(fd, lw*4);
    }

    /* more random header info */
    getlong(fd, &htabsize);
    getlong(fd, &firsthunk);
    getlong(fd, &lasthunk);
    DBUG_5("hunk", "htabsize = %ld\nfirsthunk = %ld\nlasthunk = %ld\n", htabsize, firsthunk, lasthunk);

    /* alloc space for hunk database */
    nhunks = (unsigned)(lasthunk - firsthunk + 1);
	j = nhunks * sizeof(*hunk);
	DBUG_4("mem", "grabbing %ld bytes for %d hunks", j, nhunks);
    hunk = (Hinfo **)malloc(j);
    if (hunk == NULL)
        allerr();

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

	DBUG_RETURN(0);
}


/*
 * Compute hunk order and starting addresses.
 * 
 * Changed for unhunk  19Apr86  edb
 * We have three bases to work with: text, data, bss;
 * each may be specified individually, otherwise text is assumed to
 * start at zero, data immediately follows text (padded to longword),
 * and bss immediately follows data (padded to longword).
 */
bind()
{
    int typ, hnk;
    long addr;
    Hinfo *hptr;

	DBUG_ENTER("bind");

    hptr = firsthunk;
	if (printing)
		printf("Assigning hunk load addresses:\n");
    for (typ = TEXT; typ <= BSS; ++typ) {	/* text, then data, then bss */
    	addr = origin[typ];
        siz[typ] = 0L;
        for (hnk = 0; hnk < nhunks; ++hnk)
            if (hunk[hnk]->htype == typ) {
                if (firsthunk == (Hinfo *)NULL)
                    hptr = firsthunk = hunk[hnk];
                    else {			/* list of each type in order found */
                        hptr->hnext = hunk[hnk];
                        hptr = hunk[hnk];
                    }
                siz[typ] += hptr->hsize;
                hptr->haddr = addr;
                addr += hptr->hsize;
				if(printing)
					printf("Hunk %d (%s) load address 0x%lx\n",
						hnk, secname[hptr->htype], hptr->haddr);
                DBUG_5("dump", "%s hunk[%d]->haddr = $%lx\n",secname[typ],hnk,hptr->haddr);
            }
		/*
		 * if not otherwise specified, round data and bss segments
		 * each up to longword boundary
		 */
		if (!orgspec[DATA]) {
			/*
			 * charge pad bytes to text segment
			 */
			siz[TEXT] = ROUNDLONG(siz[TEXT]);
			origin[DATA] = origin[TEXT] + siz[TEXT];
		}
		if (!orgspec[BSS]) {
			siz[DATA] = ROUNDLONG(siz[DATA]);
			origin[BSS] = origin[DATA] + siz[DATA];
		}

    }

	printf("Section    Origin      Size(bytes)\n");
	for(typ=TEXT; typ<=BSS; typ++)
		printf(" %s      0x%-6lx      %ld (0x%lx)\n",
			secname[typ], origin[typ], siz[typ], siz[typ]);

	DBUG_RETURN(0);
}



/*
 * Allocate (and initialize) a Hinfo node.
 *
 * use hrel as filepos to re-read reloc info  20Apr86 edb
 */
Hinfo *ahinfo(hnum)
int hnum;
{
    Hinfo *h;

	DBUG_ENTER("ahinfo");

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

	DBUG_4("mem","malloc'ing %d bytes for hunk %d",sizeof(Hinfo),hnum);
    if ((h = (Hinfo *)malloc(sizeof(Hinfo))) == NULL)
        allerr();
    h->hsize = 0L;
    h->hpos = 0L;
    h->haddr = 0L;
    h->hrsize = 0L;
    h->htype = -1;
    h->hrel = 0L;
    h->hnext = (Hinfo *)NULL;

    DBUG_RETURN(h);
}


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

	DBUG_ENTER("release");

    for (i = 0; i < nhunks; ++i)
    {
		DBUG_3("mem", "freeing node at 0x%x", hunk[i]);
        free(hunk[i]);
    }
	DBUG_2("mem", "freeing hunk");
    free(hunk);
	DBUG_RETURN(0);
}


/*
 * Skip some of the input file
 */
skip(fd, count)
int fd;
long count;
{
	DBUG_ENTER("skip");
	DBUG_3("hunk", "skipping %ld bytes", count);
    filpos += count;
    lseek(fd, count, 1);
	DBUG_RETURN(0);
}


/*
 * Out of memory (malloc() didn't work);
 * complain and die.
 */
allerr()
{
	DBUG_ENTER("allerr");
    panic("Allocation failure, heap exhausted, I give up!\n");
	/*NOTREACHED*/
	DBUG_RETURN(0);
}


/*
 * Print information about an Hinfo node.
 */
dumphinfo(h)
Hinfo *h;
{
	DBUG_ENTER("dumphinfo");
    printf("hsize = %ld ($%lx), hpos = %ld, haddr = %ld ($%lx)\n",
                h->hsize, h->hsize, h->hpos, h->haddr, h->haddr);
    printf("htype = %s ", secname[h->htype]);
    printf("hrel = %ld, hnext = $%lx\n",
                h->hrel, (long)h->hnext);
	DBUG_RETURN(0);
}

/* cvt2long() moved to longio.c to help localize machine dependencies  edb*/
