/*
 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * Portions Copyright (c) 1999 Apple Computer, Inc.  All Rights
 * Reserved.  This file contains Original Code and/or Modifications of
 * Original Code as defined in and that are subject to the Apple Public
 * Source License Version 1.1 (the "License").  You may not use this file
 * except in compliance with the License.  Please obtain a copy of the
 * License at http://www.apple.com/publicsource and read it before using
 * this file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/* outobj.c	output routines for the Netwide Assembler to produce
 *		Microsoft 16-bit .OBJ object files
 *
 * The Netwide Assembler is copyright (C) 1996 Simon Tatham and
 * Julian Hall. All rights reserved. The software is
 * redistributable under the licence given in the file "Licence"
 * distributed in the NASM archive.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "nasm.h"
#include "nasmlib.h"
#include "outform.h"

#ifdef OF_OBJ

static char obj_infile[FILENAME_MAX];
static int obj_uppercase;

static efunc error;
static evalfunc evaluate;
static ldfunc deflabel;
static FILE *ofp;
static long first_seg;
static int any_segs;

#define LEDATA_MAX 1024		       /* maximum size of LEDATA record */
#define RECORD_MAX 1024		       /* maximum size of _any_ record */
#define GROUP_MAX 256		       /* we won't _realistically_ have more
					* than this many segs in a group */
#define EXT_BLKSIZ 256		       /* block size for externals list */

static unsigned char record[RECORD_MAX], *recptr;

struct Segment;			       /* need to know these structs exist */
struct Group;

static struct Public {
    struct Public *next;
    char *name;
    long offset;
    long segment;		       /* only if it's far-absolute */
} *fpubhead, **fpubtail;

static struct External {
    struct External *next;
    char *name;
    long commonsize;
    long commonelem;		       /* element size if FAR, else zero */
    int index;			       /* OBJ-file external index */
    enum {
	DEFWRT_NONE,		       /* no unusual default-WRT */
	DEFWRT_STRING,		       /* a string we don't yet understand */
	DEFWRT_SEGMENT,		       /* a segment */
	DEFWRT_GROUP		       /* a group */
    } defwrt_type;
    union {
	char *string;
	struct Segment *seg;
	struct Group *grp;
    } defwrt_ptr;
    struct External *next_dws;	       /* next with DEFWRT_STRING */
} *exthead, **exttail, *dws;

static int externals;

static struct ExtBack {
    struct ExtBack *next;
    struct External *exts[EXT_BLKSIZ];
} *ebhead, **ebtail;

static struct Segment {
    struct Segment *next;
    long index;			       /* the NASM segment id */
    long obj_index;		       /* the OBJ-file segment index */
    struct Group *grp;		       /* the group it belongs to */
    long currentpos;
    long align;			       /* can be SEG_ABS + absolute addr */
    enum {
	CMB_PRIVATE = 0,
	CMB_PUBLIC = 2,
	CMB_STACK = 5,
	CMB_COMMON = 6
    } combine;
    long use32;			       /* is this segment 32-bit? */
    struct Public *pubhead, **pubtail;
    char *name;
    char *segclass, *overlay;	       /* `class' is a C++ keyword :-) */
} *seghead, **segtail, *obj_seg_needs_update;

static struct Group {
    struct Group *next;
    char *name;
    long index;			       /* NASM segment id */
    long obj_index;		       /* OBJ-file group index */
    long nentries;		       /* number of elements... */
    long nindices;		       /* ...and number of index elts... */
    union {
	long index;
	char *name;
    } segs[GROUP_MAX];		       /* ...in this */
} *grphead, **grptail, *obj_grp_needs_update;

static struct ObjData {
    struct ObjData *next;
    int nonempty;
    struct Segment *seg;
    long startpos;
    int letype, ftype;
    unsigned char ledata[LEDATA_MAX], *lptr;
    unsigned char fixupp[RECORD_MAX], *fptr;
} *datahead, *datacurr, **datatail;

static struct ImpDef {
    struct ImpDef *next;
    char *extname;
    char *libname;
    unsigned int impindex;
    char *impname;
} *imphead, **imptail;

static struct ExpDef {
    struct ExpDef *next;
    char *intname;
    char *extname;
    unsigned int ordinal;
    int flags;
} *exphead, **exptail;

#define EXPDEF_FLAG_ORDINAL  0x80
#define EXPDEF_FLAG_RESIDENT 0x40
#define EXPDEF_FLAG_NODATA   0x20
#define EXPDEF_MASK_PARMCNT  0x1F

static long obj_entry_seg, obj_entry_ofs;

enum RecordID {			       /* record ID codes */

    THEADR = 0x80,		       /* module header */
    COMENT = 0x88,		       /* comment record */

    LNAMES = 0x96,		       /* list of names */

    SEGDEF = 0x98,		       /* segment definition */
    GRPDEF = 0x9A,		       /* group definition */
    EXTDEF = 0x8C,		       /* external definition */
    PUBDEF = 0x90,		       /* public definition */
    COMDEF = 0xB0,		       /* common definition */

    LEDATA = 0xA0,		       /* logical enumerated data */
    FIXUPP = 0x9C,		       /* fixups (relocations) */

    MODEND = 0x8A		       /* module end */
};

extern struct ofmt of_obj;

static long obj_ledata_space(struct Segment *);
static int obj_fixup_free(struct Segment *);
static void obj_ledata_new(struct Segment *);
static void obj_ledata_commit(void);
static void obj_write_fixup (struct ObjData *, int, int, long, long, long);
static long obj_segment (char *, int, int *);
static void obj_write_file(void);
static unsigned char *obj_write_data(unsigned char *, unsigned char *, int);
static unsigned char *obj_write_byte(unsigned char *, int);
static unsigned char *obj_write_word(unsigned char *, int);
static unsigned char *obj_write_dword(unsigned char *, long);
static unsigned char *obj_write_rword(unsigned char *, int);
static unsigned char *obj_write_name(unsigned char *, char *);
static unsigned char *obj_write_index(unsigned char *, int);
static unsigned char *obj_write_value(unsigned char *, unsigned long);
static void obj_record(int, unsigned char *, unsigned char *);
static int obj_directive (char *, char *, int);

static void obj_init (FILE *fp, efunc errfunc, ldfunc ldef, evalfunc eval) {
    ofp = fp;
    error = errfunc;
    evaluate = eval;
    deflabel = ldef;
    first_seg = seg_alloc();
    any_segs = FALSE;
    fpubhead = NULL;
    fpubtail = &fpubhead;
    exthead = NULL;
    exttail = &exthead;
    imphead = NULL;
    imptail = &imphead;
    exphead = NULL;
    exptail = &exphead;
    dws = NULL;
    externals = 0;
    ebhead = NULL;
    ebtail = &ebhead;
    seghead = obj_seg_needs_update = NULL;
    segtail = &seghead;
    grphead = obj_grp_needs_update = NULL;
    grptail = &grphead;
    datahead = datacurr = NULL;
    datatail = &datahead;
    obj_entry_seg = NO_SEG;
    obj_uppercase = FALSE;
}

