/******************************************************************************
*******************************************************************************
**
** Unidot object format to x.out convertor.
**
** Usage is:
**
**	xcon [-o outfile] [-s] file.obj
**
** The input file (file.obj) must be in the standard Unidot object file format,
** and its name must end with ".obj".  The input file is converted into the
** x.out format, and the result is put in the output file specified by the
** "-o" switch.  If no "-o" switch is given, the file "x.out" is created as
** a default.
**
** The "-s" switch will force the output to be marked as segmented, for
** those rare cases where xcon cannot determine that from the input file.
**
** Care has been taken to ensure machine independence.  The output file
** generated by this program will be correct for a Z8000 regardless of
** the processor on which this program is run.  In particular, this program
** may be run on a PDP-11 or on a VAX without any byte swap problems in the
** output file.  One consequence of this policy is that routines are used
** to output the various structures defined in "xout.h".  If the x.out
** file format is changed, or if "xout.h" is changed, the corresponding
** routines will probably need changing too.  The routines to check are
** outhdr(), outrel(), outseg(), outsym(), and outtxt().
**
** John D. Polstra
** September 13, 1982
**
*******************************************************************************
******************************************************************************/


#include "stdio.h"
#include "xout.h"
#include "obj.h"

/*
 * Constants.
 */
#define	EXTMAX	(RBMSK+1-RBEXT)	/* Maximum number of external symbols allowed */
#define	SEGMAX	128		/* Maximum number of segments allowed */

/*
 * Type definitions.
 */
typedef unsigned short uns;

/*
 * Global variables.
 */
uns exttab[EXTMAX];		/* Mapping from external # to symbol # */
uns *exttop = exttab;		/* Pointer to top of exttab */

struct x_sg segtab[SEGMAX];	/* Table of information about segments */

struct x_hdr header;		/* Header for x.out file */

long curoff;			/* Current x.out file offset */
long reloff;			/* X.out file offset for next reloc. entry */
long segoff[SEGMAX];		/* X.out file offsets for segments */
long symoff;			/* X.out file offset for next symbol */

uns symct;			/* Number of symbols for the x.out file */

char dbgflg = 0;		/* Debug output flag on =1 off =0 */
char objbuf[255];		/* Buffer holding body of current .obj block */
FILE *objiop;			/* Standard I/O pointer for .obj file */
char *objname;			/* Name of .obj file */
char *objpt, *objtop;		/* Pointers into objbuf */
int objtyp;			/* Type of current .obj block */

int outfd;			/* File descriptor for x.out file */
char *outname = "x.out";	/* Name of x.out file */

int firstseg;			/* First segment (0 or 1) actually output */
char pass2;			/* Flag set for second pass over .obj file */

char *prname;			/* Name of this program */

/*
 * Here is a brief description of our section / segment numbering
 * scheme.  The tables segtab[] and segoff[] are indexed by the Unidot
 * section number; thus they always have a slot for the absolute section
 * (number 0) whether it is used or not.  However, we do not want to
 * output a segment descriptor for the absolute section unless it is
 * really used.  So in the interlude we set the variable firstseg to
 * one iff the absolute section is used.  Within the various x.out
 * outputting routines (outrel, outsym, etc.) we adjust the section numbers
 * according to the value of firstseg.  The general philosophy is that
 * the outputting routines are called with Unidot section numbers; they
 * perform the necessary adjustments to produce the required x.out
 * segment numbers in the output file.
 *
 * Got it?
 */

/*
 * Function declarations.
 */
long		lseek();	/* Move I/O pointer to new file position */
uns		ogetb();	/* Get next byte from object block */
long		ogetl();	/* Get next long from object block */
char		*ogets();	/* Get next string from object block */
uns		ogetw();	/* Get next word from object block */

/*
 * main - The main program consists simply of some initialization followed
 * by two processing passes, with some interlude processing in between
 * the two passes.
 */
main(argc, argv)

int argc;
char *argv[];

{
    init(argc, argv);
    dopass();
    interlude();
    pass2 = 1;
    rewind(objiop);
    dopass();
    close(outfd);
    fclose(objiop);
    return(0);
}