static void obj_cleanup (void) {
    obj_write_file();
    fclose (ofp);
    while (seghead) {
	struct Segment *segtmp = seghead;
	seghead = seghead->next;
	while (segtmp->pubhead) {
	    struct Public *pubtmp = segtmp->pubhead;
	    segtmp->pubhead = pubtmp->next;
	    nasm_free (pubtmp->name);
	    nasm_free (pubtmp);
	}
	nasm_free (segtmp);
    }
    while (fpubhead) {
	struct Public *pubtmp = fpubhead;
	fpubhead = fpubhead->next;
	nasm_free (pubtmp->name);
	nasm_free (pubtmp);
    }
    while (exthead) {
	struct External *exttmp = exthead;
	exthead = exthead->next;
	nasm_free (exttmp);
    }
    while (imphead) {
	struct ImpDef *imptmp = imphead;
	imphead = imphead->next;
	nasm_free (imptmp->extname);
	nasm_free (imptmp->libname);
	nasm_free (imptmp->impname);   /* nasm_free won't mind if it's NULL */
	nasm_free (imptmp);
    }
    while (exphead) {
	struct ExpDef *exptmp = exphead;
	exphead = exphead->next;
	nasm_free (exptmp->extname);
	nasm_free (exptmp->intname);
	nasm_free (exptmp);
    }
    while (ebhead) {
	struct ExtBack *ebtmp = ebhead;
	ebhead = ebhead->next;
	nasm_free (ebtmp);
    }
    while (grphead) {
	struct Group *grptmp = grphead;
	grphead = grphead->next;
	nasm_free (grptmp);
    }
    while (datahead) {
	struct ObjData *datatmp = datahead;
	datahead = datahead->next;
	nasm_free (datatmp);
    }
}

static void obj_ext_set_defwrt (struct External *ext, char *id) {
    struct Segment *seg;
    struct Group *grp;

    for (seg = seghead; seg; seg = seg->next)
	if (!strcmp(seg->name, id)) {
	    ext->defwrt_type = DEFWRT_SEGMENT;
	    ext->defwrt_ptr.seg = seg;
	    nasm_free (id);
	    return;
	}

    for (grp = grphead; grp; grp = grp->next)
	if (!strcmp(grp->name, id)) {
	    ext->defwrt_type = DEFWRT_GROUP;
	    ext->defwrt_ptr.grp = grp;
	    nasm_free (id);
	    return;
	}

    ext->defwrt_type = DEFWRT_STRING;
    ext->defwrt_ptr.string = id;
    ext->next_dws = dws;
    dws = ext;
}

static void obj_deflabel (char *name, long segment,
			  long offset, int is_global, char *special) {
    /*
     * We have three cases:
     *
     * (i) `segment' is a segment-base. If so, set the name field
     * for the segment or group structure it refers to, and then
     * return.
     *
     * (ii) `segment' is one of our segments, or a SEG_ABS segment.
     * Save the label position for later output of a PUBDEF record.
     * (Or a MODPUB, if we work out how.)
     *
     * (iii) `segment' is not one of our segments. Save the label
     * position for later output of an EXTDEF, and also store a
     * back-reference so that we can map later references to this
     * segment number to the external index.
     */
    struct External *ext;
    struct ExtBack *eb;
    struct Segment *seg;
    int i;
    int used_special = FALSE;	       /* have we used the special text? */

    /*
     * If it's a special-retry from pass two, discard it.
     */
    if (is_global == 3)
	return;

    /*
     * First check for the double-period, signifying something
     * unusual.
     */
    if (name[0] == '.' && name[1] == '.' && name[2] != '@') {
	if (!strcmp(name, "..start")) {
	    obj_entry_seg = segment;
	    obj_entry_ofs = offset;
	    return;
	}
	error (ERR_NONFATAL, "unrecognised special symbol `%s'", name);
    }

    /*
     * Case (i):
     */
    if (obj_seg_needs_update) {
	obj_seg_needs_update->name = name;
	return;
    } else if (obj_grp_needs_update) {
	obj_grp_needs_update->name = name;
	return;
    }
    if (segment < SEG_ABS && segment != NO_SEG && segment % 2)
	return;

    if (segment >= SEG_ABS || segment == NO_SEG) {
	/*
	 * SEG_ABS subcase of (ii).
	 */
	if (is_global) {
	    struct Public *pub;

	    pub = *fpubtail = nasm_malloc(sizeof(*pub));
	    fpubtail = &pub->next;
	    pub->next = NULL;
	    pub->name = nasm_strdup(name);
	    pub->offset = offset;
	    pub->segment = (segment == NO_SEG ? 0 : segment & ~SEG_ABS);
	}
	if (special)
	    error(ERR_NONFATAL, "OBJ supports no special symbol features"
		  " for this symbol type");
	return;
    }

    /*
     * If `any_segs' is still FALSE, we might need to define a
     * default segment, if they're trying to declare a label in
     * `first_seg'.
     */
    if (!any_segs && segment == first_seg) {
	int tempint;		       /* ignored */
	if (segment != obj_segment("__NASMDEFSEG", 2, &tempint))
	    error (ERR_PANIC, "strange segment conditions in OBJ driver");
    }

    for (seg = seghead; seg; seg = seg->next)
	if (seg->index == segment) {
	    /*
	     * Case (ii). Maybe MODPUB someday?
	     */
	    if (is_global) {
		struct Public *pub;
		pub = *seg->pubtail = nasm_malloc(sizeof(*pub));
		seg->pubtail = &pub->next;
		pub->next = NULL;
		pub->name = nasm_strdup(name);
		pub->offset = offset;
	    }
	    if (special)
		error(ERR_NONFATAL, "OBJ supports no special symbol features"
		      " for this symbol type");
	    return;
	}

    /*
     * Case (iii).
     */
    ext = *exttail = nasm_malloc(sizeof(*ext));
    ext->next = NULL;
    exttail = &ext->next;
    ext->name = name;
    ext->defwrt_type = DEFWRT_NONE;
    if (is_global == 2) {
	ext->commonsize = offset;
	ext->commonelem = 1;	       /* default FAR */
    } else
	ext->commonsize = 0;

    /*
     * Now process the special text, if any, to find default-WRT
     * specifications and common-variable element-size and near/far
     * specifications.
     */
    while (special && *special) {
	used_special = TRUE;

	/*
	 * We might have a default-WRT specification.
	 */
	if (!nasm_strnicmp(special, "wrt", 3)) {
	    char *p;
	    int len;
	    special += 3;
	    special += strspn(special, " \t");
	    p = nasm_strndup(special, len = strcspn(special, ":"));
	    obj_ext_set_defwrt (ext, p);
	    special += len;
	    if (*special && *special != ':')
		error(ERR_NONFATAL, "`:' expected in special symbol"
		      " text for `%s'", ext->name);
	    else if (*special == ':')
		special++;
	}

	/*
	 * The NEAR or FAR keywords specify nearness or
	 * farness. FAR gives default element size 1.
	 */
	if (!nasm_strnicmp(special, "far", 3)) {
	    if (ext->commonsize)
		ext->commonelem = 1;
	    else
		error(ERR_NONFATAL, "`%s': `far' keyword may only be applied"
		      " to common variables\n", ext->name);
	    special += 3;
	    special += strspn(special, " \t");
	} else if (!nasm_strnicmp(special, "near", 4)) {
	    if (ext->commonsize)
		ext->commonelem = 0;
	    else
		error(ERR_NONFATAL, "`%s': `far' keyword may only be applied"
		      " to common variables\n", ext->name);
	    special += 4;
	    special += strspn(special, " \t");
	}

	/*
	 * If it's a common, and anything else remains on the line
	 * before a further colon, evaluate it as an expression and
	 * use that as the element size. Forward references aren't
	 * allowed.
	 */
	if (*special == ':')
	    special++;
	else if (*special) {
	    if (ext->commonsize) {
		expr *e;
		struct tokenval tokval;

		stdscan_reset();
		stdscan_bufptr = special;
		tokval.t_type = TOKEN_INVALID;
		e = evaluate(stdscan, NULL, &tokval, NULL, 1, error, NULL);
		if (e) {
		    if (!is_simple(e))
			error (ERR_NONFATAL, "cannot use relocatable"
			       " expression as common-variable element size");
		    else
			ext->commonelem = reloc_value(e);
		}
		special = stdscan_bufptr;
	    } else {
		error (ERR_NONFATAL, "`%s': element-size specifications only"
		       " apply to common variables", ext->name);
		while (*special && *special != ':')
		    special++;
		if (*special == ':')
		    special++;
	    }
	}
    }

    i = segment/2;
    eb = ebhead;
    if (!eb) {
	eb = *ebtail = nasm_malloc(sizeof(*eb));
	eb->next = NULL;
	ebtail = &eb->next;
    }
    while (i > EXT_BLKSIZ) {
	if (eb && eb->next)
	    eb = eb->next;
	else {
	    eb = *ebtail = nasm_malloc(sizeof(*eb));
	    eb->next = NULL;
	    ebtail = &eb->next;
	}
	i -= EXT_BLKSIZ;
    }
    eb->exts[i] = ext;
    ext->index = ++externals;

    if (special && !used_special)
	error(ERR_NONFATAL, "OBJ supports no special symbol features"
	      " for this symbol type");
}

static void obj_out (long segto, void *data, unsigned long type,
		     long segment, long wrt) {
    long size, realtype;
    unsigned char *ucdata;
    long ldata;
    struct Segment *seg;

    /*
     * handle absolute-assembly (structure definitions)
     */
    if (segto == NO_SEG) {
	if ((type & OUT_TYPMASK) != OUT_RESERVE)
	    error (ERR_NONFATAL, "attempt to assemble code in [ABSOLUTE]"
		   " space");
	return;
    }

    /*
     * If `any_segs' is still FALSE, we must define a default
     * segment.
     */
    if (!any_segs) {
	int tempint;		       /* ignored */
	if (segto != obj_segment("__NASMDEFSEG", 2, &tempint))
	    error (ERR_PANIC, "strange segment conditions in OBJ driver");
    }

    /*
     * Find the segment we are targetting.
     */
    for (seg = seghead; seg; seg = seg->next)
	if (seg->index == segto)
	    break;
    if (!seg)
	error (ERR_PANIC, "code directed to nonexistent segment?");

    size = type & OUT_SIZMASK;
    realtype = type & OUT_TYPMASK;
    if (realtype == OUT_RAWDATA) {
	ucdata = data;
	while (size > 0) {
	    long len = obj_ledata_space(seg);
	    if (len == 0) {
		obj_ledata_new(seg);
		len = obj_ledata_space(seg);
	    }
	    if (len > size)
		len = size;
	    datacurr->lptr = obj_write_data (datacurr->lptr, ucdata, len);
	    datacurr->nonempty = TRUE;
	    ucdata += len;
	    size -= len;
	    seg->currentpos += len;
	}
    } else if (realtype == OUT_ADDRESS || realtype == OUT_REL2ADR ||
	       realtype == OUT_REL4ADR) {
	int rsize;

	if (segment == NO_SEG && realtype != OUT_ADDRESS)
	    error(ERR_NONFATAL, "relative call to absolute address not"
		  " supported by OBJ format");
	if (segment >= SEG_ABS)
	    error(ERR_NONFATAL, "far-absolute relocations not supported"
		  " by OBJ format");
	ldata = *(long *)data;
	if (realtype == OUT_REL2ADR) {
	    ldata += (size-2);
	    size = 2;
	}
	if (realtype == OUT_REL4ADR) {
	    ldata += (size-4);
	    size = 4;
	}
	if (obj_ledata_space(seg) < 4 || !obj_fixup_free(seg))
	    obj_ledata_new(seg);
	if (size == 2)
	    datacurr->lptr = obj_write_word (datacurr->lptr, ldata);
	else
	    datacurr->lptr = obj_write_dword (datacurr->lptr, ldata);
	datacurr->nonempty = TRUE;
	rsize = size;
	if (segment < SEG_ABS && (segment != NO_SEG && segment % 2) &&
	    size == 4) {
	    /*
	     * This is a 4-byte segment-base relocation such as
	     * `MOV EAX,SEG foo'. OBJ format can't actually handle
	     * these, but if the constant term has the 16 low bits
	     * zero, we can just apply a 2-byte segment-base
	     * relocation to the low word instead.
	     */
	    rsize = 2;
	    if (ldata & 0xFFFF)
		error(ERR_NONFATAL, "OBJ format cannot handle complex"
		      " dword-size segment base references");
	}
	if (segment != NO_SEG)
	    obj_write_fixup (datacurr, rsize,
			     (realtype == OUT_REL2ADR ||
			      realtype == OUT_REL4ADR ? 0 : 0x4000),
			     segment, wrt,
			     (seg->currentpos - datacurr->startpos));
	seg->currentpos += size;
    } else if (realtype == OUT_RESERVE) {
	obj_ledata_commit();
	seg->currentpos += size;
    }
}

static long obj_ledata_space(struct Segment *segto) {
    if (datacurr && datacurr->seg == segto)
	return datacurr->ledata + LEDATA_MAX - datacurr->lptr;
    else
	return 0;
}

static int obj_fixup_free(struct Segment *segto) {
    if (datacurr && datacurr->seg == segto)
	return (datacurr->fixupp + RECORD_MAX - datacurr->fptr) > 8;
    else
	return 0;
}

static void obj_ledata_new(struct Segment *segto) {
    datacurr = *datatail = nasm_malloc(sizeof(*datacurr));
    datacurr->next = NULL;
    datatail = &datacurr->next;
    datacurr->nonempty = FALSE;
    datacurr->lptr = datacurr->ledata;
    datacurr->fptr = datacurr->fixupp;
    datacurr->seg = segto;
    if (segto->use32)
	datacurr->letype = LEDATA+1;
    else
	datacurr->letype = LEDATA;
    datacurr->startpos = segto->currentpos;
    datacurr->ftype = FIXUPP;

    datacurr->lptr = obj_write_index (datacurr->lptr, segto->obj_index);
    if (datacurr->letype == LEDATA)
	datacurr->lptr = obj_write_word (datacurr->lptr, segto->currentpos);
    else
	datacurr->lptr = obj_write_dword (datacurr->lptr, segto->currentpos);
}