/*
 * dopass - Performs a scan of the .obj file.
 */
dopass() {

    uns action;
    uns adr;
    uns base;
    uns len;
    uns relent;
    uns relpos;
    uns sec;
    char *sym;
    int type;
    uns val;

    header.x_nseg = 1;	/* We always leave room for an absolute segment */
    if(ofill() != OBOST)
	ferr("Bad .obj file format\n");
    while(ofill() != OBOND) {
	switch(objtyp) {

	case OBSEC:	/* Sections block */
	    while(objpt < objtop) {
		ogetb();	/* Discard alignment */
		ogetb();	/* Discard extent */
		ogetb();	/* Discard attributes */
		sym = ogets();	/* Get section name */
		if(pass2 && dbgflg) printf("sections %s\n",sym);
		if(!pass2) {
		    /*
		     * Enter a new segment into segtab.
		     * Increment the number of segments
		     * in the header.  Also increase
		     * the symbol table size to account
		     * for the segment name symbol.
		     */
		    segtab[header.x_nseg].x_sg_no = 255;
		    if (strcmp(sym,"__text")== 0)
			segtab[header.x_nseg].x_sg_typ = X_SG_COD;
		    if (strcmp(sym,"__con")== 0)
			segtab[header.x_nseg].x_sg_typ = X_SG_CON;
		    if (strcmp(sym,"__data")== 0)
			segtab[header.x_nseg].x_sg_typ = X_SG_DAT;
		    if (strcmp(sym,"__bss")== 0)
			segtab[header.x_nseg].x_sg_typ = X_SG_BSS;
		    header.x_symb += sizeof(struct x_sym);
		    symct++;
		} else
		    outsym(sym, X_SY_SEG, 0, header.x_nseg);
		header.x_nseg++;
	    }
	    break;
	
	case OBGLO:	/* Global symbols block */
	    if(pass2 && dbgflg) printf("global ");
	case OBLOC:	/* Local symbols block */
	    if(pass2 && dbgflg) printf("symbols block\n");
	    while(objpt < objtop) {
		val = (uns)ogetl();
		sec = ogetb();
		sym = ogets();
		if(!pass2) {
		    if(sec == RBUND)	/* Enter symbol # of new external */
			*exttop++ = symct;
		    header.x_symb += sizeof(struct x_sym);
		    symct++;
		} else {
		    type = objtyp==OBGLO ? X_SY_GLB : X_SY_LOC;
		    if(sec == RBUND)
			type = X_SY_UNX;
		    outsym(sym, type, val, sec);
		}
	    }
	    break;

	case OBTXT:	/* Text block */
	    adr = (uns)ogetl();
	    sec = ogetb();
	    len = ogetb();
	    if(pass2 && dbgflg)
		printf("text section[%x] at %x for %x bytes\n",sec,adr,len);
	    if(!pass2) {
		if(adr+len > segtab[sec].x_sg_len)
		    segtab[sec].x_sg_len = adr+len;
		objpt += len;
		while(objpt < objtop) {
		    ogetb();		/* Relocation byte number */
		    relent = ogetw();	/* Relocation entry */
		    header.x_reloc += sizeof(struct x_rel);
		    relent &= RAMSK;
		    if(relent==RAZLS || relent==RAZSS || relent==RAZOF)
			header.x_magic = X_SU_MAGIC;
		}
	    } else {
		outtxt(adr, sec, objpt, len);
		objpt += len;
		while(objpt < objtop) {
		    relpos = adr + ogetb() - 6;
		    relent = ogetw();
		    action = relent & RAMSK;
		    base = relent & RBMSK;
		    switch(action) {

		    case RAA32M:	/* absolute 32-bit address */
			/*
			 * The loader can only relocate the least
			 * significant half of a 32-bit item.
			 */
			relpos += 2;
			/* Fall through to next case */

		    case RAA16M:	/* absolute 16-bit address */
		    case RAZOF:		/* segmented offset */
			type = base<RBEXT ? X_RL_OFF : X_RL_XOF;
			break;
		    
		    case RAZSS:		/* short segmented address */
			type = base<RBEXT ? X_RL_SSG : X_RL_XSSG;
			break;

		    case RAZLS:		/* long segmented address */
			type = base<RBEXT ? X_RL_LSG : X_RL_XLSG;
			break;

		    default:
			ferr("Illegal reloc. 0x%x at loc. 0x%x of sec. 0x%x\n",
			relent, relpos, sec);
		    }
		    outrel(type, relpos, sec, base);
		}
	    }
	    break;
	}
    }
}