static void obj_ledata_commit(void) {
    datacurr = NULL;
}

static void obj_write_fixup (struct ObjData *data, int bytes,
			     int segrel, long seg, long wrt,
			     long offset) {
    int locat, method;
    int base;
    long tidx, fidx;
    struct Segment *s = NULL;
    struct Group *g = NULL;
    struct External *e = NULL;

    if (bytes == 1) {
	error(ERR_NONFATAL, "`obj' output driver does not support"
	      " one-byte relocations");
	return;
    }

    locat = 0x8000 | segrel | offset;
    if (seg % 2) {
	base = TRUE;
	locat |= 0x800;
	seg--;
	if (bytes != 2)
	    error(ERR_PANIC, "OBJ: 4-byte segment base fixup got"
		  " through sanity check");
    } else {
	base = FALSE;
	if (bytes == 2)
	    locat |= 0x400;
	else {
	    locat |= 0x2400;
	    data->ftype = FIXUPP+1;    /* need new-style FIXUPP record */
	}
    }
    data->fptr = obj_write_rword (data->fptr, locat);

    tidx = fidx = -1, method = 0;      /* placate optimisers */

    /*
     * See if we can find the segment ID in our segment list. If
     * so, we have a T4 (LSEG) target.
     */
    for (s = seghead; s; s = s->next)
	if (s->index == seg)
	    break;
    if (s)
	method = 4, tidx = s->obj_index;
    else {
	for (g = grphead; g; g = g->next)
	    if (g->index == seg)
		break;
	if (g)
	    method = 5, tidx = g->obj_index;
	else {
	    long i = seg/2;
	    struct ExtBack *eb = ebhead;
	    while (i > EXT_BLKSIZ) {
		if (eb)
		    eb = eb->next;
		else
		    break;
		i -= EXT_BLKSIZ;
	    }
	    if (eb)
		method = 6, e = eb->exts[i], tidx = e->index;
	    else
		error(ERR_PANIC,
		      "unrecognised segment value in obj_write_fixup");
	}
    }

    /*
     * If no WRT given, assume the natural default, which is method
     * F5 unless:
     *
     * - we are doing an OFFSET fixup for a grouped segment, in
     *   which case we require F1 (group).
     *
     * - we are doing an OFFSET fixup for an external with a
     *   default WRT, in which case we must honour the default WRT.
     */
    if (wrt == NO_SEG) {
	if (!base && s && s->grp)
	    method |= 0x10, fidx = s->grp->obj_index;
	else if (!base && e && e->defwrt_type != DEFWRT_NONE) {
	    if (e->defwrt_type == DEFWRT_SEGMENT)
		method |= 0x00, fidx = e->defwrt_ptr.seg->obj_index;
	    else if (e->defwrt_type == DEFWRT_GROUP)
		method |= 0x10, fidx = e->defwrt_ptr.grp->obj_index;
	    else {
		error(ERR_NONFATAL, "default WRT specification for"
		      " external `%s' unresolved", e->name);
		method |= 0x50, fidx = -1; /* got to do _something_ */
	    }
	} else
	    method |= 0x50, fidx = -1;
    } else {
	/*
	 * See if we can find the WRT-segment ID in our segment
	 * list. If so, we have a F0 (LSEG) frame.
	 */
	for (s = seghead; s; s = s->next)
	    if (s->index == wrt-1)
		break;
	if (s)
	    method |= 0x00, fidx = s->obj_index;
	else {
	    for (g = grphead; g; g = g->next)
		if (g->index == wrt-1)
		    break;
	    if (g)
		method |= 0x10, fidx = g->obj_index;
	    else {
		long i = wrt/2;
		struct ExtBack *eb = ebhead;
		while (i > EXT_BLKSIZ) {
		    if (eb)
			eb = eb->next;
		    else
			break;
		    i -= EXT_BLKSIZ;
		}
		if (eb)
		    method |= 0x20, fidx = eb->exts[i]->index;
		else
		    error(ERR_PANIC,
			  "unrecognised WRT value in obj_write_fixup");
	    }
	}
    }

    data->fptr = obj_write_byte (data->fptr, method);
    if (fidx != -1)
	data->fptr = obj_write_index (data->fptr, fidx);
    data->fptr = obj_write_index (data->fptr, tidx);
}

static long obj_segment (char *name, int pass, int *bits) {
    /*
     * We call the label manager here to define a name for the new
     * segment, and when our _own_ label-definition stub gets
     * called in return, it should register the new segment name
     * using the pointer it gets passed. That way we save memory,
     * by sponging off the label manager.
     */
    if (!name) {
	*bits = 16;
	return first_seg;
    } else {
	struct Segment *seg;
	struct Group *grp;
	struct External **extp;
	int obj_idx, i, attrs, rn_error;
	char *p;

	/*
	 * Look for segment attributes.
	 */
	attrs = 0;
	while (*name == '.')
	    name++;		       /* hack, but a documented one */
	p = name;
	while (*p && !isspace(*p))
	    p++;
	if (*p) {
	    *p++ = '\0';
	    while (*p && isspace(*p))
		*p++ = '\0';
	}
	while (*p) {
	    while (*p && !isspace(*p))
		p++;
	    if (*p) {
		*p++ = '\0';
		while (*p && isspace(*p))
		    *p++ = '\0';
	    }

	    attrs++;
	}

	obj_idx = 1;
	for (seg = seghead; seg; seg = seg->next) {
	    obj_idx++;
	    if (!strcmp(seg->name, name)) {
		if (attrs > 0 && pass == 1)
		    error(ERR_WARNING, "segment attributes specified on"
			  " redeclaration of segment: ignoring");
		if (seg->use32)
		    *bits = 32;
		else
		    *bits = 16;
		return seg->index;
	    }
	}

	*segtail = seg = nasm_malloc(sizeof(*seg));
	seg->next = NULL;
	segtail = &seg->next;
	seg->index = (any_segs ? seg_alloc() : first_seg);
	seg->obj_index = obj_idx;
	seg->grp = NULL;
	any_segs = TRUE;
	seg->name = NULL;
	seg->currentpos = 0;
	seg->align = 1;		       /* default */
	seg->use32 = FALSE;	       /* default */
	seg->combine = CMB_PUBLIC;     /* default */
	seg->segclass = seg->overlay = NULL;
	seg->pubhead = NULL;
	seg->pubtail = &seg->pubhead;

	/*
	 * Process the segment attributes.
	 */
	p = name;
	while (attrs--) {
	    p += strlen(p);
	    while (!*p) p++;

	    /*
	     * `p' contains a segment attribute.
	     */
	    if (!nasm_stricmp(p, "private"))
		seg->combine = CMB_PRIVATE;
	    else if (!nasm_stricmp(p, "public"))
		seg->combine = CMB_PUBLIC;
	    else if (!nasm_stricmp(p, "common"))
		seg->combine = CMB_COMMON;
	    else if (!nasm_stricmp(p, "stack"))
		seg->combine = CMB_STACK;
	    else if (!nasm_stricmp(p, "use16"))
		seg->use32 = FALSE;
	    else if (!nasm_stricmp(p, "use32"))
		seg->use32 = TRUE;
	    else if (!nasm_stricmp(p, "flat")) {
		/*
		 * This segment is an OS/2 FLAT segment. That means
		 * that its default group is group FLAT, even if
		 * the group FLAT does not explicitly _contain_ the
		 * segment.
		 * 
		 * When we see this, we must create the group
		 * `FLAT', containing no segments, if it does not
		 * already exist; then we must set the default
		 * group of this segment to be the FLAT group.
		 */
		struct Group *grp;
		for (grp = grphead; grp; grp = grp->next)
		    if (!strcmp(grp->name, "FLAT"))
			break;
		if (!grp) {
		    obj_directive ("group", "FLAT", 1);
		    for (grp = grphead; grp; grp = grp->next)
			if (!strcmp(grp->name, "FLAT"))
			    break;
		    if (!grp)
			error (ERR_PANIC, "failure to define FLAT?!");
		}
		seg->grp = grp;
	    } else if (!nasm_strnicmp(p, "class=", 6))
		seg->segclass = nasm_strdup(p+6);
	    else if (!nasm_strnicmp(p, "overlay=", 8))
		seg->overlay = nasm_strdup(p+8);
	    else if (!nasm_strnicmp(p, "align=", 6)) {
		seg->align = readnum(p+6, &rn_error);
		if (rn_error) {
		    seg->align = 1;
		    error (ERR_NONFATAL, "segment alignment should be"
			   " numeric");
		}
		switch ((int) seg->align) {
		  case 1:	       /* BYTE */
		  case 2:	       /* WORD */
		  case 4:	       /* DWORD */
		  case 16:	       /* PARA */
		  case 256:	       /* PAGE */
		  case 4096:	       /* PharLap extension */
		    break;
		  case 8:
		    error(ERR_WARNING, "OBJ format does not support alignment"
			  " of 8: rounding up to 16");
		    seg->align = 16;
		    break;
		  case 32:
		  case 64:
		  case 128:
		    error(ERR_WARNING, "OBJ format does not support alignment"
			  " of %d: rounding up to 256", seg->align);
		    seg->align = 256;
		    break;
		  case 512:
		  case 1024:
		  case 2048:
		    error(ERR_WARNING, "OBJ format does not support alignment"
			  " of %d: rounding up to 4096", seg->align);
		    seg->align = 4096;
		    break;
		  default:
		    error(ERR_NONFATAL, "invalid alignment value %d",
			  seg->align);
		    seg->align = 1;
		    break;
		}
	    } else if (!nasm_strnicmp(p, "absolute=", 9)) {
		seg->align = SEG_ABS + readnum(p+9, &rn_error);
		if (rn_error)
		    error (ERR_NONFATAL, "argument to `absolute' segment"
			   " attribute should be numeric");
	    }
	}

	obj_seg_needs_update = seg;
	if (seg->align >= SEG_ABS)
	    deflabel (name, NO_SEG, seg->align - SEG_ABS,
		      NULL, FALSE, FALSE, &of_obj, error);
	else
	    deflabel (name, seg->index+1, 0L,
		      NULL, FALSE, FALSE, &of_obj, error);
	obj_seg_needs_update = NULL;

	/*
	 * See if this segment is defined in any groups.
	 */
	for (grp = grphead; grp; grp = grp->next) {
	    for (i = grp->nindices; i < grp->nentries; i++) {
		if (!strcmp(grp->segs[i].name, seg->name)) {
		    nasm_free (grp->segs[i].name);
		    grp->segs[i] = grp->segs[grp->nindices];
		    grp->segs[grp->nindices++].index = seg->obj_index;
		    if (seg->grp)
			error(ERR_WARNING, "segment `%s' is already part of"
			      " a group: first one takes precedence",
			      seg->name);
		    else
			seg->grp = grp;
		}
	    }
	}

	/*
	 * Walk through the list of externals with unresolved
	 * default-WRT clauses, and resolve any that point at this
	 * segment.
	 */
	extp = &dws;
	while (*extp) {
	    if ((*extp)->defwrt_type == DEFWRT_STRING &&
		!strcmp((*extp)->defwrt_ptr.string, seg->name)) {
		(*extp)->defwrt_type = DEFWRT_SEGMENT;
		(*extp)->defwrt_ptr.seg = seg;
		*extp = (*extp)->next_dws;
	    } else
		extp = &(*extp)->next_dws;
	}

	if (seg->use32)
	    *bits = 32;
	else
	    *bits = 16;
	return seg->index;
    }
}