/*
 * ferr - Issues a fatal error message and terminates.
 */
ferr(a, b, c, d, e)

char *a;
int b, c, d, e;

{
    fprintf(stderr, a, b, c, d, e);
    exit(1);
}

/*
 * init - Initialization.
 */
init(argc, argv)

int argc;
char *argv[];
{
    char *ap;

    prname = argv[0];
    argv[argc] = (char *)0;	/* For the benefit of UNIX Version 6 */
    header.x_magic = X_NU_MAGIC;
    while(*++argv != (char *)0) {
	ap = *argv;
	if(*ap == '-')	/* options */
	    while(*++ap != '\0')
		switch(*ap) {

		case 'd':	/* debug flag on */
		    dbgflg = 1;
		    break;
		case 'o':	/* Next argument is output file */
		    if((outname = *++argv) == (char *)0)
			usage();
		    break;

		case 's':	/* Force segmented magic number */
		    header.x_magic = X_SU_MAGIC;
		    break;

		default:	/* Illegal switch */
		    usage();
		}
	else {		/* object file name */
	    if(objname != (char *)0)
		usage();
	    objname = ap;
	}
    }

    if(objname==(char *)0 || strcmp(&objname[strlen(objname)-4], ".obj")!=0)
	usage();
    if((objiop = fopenb(objname, "r")) == NULL)
	ferr("Cannot open %s\n", objname);
    if(strcmp(objname, outname) == 0)
	ferr("Would destroy %s\n", objname);

    /*
     * The 0th entry in the segment table is the absolute segment.
     * We will output it only if some code is generated into it.  If we
     * do end up outputting it, we will hard assign it to segment number 0.
     */
    segtab[0].x_sg_typ = X_SG_BSS;
}

/*
 * interlude - Performs interpass processing.
 */
interlude() {

    long off;
    uns *extptr;
    int segno;

    if(exttop == exttab) {	/* No undefined externals, make executable */
	header.x_magic = header.x_magic==X_SU_MAGIC ? X_SX_MAGIC : X_NXN_MAGIC;
	outfd = creatb(outname, 0777);
    } else
	outfd = creatb(outname, 0666);
    if(outfd == -1)
	ferr("Cannot create %s\n", outname);

    if(segtab[0].x_sg_typ==X_SG_BSS || segtab[0].x_sg_typ==X_SG_STK)
	/*
	 * There is no code going into the absolute segment, so we skip it.
	 */
	firstseg = 1;

    off = (sizeof header) + (header.x_nseg - firstseg) * sizeof(struct x_sg);
    for(segno=0 ; segno<header.x_nseg ; segno++) {
	segoff[segno] = off;
	if(segtab[segno].x_sg_typ!=X_SG_BSS &&
	segtab[segno].x_sg_typ!=X_SG_STK) {
	    off += segtab[segno].x_sg_len;
	    header.x_init += segtab[segno].x_sg_len;
	}
    }
    reloff = off;
    symoff = reloff + header.x_reloc;
    
    curoff = 0L;
    outhdr();
    outseg();
}

/*
 * ofill - Reads the next object block into objbuf, and returns the block type.
 */
ofill() {

    int ch;

    if((objtyp = getc(objiop)) == EOF) return(0);
    if((ch = getc(objiop)) == EOF) ferr("Premature EOF\n");
    objtop = &objbuf[ch];
    for(objpt=objbuf ; objpt<objtop ; objpt++) {
	if((ch = getc(objiop)) == EOF) ferr("Premature EOF\n");
	*objpt = ch;
    }
    objpt = objbuf;
    return(objtyp);
}

/*
 * ogetb - Returns the next byte from the object buffer.
 */
uns
ogetb() {

    if(objpt >= objtop) {
	ferr("Off the end of objbuf\n");
    }
    return(*objpt++&0377);
}

/*
 * ogetl - Returns the next long word from the object buffer.
 */
long
ogetl() {

    long l;

    l = ogetw();
    return((long)ogetw()<<16|l);
}

/*
 * ogets - Returns a pointer to the next string in the object buffer.
 */
char *
ogets() {

    char *s;

    s = objpt;  while(ogetb());
    return(s);
}

/*
 * ogetw - Returns the next word from the object buffer.
 */
uns
ogetw() {

    uns w;

    w = ogetb();
    return(ogetb()<<8|w);
}

/*
 * outb - Outputs a single byte to the .out file.
 */
outb(val)

char val;
{
    write(outfd, &val, 1);
    curoff++;
}

/*
 * outhdr - Outputs the header section of the .out file.
 */
outhdr()
{
    outw(header.x_magic);
    outw(header.x_nseg - firstseg);
    outl(header.x_init);
    outl(header.x_reloc);
    outl(header.x_symb);
}

/*
 * outl - Outputs a single long word to the .out file.  The long word is
 * written in Z8000 (MSB first) format, regardless of the host processor.
 */
outl(val)

long val;
{
    char buf[4];

    buf[3] = val;
    val >>= 8;
    buf[2] = val;
    val >>= 8;
    buf[1] = val;
    val >>= 8;
    buf[0] = val;
    write(outfd, buf, 4);
    curoff += 4;
}

/*
 * outrel - Outputs a relocation entry with the specified type, offset,
 * segment, and relocation base.
 */
outrel(type, offset, segment, base)

int type;
uns offset;
int segment;
uns base;
{
    if(curoff != reloff)
	lseek(outfd, curoff = reloff, 0);
    if(base >= RBEXT)
	base = exttab[base-RBEXT];
    else
	base -= firstseg;
    outb(segment - firstseg);
    outb(type);
    outw(offset);
    outw(base);
    reloff = curoff;
}

/*
 * outseg - Outputs the segments section of the .out file.
 */
outseg()
{
    struct x_sg *p;

    for(p = &segtab[firstseg] ; p < &segtab[header.x_nseg] ; p++) {
	outb(p->x_sg_no);
	outb(p->x_sg_typ);
	outw(p->x_sg_len);
    }
}

/*
 * outsym - Outputs a symbol table entry with the specified name,
 * type, value, and segment.
 */
outsym(name, type, value, segment)

register char *name;
int type;
unsigned short value;
int segment;
{
    register int i;

    if(curoff != symoff)
	lseek(outfd, curoff = symoff, 0);
    if(segment==RBABS || segment==RBUND)
	segment = 255;
    else
	segment -= firstseg;
    outb(segment);
    outb(type);
    outw(value);
    for(i=0 ; i<XNAMELN ; i++) {
	outb(*name);
	if(*name != '\0')
	    name++;
    }
    symoff = curoff;
}

/*
 * outtxt - Outputs a block of text for the specified offset in the specified
 * segment.  The text comes from txtptr and has txtct bytes.
 */
outtxt(offset, segment, txtptr, txtct)

uns offset;
int segment;
char *txtptr;
int txtct;
{
    long txtoff;

    txtoff = segoff[segment] + (long)offset;
    if(curoff != txtoff)
	lseek(outfd, curoff = txtoff, 0);
    write(outfd, txtptr, txtct);
    curoff += txtct;
}

/*
 * outw - Outputs a single word to the .out file.  The word is written in
 * Z8000 (MSB first) format, regardless of the host processor.
 */
outw(val)

uns val;
{
    char buf[2];

    buf[1] = val;
    val >>= 8;
    buf[0] = val;
    write(outfd, buf, 2);
    curoff += 2;
}

/*
 * usage - Issues a fatal error message for incorrect usage.
 */
usage() {

    ferr("Usage: %s [-o x.out] [-s] file.obj\n", prname);
}