static int obj_directive (char *directive, char *value, int pass) {
    if (!strcmp(directive, "group")) {
	char *p, *q, *v;
	if (pass == 1) {
	    struct Group *grp;
	    struct Segment *seg;
	    struct External **extp;
	    int obj_idx;

	    q = value;
	    while (*q == '.')
		q++;		       /* hack, but a documented one */
	    v = q;
	    while (*q && !isspace(*q))
		q++;
	    if (isspace(*q)) {
		*q++ = '\0';
		while (*q && isspace(*q))
		    q++;
	    }
	    /*
	     * Here we used to sanity-check the group directive to
	     * ensure nobody tried to declare a group containing no
	     * segments. However, OS/2 does this as standard
	     * practice, so the sanity check has been removed.
	     *
	     * if (!*q) {
	     *     error(ERR_NONFATAL,"GROUP directive contains no segments");
	     *     return 1;
	     * }
	     */

	    obj_idx = 1;
	    for (grp = grphead; grp; grp = grp->next) {
		obj_idx++;
		if (!strcmp(grp->name, v)) {
		    error(ERR_NONFATAL, "group `%s' defined twice", v);
		    return 1;
		}
	    }

	    *grptail = grp = nasm_malloc(sizeof(*grp));
	    grp->next = NULL;
	    grptail = &grp->next;
	    grp->index = seg_alloc();
	    grp->obj_index = obj_idx;
	    grp->nindices = grp->nentries = 0;
	    grp->name = NULL;

	    obj_grp_needs_update = grp;
	    deflabel (v, grp->index+1, 0L,
		      NULL, FALSE, FALSE, &of_obj, error);
	    obj_grp_needs_update = NULL;

	    while (*q) {
		p = q;
		while (*q && !isspace(*q))
		    q++;
		if (isspace(*q)) {
		    *q++ = '\0';
		    while (*q && isspace(*q))
			q++;
		}
		/*
		 * Now p contains a segment name. Find it.
		 */
		for (seg = seghead; seg; seg = seg->next)
		    if (!strcmp(seg->name, p))
			break;
		if (seg) {
		    /*
		     * We have a segment index. Shift a name entry
		     * to the end of the array to make room.
		     */
		    grp->segs[grp->nentries++] = grp->segs[grp->nindices];
		    grp->segs[grp->nindices++].index = seg->obj_index;
		    if (seg->grp)
			error(ERR_WARNING, "segment `%s' is already part of"
			      " a group: first one takes precedence",
			      seg->name);
		    else
			seg->grp = grp;
		} else {
		    /*
		     * We have an as-yet undefined segment.
		     * Remember its name, for later.
		     */
		    grp->segs[grp->nentries++].name = nasm_strdup(p);
		}
	    }

	    /*
	     * Walk through the list of externals with unresolved
	     * default-WRT clauses, and resolve any that point at
	     * this group.
	     */
	    extp = &dws;
	    while (*extp) {
		if ((*extp)->defwrt_type == DEFWRT_STRING &&
		    !strcmp((*extp)->defwrt_ptr.string, grp->name)) {
		    (*extp)->defwrt_type = DEFWRT_GROUP;
		    (*extp)->defwrt_ptr.grp = grp;
		    *extp = (*extp)->next_dws;
	    } else
		    extp = &(*extp)->next_dws;
	    }
	}
	return 1;
    }
    if (!strcmp(directive, "uppercase")) {
	obj_uppercase = TRUE;
	return 1;
    }
    if (!strcmp(directive, "import")) {
	char *q, *extname, *libname, *impname;

	if (pass == 2)
	    return 1;		       /* ignore in pass two */
	extname = q = value;
	while (*q && !isspace(*q))
	    q++;
	if (isspace(*q)) {
	    *q++ = '\0';
	    while (*q && isspace(*q))
		q++;
	}

	libname = q;
	while (*q && !isspace(*q))
	    q++;
	if (isspace(*q)) {
	    *q++ = '\0';
	    while (*q && isspace(*q))
		q++;
	}

	impname = q;

	if (!*extname || !*libname)
	    error(ERR_NONFATAL, "`import' directive requires symbol name"
		  " and library name");
	else {
	    struct ImpDef *imp;
	    int err = FALSE;

	    imp = *imptail = nasm_malloc(sizeof(struct ImpDef));
	    imptail = &imp->next;
	    imp->next = NULL;
	    imp->extname = nasm_strdup(extname);
	    imp->libname = nasm_strdup(libname);
	    imp->impindex = readnum(impname, &err);
	    if (!*impname || err)
		imp->impname = nasm_strdup(impname);
	    else
		imp->impname = NULL;
	}

	return 1;
    }
    if (!strcmp(directive, "export")) {
	char *q, *extname, *intname, *v;
	struct ExpDef *export;
	int flags = 0;
	unsigned int ordinal = 0;

	if (pass == 2)
	    return 1;		       /* ignore in pass two */
	intname = q = value;
	while (*q && !isspace(*q))
	    q++;
	if (isspace(*q)) {
	    *q++ = '\0';
	    while (*q && isspace(*q))
		q++;
	}

	extname = q;
	while (*q && !isspace(*q))
	    q++;
	if (isspace(*q)) {
	    *q++ = '\0';
	    while (*q && isspace(*q))
		q++;
	}

	if (!*intname) {
	    error(ERR_NONFATAL, "`export' directive requires export name");
	    return 1;
	}
	if (!*extname) {
	    extname = intname;
	    intname = "";
	}
	while (*q) {
	    v = q;
	    while (*q && !isspace(*q))
		q++;
	    if (isspace(*q)) {
		*q++ = '\0';
		while (*q && isspace(*q))
		    q++;
	    }
	    if (!nasm_stricmp(v, "resident"))
		flags |= EXPDEF_FLAG_RESIDENT;
	    else if (!nasm_stricmp(v, "nodata"))
		flags |= EXPDEF_FLAG_NODATA;
	    else if (!nasm_strnicmp(v, "parm=", 5)) {
		int err = FALSE;
		flags |= EXPDEF_MASK_PARMCNT & readnum(v+5, &err);
		if (err) {
		    error(ERR_NONFATAL,
			  "value `%s' for `parm' is non-numeric", v+5);
		    return 1;
		}
	    } else {
		int err = FALSE;
		ordinal = readnum(v, &err);
		if (err) {
		    error(ERR_NONFATAL, "unrecognised export qualifier `%s'",
			  v);
		    return 1;
		}
		flags |= EXPDEF_FLAG_ORDINAL;
	    }
	}

	export = *exptail = nasm_malloc(sizeof(struct ExpDef));
	exptail = &export->next;
	export->next = NULL;
	export->extname = nasm_strdup(extname);
	export->intname = nasm_strdup(intname);
	export->ordinal = ordinal;
	export->flags = flags;

	return 1;
    }
    return 0;
}

static long obj_segbase (long segment) {
    struct Segment *seg;

    /*
     * Find the segment in our list.
     */
    for (seg = seghead; seg; seg = seg->next)
	if (seg->index == segment-1)
	    break;

    if (!seg) {
	/*
	 * Might be an external with a default WRT.
	 */
	long i = segment/2;
	struct ExtBack *eb = ebhead;
	struct External *e;

	while (i > EXT_BLKSIZ) {
	    if (eb)
		eb = eb->next;
	    else
		break;
	    i -= EXT_BLKSIZ;
	}
	if (eb) {
	    e = eb->exts[i];
	    if (e->defwrt_type == DEFWRT_NONE)
		return segment;	       /* fine */
	    else if (e->defwrt_type == DEFWRT_SEGMENT)
		return e->defwrt_ptr.seg->index+1;
	    else if (e->defwrt_type == DEFWRT_GROUP)
		return e->defwrt_ptr.grp->index+1;
	    else if (e->defwrt_type == DEFWRT_STRING)
		return NO_SEG;	       /* can't tell what it is */
	}

	return segment;		       /* not one of ours - leave it alone */
    }

    if (seg->align >= SEG_ABS)
	return seg->align;	       /* absolute segment */
    if (seg->grp)
	return seg->grp->index+1;      /* grouped segment */

    return segment;		       /* no special treatment */
}

static void obj_filename (char *inname, char *outname, efunc error) {
    strcpy(obj_infile, inname);
    standard_extension (inname, outname, ".obj", error);
}

static void obj_write_file (void) {
    struct Segment *seg;
    struct Group *grp;
    struct Public *pub;
    struct External *ext;
    struct ObjData *data;
    struct ImpDef *imp;
    struct ExpDef *export;
    static char boast[] = "The Netwide Assembler " NASM_VER;
    int lname_idx, rectype;

    /*
     * Write the THEADR module header.
     */
    recptr = record;
    recptr = obj_write_name (recptr, obj_infile);
    obj_record (THEADR, record, recptr);

    /*
     * Write the NASM boast comment.
     */
    recptr = record;
    recptr = obj_write_rword (recptr, 0);   /* comment type zero */
    recptr = obj_write_name (recptr, boast);
    obj_record (COMENT, record, recptr);

    /*
     * Write the IMPDEF records, if any.
     */
    for (imp = imphead; imp; imp = imp->next) {
	recptr = record;
	recptr = obj_write_rword (recptr, 0xA0);   /* comment class A0 */
	recptr = obj_write_byte (recptr, 1);   /* subfunction 1: IMPDEF */
	if (imp->impname)
	    recptr = obj_write_byte (recptr, 0);   /* import by name */
	else
	    recptr = obj_write_byte (recptr, 1);   /* import by ordinal */
	recptr = obj_write_name (recptr, imp->extname);
	recptr = obj_write_name (recptr, imp->libname);
	if (imp->impname)
	    recptr = obj_write_name (recptr, imp->impname);
	else
	    recptr = obj_write_word (recptr, imp->impindex);
	obj_record (COMENT, record, recptr);
    }

    /*
     * Write the EXPDEF records, if any.
     */
    for (export = exphead; export; export = export->next) {
	recptr = record;
	recptr = obj_write_rword (recptr, 0xA0);   /* comment class A0 */
	recptr = obj_write_byte (recptr, 2);   /* subfunction 1: EXPDEF */
	recptr = obj_write_byte (recptr, export->flags);
	recptr = obj_write_name (recptr, export->extname);
	recptr = obj_write_name (recptr, export->intname);
	if (export->flags & EXPDEF_FLAG_ORDINAL)
	    recptr = obj_write_word (recptr, export->ordinal);
	obj_record (COMENT, record, recptr);
    }

    /*
     * Write the first LNAMES record, containing LNAME one, which
     * is null. Also initialise the LNAME counter.
     */
    recptr = record;
    recptr = obj_write_name (recptr, "");
    obj_record (LNAMES, record, recptr);
    lname_idx = 2;

    /*
     * Write the SEGDEF records. Each has an associated LNAMES
     * record.
     */
    for (seg = seghead; seg; seg = seg->next) {
	int new_segdef;		       /* do we use the newer record type? */
	int acbp;
	int sn, cn, on;		       /* seg, class, overlay LNAME idx */

	if (seg->use32 || seg->currentpos >= 0x10000L)
	    new_segdef = TRUE;
	else
	    new_segdef = FALSE;

	recptr = record;
	recptr = obj_write_name (recptr, seg->name);
	sn = lname_idx++;
	if (seg->segclass) {
	    recptr = obj_write_name (recptr, seg->segclass);
	    cn = lname_idx++;
	} else
	    cn = 1;
	if (seg->overlay) {
	    recptr = obj_write_name (recptr, seg->overlay);
	    on = lname_idx++;
	} else
	    on = 1;
	obj_record (LNAMES, record, recptr);

	acbp = (seg->combine << 2);    /* C field */

	if (seg->currentpos >= 0x10000L && !new_segdef)
	    acbp |= 0x02;	       /* B bit */

	if (seg->use32)
	    acbp |= 0x01;	       /* P bit is Use32 flag */

	/* A field */
	if (seg->align >= SEG_ABS)
	    acbp |= 0x00;
	else if (seg->align >= 4096) {
	    if (seg->align > 4096)
		error(ERR_NONFATAL, "segment `%s' requires more alignment"
		      " than OBJ format supports", seg->name);
	    acbp |= 0xC0;	       /* PharLap extension */
	} else if (seg->align >= 256) {
	    acbp |= 0x80;
	} else if (seg->align >= 16) {
	    acbp |= 0x60;
	} else if (seg->align >= 4) {
	    acbp |= 0xA0;
	} else if (seg->align >= 2) {
	    acbp |= 0x40;
	} else
	    acbp |= 0x20;

	recptr = record;
	recptr = obj_write_byte (recptr, acbp);
	if (seg->align & SEG_ABS) {
	    recptr = obj_write_word (recptr, seg->align - SEG_ABS);
	    recptr = obj_write_byte (recptr, 0);
	}
	if (new_segdef)
	    recptr = obj_write_dword (recptr, seg->currentpos);
	else
	    recptr = obj_write_word (recptr, seg->currentpos & 0xFFFF);
	recptr = obj_write_index (recptr, sn);
	recptr = obj_write_index (recptr, cn);
	recptr = obj_write_index (recptr, on);
	if (new_segdef)
	    obj_record (SEGDEF+1, record, recptr);
	else
	    obj_record (SEGDEF, record, recptr);
    }

    /*
     * Write some LNAMES for the group names. lname_idx is left
     * alone here - it will catch up when we write the GRPDEFs.
     */
    recptr = record;
    for (grp = grphead; grp; grp = grp->next) {
	if (recptr - record + strlen(grp->name)+2 > 1024) {
	    obj_record (LNAMES, record, recptr);
	    recptr = record;
	}
	recptr = obj_write_name (recptr, grp->name);
    }
    if (recptr > record)
	obj_record (LNAMES, record, recptr);

    /*
     * Write the GRPDEF records.
     */
    for (grp = grphead; grp; grp = grp->next) {
	int i;

	if (grp->nindices != grp->nentries) {
	    for (i = grp->nindices; i < grp->nentries; i++) {
		error(ERR_NONFATAL, "group `%s' contains undefined segment"
		      " `%s'", grp->name, grp->segs[i].name);
		nasm_free (grp->segs[i].name);
		grp->segs[i].name = NULL;
	    }
	}
	recptr = record;
	recptr = obj_write_index (recptr, lname_idx++);
	for (i = 0; i < grp->nindices; i++) {
	    recptr = obj_write_byte (recptr, 0xFF);
	    recptr = obj_write_index (recptr, grp->segs[i].index);
	}
	obj_record (GRPDEF, record, recptr);
    }

    /*
     * Write the PUBDEF records: first the ones in the segments,
     * then the far-absolutes.
     */
    for (seg = seghead; seg; seg = seg->next) {
	int any;

	recptr = record;
	recptr = obj_write_index (recptr, seg->grp ? seg->grp->obj_index : 0);
	recptr = obj_write_index (recptr, seg->obj_index);
	any = FALSE;
	if (seg->use32)
	    rectype = PUBDEF+1;
	else
	    rectype = PUBDEF;
	for (pub = seg->pubhead; pub; pub = pub->next) {
	    if (recptr - record + strlen(pub->name) + 7 > 1024) {
		if (any)
		    obj_record (rectype, record, recptr);
		recptr = record;
		recptr = obj_write_index (recptr, 0);
		recptr = obj_write_index (recptr, seg->obj_index);
	    }
	    recptr = obj_write_name (recptr, pub->name);
	    if (seg->use32)
		recptr = obj_write_dword (recptr, pub->offset);
	    else
		recptr = obj_write_word (recptr, pub->offset);
	    recptr = obj_write_index (recptr, 0);
	    any = TRUE;
	}
	if (any)
	    obj_record (rectype, record, recptr);
    }
    for (pub = fpubhead; pub; pub = pub->next) {   /* pub-crawl :-) */
	recptr = record;
	recptr = obj_write_index (recptr, 0);   /* no group */
	recptr = obj_write_index (recptr, 0);   /* no segment either */
	recptr = obj_write_word (recptr, pub->segment);
	recptr = obj_write_name (recptr, pub->name);
	recptr = obj_write_word (recptr, pub->offset);
	recptr = obj_write_index (recptr, 0);
	obj_record (PUBDEF, record, recptr);
    }

    /*
     * Write the EXTDEF and COMDEF records, in order.
     */
    recptr = record;
    for (ext = exthead; ext; ext = ext->next) {
	if (ext->commonsize == 0) {
	    /* dj@delorie.com: check for buffer overrun before we overrun it */
	    if (recptr - record + strlen(ext->name)+2 > RECORD_MAX) {
		obj_record (EXTDEF, record, recptr);
		recptr = record;
	    }
	    recptr = obj_write_name (recptr, ext->name);
	    recptr = obj_write_index (recptr, 0);
	} else {
	    if (recptr > record)
		obj_record (EXTDEF, record, recptr);
	    recptr = record;
	    if (ext->commonsize) {
		recptr = obj_write_name (recptr, ext->name);
		recptr = obj_write_index (recptr, 0);
		if (ext->commonelem) {
		    recptr = obj_write_byte (recptr, 0x61);/* far communal */
		    recptr = obj_write_value (recptr, (ext->commonsize /
						       ext->commonelem));
		    recptr = obj_write_value (recptr, ext->commonelem);
		} else {
		    recptr = obj_write_byte (recptr, 0x62);/* near communal */
		    recptr = obj_write_value (recptr, ext->commonsize);
		}
		obj_record (COMDEF, record, recptr);
	    }
	    recptr = record;
	}
    }
    if (recptr > record)
	obj_record (EXTDEF, record, recptr);

    /*
     * Write a COMENT record stating that the linker's first pass
     * may stop processing at this point. Exception is if our
     * MODEND record specifies a start point, in which case,
     * according to some variants of the documentation, this COMENT
     * should be omitted. So we'll omit it just in case.
     */
    if (obj_entry_seg == NO_SEG) {
	recptr = record;
	recptr = obj_write_rword (recptr, 0x40A2);
	recptr = obj_write_byte (recptr, 1);
	obj_record (COMENT, record, recptr);
    }

    /*
     * Write the LEDATA/FIXUPP pairs.
     */
    for (data = datahead; data; data = data->next) {
	if (data->nonempty) {
	    obj_record (data->letype, data->ledata, data->lptr);
	    if (data->fptr != data->fixupp)
		obj_record (data->ftype, data->fixupp, data->fptr);
	}
    }

    /*
     * Write the MODEND module end marker.
     */
    recptr = record;
    rectype = MODEND;
    if (obj_entry_seg != NO_SEG) {
	recptr = obj_write_byte (recptr, 0xC1);
	/*
	 * Find the segment in the segment list.
	 */
	for (seg = seghead; seg; seg = seg->next) {
	    if (seg->index == obj_entry_seg) {
		if (seg->grp) {
		    recptr = obj_write_byte (recptr, 0x10);
		    recptr = obj_write_index (recptr, seg->grp->obj_index);
		} else {
		    recptr = obj_write_byte (recptr, 0x50);
		}
		recptr = obj_write_index (recptr, seg->obj_index);
		if (seg->use32) {
		    rectype = MODEND+1;
		    recptr = obj_write_dword (recptr, obj_entry_ofs);
		} else
		    recptr = obj_write_word (recptr, obj_entry_ofs);
		break;
	    }
	}
	if (!seg)
	    error(ERR_NONFATAL, "entry point is not in this module");
    } else
	recptr = obj_write_byte (recptr, 0);
    obj_record (rectype, record, recptr);
}

static unsigned char *obj_write_data(unsigned char *ptr,
				     unsigned char *data, int len) {
    while (len--)
	*ptr++ = *data++;
    return ptr;
}

static unsigned char *obj_write_byte(unsigned char *ptr, int data) {
    *ptr++ = data;
    return ptr;
}

static unsigned char *obj_write_word(unsigned char *ptr, int data) {
    *ptr++ = data & 0xFF;
    *ptr++ = (data >> 8) & 0xFF;
    return ptr;
}

static unsigned char *obj_write_dword(unsigned char *ptr, long data) {
    *ptr++ = data & 0xFF;
    *ptr++ = (data >> 8) & 0xFF;
    *ptr++ = (data >> 16) & 0xFF;
    *ptr++ = (data >> 24) & 0xFF;
    return ptr;
}

static unsigned char *obj_write_rword(unsigned char *ptr, int data) {
    *ptr++ = (data >> 8) & 0xFF;
    *ptr++ = data & 0xFF;
    return ptr;
}

static unsigned char *obj_write_name(unsigned char *ptr, char *data) {
    *ptr++ = strlen(data);
    if (obj_uppercase) {
	while (*data) {
	    *ptr++ = (unsigned char) toupper(*data);
	    data++;
	}
    } else {
	while (*data)
	    *ptr++ = (unsigned char) *data++;
    }
    return ptr;
}

static unsigned char *obj_write_index(unsigned char *ptr, int data) {
    if (data < 128)
	*ptr++ = data;
    else {
	*ptr++ = 0x80 | ((data >> 8) & 0x7F);
	*ptr++ = data & 0xFF;
    }
    return ptr;
}

static unsigned char *obj_write_value(unsigned char *ptr,
				      unsigned long data) {
    if (data <= 128)
	*ptr++ = data;
    else if (data <= 0xFFFF) {
	*ptr++ = 129;
	*ptr++ = data & 0xFF;
	*ptr++ = (data >> 8) & 0xFF;
    } else if (data <= 0xFFFFFFL) {
	*ptr++ = 132;
	*ptr++ = data & 0xFF;
	*ptr++ = (data >> 8) & 0xFF;
	*ptr++ = (data >> 16) & 0xFF;
    } else {
	*ptr++ = 136;
	*ptr++ = data & 0xFF;
	*ptr++ = (data >> 8) & 0xFF;
	*ptr++ = (data >> 16) & 0xFF;
	*ptr++ = (data >> 24) & 0xFF;
    }
    return ptr;
}

static void obj_record(int type, unsigned char *start, unsigned char *end) {
    unsigned long cksum, len;

    cksum = type;
    fputc (type, ofp);
    len = end-start+1;
    cksum += (len & 0xFF) + ((len>>8) & 0xFF);
    fwriteshort (len, ofp);
    fwrite (start, 1, end-start, ofp);
    while (start < end)
	cksum += *start++;
    fputc ( (-(long)cksum) & 0xFF, ofp);
}

static char *obj_stdmac[] = {
    "%define __SECT__ [section .text]",
    "%imacro group 1+.nolist",
    "[group %1]",
    "%endmacro",
    "%imacro uppercase 1+.nolist",
    "[uppercase %1]",
    "%endmacro",
    "%imacro export 1+.nolist",
    "[export %1]",
    "%endmacro",
    "%imacro import 1+.nolist",
    "[import %1]",
    "%endmacro",
    NULL
};

struct ofmt of_obj = {
    "Microsoft MS-DOS 16-bit OMF object files",
    "obj",
    obj_stdmac,
    obj_init,
    obj_out,
    obj_deflabel,
    obj_segment,
    obj_segbase,
    obj_directive,
    obj_filename,
    obj_cleanup
};
#endif /* OF_OBJ */
