/*****************************************************************************/
/*                                                                           */
/*				     o65.c				     */
/*                                                                           */
/*		    Module to handle the o65 binary format		     */
/*                                                                           */
/*                                                                           */
/*                                                                           */
/* (C) 1999-2009, Ullrich von Bassewitz                                      */
/*                Roemerstrasse 52                                           */
/*                D-70794 Filderstadt                                        */
/* EMail:         uz@cc65.org                                                */
/*                                                                           */
/*                                                                           */
/* This software is provided 'as-is', without any expressed or implied       */
/* warranty.  In no event will the authors be held liable for any damages    */
/* arising from the use of this software.                                    */
/*                                                                           */
/* Permission is granted to anyone to use this software for any purpose,     */
/* including commercial applications, and to alter it and redistribute it    */
/* freely, subject to the following restrictions:                            */
/*                                                                           */
/* 1. The origin of this software must not be misrepresented; you must not   */
/*    claim that you wrote the original software. If you use this software   */
/*    in a product, an acknowledgment in the product documentation would be  */
/*    appreciated but is not required.                                       */
/* 2. Altered source versions must be plainly marked as such, and must not   */
/*    be misrepresented as being the original software.                      */
/* 3. This notice may not be removed or altered from any source              */
/*    distribution.                                                          */
/*                                                                           */
/*****************************************************************************/



#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <time.h>

/* common */
#include "chartype.h"
#include "check.h"
#include "print.h"
#include "version.h"
#include "xmalloc.h"

/* ld65 */
#include "error.h"
#include "exports.h"
#include "expr.h"
#include "fileio.h"
#include "global.h"
#include "lineinfo.h"
#include "o65.h"
#include "spool.h"



/*****************************************************************************/
/*     	       	    	      	     Data				     */
/*****************************************************************************/



/* Header mode bits */
#define MF_CPU_65816    0x8000		/* Executable is for 65816 */
#define MF_CPU_6502     0x0000          /* Executable is for the 6502 */
#define MF_CPU_MASK     0x8000          /* Mask to extract CPU type */

#define MF_RELOC_PAGE   0x4000          /* Page wise relocation */
#define MF_RELOC_BYTE   0x0000          /* Byte wise relocation */
#define MF_RELOC_MASK   0x4000          /* Mask to extract relocation type */

#define MF_SIZE_32BIT	0x2000		/* All size words are 32bit */
#define MF_SIZE_16BIT   0x0000          /* All size words are 16bit */
#define MF_SIZE_MASK    0x2000          /* Mask to extract size */

#define MF_FTYPE_OBJ    0x1000          /* Object file */
#define MF_FTYPE_EXE    0x0000          /* Executable file */
#define MF_FTYPE_MASK   0x1000          /* Mask to extract type */

#define MF_ADDR_SIMPLE  0x0800          /* Simple addressing */
#define MF_ADDR_DEFAULT 0x0000          /* Default addressing */
#define MF_ADDR_MASK    0x0800          /* Mask to extract addressing */

#define MF_ALIGN_1      0x0000          /* Bytewise alignment */
#define MF_ALIGN_2      0x0001          /* Align words */
#define MF_ALIGN_4      0x0002          /* Align longwords */
#define MF_ALIGN_256    0x0003          /* Align pages (256 bytes) */
#define MF_ALIGN_MASK   0x0003 	       	/* Mask to extract alignment */

/* The four o65 segment types. Note: These values are identical to the values
 * needed for the segmentID in the o65 spec.
 */
#define O65SEG_UNDEF	0x00
#define O65SEG_ABS	0x01
#define O65SEG_TEXT	0x02
#define O65SEG_DATA	0x03
#define O65SEG_BSS	0x04
#define O65SEG_ZP 	0x05

/* Relocation type codes for the o65 format */
#define O65RELOC_WORD	0x80
#define O65RELOC_HIGH	0x40
#define O65RELOC_LOW	0x20
#define O65RELOC_SEGADR	0xC0
#define O65RELOC_SEG	0xA0
#define O65RELOC_MASK   0xE0

/* O65 executable file header */
typedef struct O65Header O65Header;
struct O65Header {
    unsigned	    Version;	    	/* Version number for o65 format */
    unsigned        Mode;      	    	/* Mode word */
    unsigned long   TextBase;		/* Base address of text segment */
    unsigned long   TextSize;		/* Size of text segment */
    unsigned long   DataBase;		/* Base of data segment */
    unsigned long   DataSize;		/* Size of data segment */
    unsigned long   BssBase;		/* Base of bss segment */
    unsigned long   BssSize;		/* Size of bss segment */
    unsigned long   ZPBase;		/* Base of zeropage segment */
    unsigned long   ZPSize;		/* Size of zeropage segment */
    unsigned long   StackSize;		/* Requested stack size */
};

/* An o65 option */
typedef struct O65Option O65Option;
struct O65Option {
    O65Option*	    Next;		/* Next in option list */
    unsigned char   Type;	       	/* Type of option */
    unsigned char   Len;	       	/* Data length */
    unsigned char   Data [1];	       	/* Data, dynamically allocated */
};

/* A o65 relocation table */
typedef struct O65RelocTab O65RelocTab;
struct O65RelocTab {
    unsigned 	    Size;   	       	/* Size of the table */
    unsigned	    Fill;   	       	/* Amount used */
    unsigned char*  Buf; 	       	/* Buffer, dynamically allocated */
};

/* Structure describing the format */
struct O65Desc {
    O65Header 	    Header; 	       	/* File header */
    O65Option*	    Options;	       	/* List of file options */
    ExtSymTab*	    Exports;	       	/* Table with exported symbols */
    ExtSymTab*	    Imports;	       	/* Table with imported symbols */
    unsigned   	    Undef;	       	/* Count of undefined symbols */
    FILE*     	    F;		       	/* The file we're writing to */
    const char*     Filename;	       	/* Name of the output file */
    O65RelocTab*    TextReloc;	       	/* Relocation table for text segment */
    O65RelocTab*    DataReloc;	       	/* Relocation table for data segment */

    unsigned  	    TextCount;	       	/* Number of segments assigned to .text */
    SegDesc** 	    TextSeg;	       	/* Array of text segments */
    unsigned  	    DataCount;	       	/* Number of segments assigned to .data */
    SegDesc** 	    DataSeg;	       	/* Array of data segments */
    unsigned  	    BssCount;	       	/* Number of segments assigned to .bss */
    SegDesc** 	    BssSeg;    	       	/* Array of bss segments */
    unsigned  	    ZPCount;	       	/* Number of segments assigned to .zp */
    SegDesc** 	    ZPSeg;	       	/* Array of zp segments */

    /* Temporary data for writing segments */
    unsigned long   SegSize;
    O65RelocTab*    CurReloc;
    long      	    LastOffs;
};

/* Structure for parsing expression trees */
typedef struct ExprDesc ExprDesc;
struct ExprDesc {
    O65Desc*   	    D;			/* File format descriptor */
    long       	    Val;		/* The offset value */
    int	       	    TooComplex;	       	/* Expression too complex */
    Memory*         MemRef;             /* Memory reference if any */
    Segment*        SegRef;             /* Segment reference if any */
    Section*   	    SecRef;		/* Section reference if any */
    ExtSym*    	    ExtRef;		/* External reference if any */
};



/*****************************************************************************/
/*  	       	    	       Helper functions	     			     */
/*****************************************************************************/



static ExprDesc* InitExprDesc (ExprDesc* ED, O65Desc* D)
/* Initialize an ExprDesc structure for use with O65ParseExpr */
{
    ED->D    	   = D;
    ED->Val  	   = 0;
    ED->TooComplex = 0;
    ED->MemRef     = 0;
    ED->SegRef     = 0;
    ED->SecRef     = 0;
    ED->ExtRef     = 0;
    return ED;
}



static void WriteSize (const O65Desc* D, unsigned long Val)
/* Write a "size" word to the file */
{
    switch (D->Header.Mode & MF_SIZE_MASK) {
	case MF_SIZE_16BIT:     Write16 (D->F, (unsigned) Val); break;
	case MF_SIZE_32BIT:     Write32 (D->F, Val);            break;
    	default:                Internal ("Invalid size in header: %04X", D->Header.Mode);
    }
}



static unsigned O65SegType (const SegDesc* S)
/* Map our own segment types into something o65 compatible */
{
    /* Check the segment type. Readonly segments are assign to the o65
     * text segment, writeable segments that contain data are assigned
     * to data, bss and zp segments are handled respectively.
     * Beware: Zeropage segments have the SF_BSS flag set, so be sure
     * to check SF_ZP first.
     */
    if (S->Flags & SF_RO) {
    	return O65SEG_TEXT;
    } else if (S->Flags & SF_ZP) {
    	return O65SEG_ZP;
    } else if (S->Flags & SF_BSS) {
    	return O65SEG_BSS;
    } else {
    	return O65SEG_DATA;
    }
}



static void CvtMemoryToSegment (ExprDesc* ED)
/* Convert a memory area into a segment by searching the list of run segments
 * in this memory area and assigning the nearest one.
 */
{
    /* Get the memory area from the expression */
    Memory* M = ED->MemRef;

    /* Get the list of segments in this memory area */
    MemListNode* N = M->SegList;

    /* Remember the "nearest" segment and its offset */
    Segment* Nearest   = 0;
    unsigned long Offs = ULONG_MAX;

    /* Walk over all segments */
    while (N != 0) {

        /* Get the segment from this node and check if it's a run segment */
        SegDesc* S = N->Seg;
        if (S->Run == M) {

            unsigned long O;

            /* Get the segment from the segment descriptor */
            Segment* Seg = S->Seg;

            /* Check the PC. */
            if ((long) Seg->PC <= ED->Val && (O = (ED->Val - Seg->PC)) < Offs) {
                /* This is the nearest segment for now */
                Offs = O;
                Nearest = Seg;

                /* If we found an exact match, don't look further */
                if (Offs == 0) {
                    break;
                }
            }
        }

        /* Next segment */
        N = N->Next;
    }

    /* If we found a segment, use it and adjust the offset */
    if (Nearest) {
        ED->SegRef = Nearest;
        ED->MemRef = 0;
        ED->Val    -= Nearest->PC;
    }
}



static const SegDesc* FindSeg (SegDesc** const List, unsigned Count, const Segment* S)
/* Search for a segment in the given list of segment descriptors and return
 * the descriptor for a segment if we found it, and NULL if not.
 */
{
    unsigned I;

    for (I = 0; I < Count; ++I) {
     	if (List[I]->Seg == S) {
	    /* Found */
     	    return List[I];
     	}
    }

    /* Not found */
    return 0;
}



static const SegDesc* O65FindSeg (const O65Desc* D, const Segment* S)
/* Search for a segment in the segment lists and return it's segment descriptor */
{
    const SegDesc* SD;

    if ((SD = FindSeg (D->TextSeg, D->TextCount, S)) != 0) {
     	return SD;
    }
    if ((SD = FindSeg (D->DataSeg, D->DataCount, S)) != 0) {
     	return SD;
    }
    if ((SD = FindSeg (D->BssSeg, D->BssCount, S)) != 0) {
     	return SD;
    }
    if ((SD = FindSeg (D->ZPSeg, D->ZPCount, S)) != 0) {
     	return SD;
    }

    /* Not found */
    return 0;
}



/*****************************************************************************/
/*  	 	       	      Expression handling			     */
/*****************************************************************************/



static void O65ParseExpr (ExprNode* Expr, ExprDesc* D, int Sign)
/* Extract and evaluate all constant factors in an subtree that has only
 * additions and subtractions. If anything other than additions and
 * subtractions are found, D->TooComplex is set to true.
 */
{
    Export* E;

    switch (Expr->Op) {

	case EXPR_LITERAL:
            D->Val += (Sign * Expr->V.IVal);
	    break;

	case EXPR_SYMBOL:
	    /* Get the referenced Export */
	    E = GetExprExport (Expr);
    	    /* If this export has a mark set, we've already encountered it.
    	     * This means that the export is used to define it's own value,
    	     * which in turn means, that we have a circular reference.
    	     */
    	    if (ExportHasMark (E)) {
    		CircularRefError (E);
    	    } else if (E->Expr == 0) {
    		/* Dummy export, must be an o65 imported symbol */
    		ExtSym* S = O65GetImport (D->D, E->Name);
    		CHECK (S != 0);
    		if (D->ExtRef) {
    		    /* We cannot have more than one external reference in o65 */
    		    D->TooComplex = 1;
    		} else {
    		    /* Remember the external reference */
    		    D->ExtRef = S;
    		}
    	    } else {
    		MarkExport (E);
    	      	O65ParseExpr (E->Expr, D, Sign);
    		UnmarkExport (E);
    	    }
    	    break;

    	case EXPR_SECTION:
    	    if (D->SecRef) {
    	        /* We cannot handle more than one segment reference in o65 */
    		D->TooComplex = 1;
    	    } else {
    	 	/* Remember the segment reference */
    		D->SecRef = GetExprSection (Expr);
                /* Add the offset of the section to the constant value */
                D->Val += Sign * (D->SecRef->Offs + D->SecRef->Seg->PC);
    	    }
    	    break;

        case EXPR_SEGMENT:
    	    if (D->SegRef) {
    	        /* We cannot handle more than one segment reference in o65 */
    		D->TooComplex = 1;
    	    } else {
    	 	/* Remember the segment reference */
       	       	D->SegRef = Expr->V.Seg;
                /* Add the offset of the segment to the constant value */
                D->Val += (Sign * D->SegRef->PC);
    	    }
    	    break;

        case EXPR_MEMAREA:
    	    if (D->MemRef) {
    	        /* We cannot handle more than one memory reference in o65 */
    		D->TooComplex = 1;
    	    } else {
    	 	/* Remember the memory area reference */
       	       	D->MemRef = Expr->V.Mem;
                /* Add the start address of the memory area to the constant
                 * value
                 */
                D->Val += (Sign * D->MemRef->Start);
    	    }
    	    break;

    	case EXPR_PLUS:
    	    O65ParseExpr (Expr->Left, D, Sign);
      	    O65ParseExpr (Expr->Right, D, Sign);
    	    break;

    	case EXPR_MINUS:
    	    O65ParseExpr (Expr->Left, D, Sign);
    	    O65ParseExpr (Expr->Right, D, -Sign);
    	    break;

     	default:
    	    /* Expression contains illegal operators */
    	    D->TooComplex = 1;
    	    break;

    }
}



/*****************************************************************************/
/*  			       Relocation tables			     */
/*****************************************************************************/



static O65RelocTab* NewO65RelocTab (void)
/* Create a new relocation table */
{
    /* Allocate a new structure */
    O65RelocTab* R = xmalloc (sizeof (O65RelocTab));

    /* Initialize the data */
    R->Size = 0;
    R->Fill = 0;
    R->Buf  = 0;

    /* Return the created struct */
    return R;
}



static void FreeO65RelocTab (O65RelocTab* R)
/* Free a relocation table */
{
    xfree (R->Buf);
    xfree (R);
}



static void O65RelocPutByte (O65RelocTab* R, unsigned B)
/* Put the byte into the relocation table */
{
    /* Do we have enough space in the buffer? */
    if (R->Fill == R->Size) {
    	/* We need to grow the buffer */
        if (R->Size) {
            R->Size *= 2;
        } else {
            R->Size = 1024;     /* Initial size */
        }
        R->Buf = xrealloc (R->Buf, R->Size);
    }

    /* Put the byte into the buffer */
    R->Buf [R->Fill++] = (unsigned char) B;
}



static void O65RelocPutWord (O65RelocTab* R, unsigned W)
/* Put a word into the relocation table */
{
    O65RelocPutByte (R, W);
    O65RelocPutByte (R, W >> 8);
}



static void O65WriteReloc (O65RelocTab* R, FILE* F)
/* Write the relocation table to the given file */
{
    WriteData (F, R->Buf, R->Fill);
}



/*****************************************************************************/
/*  	       			Option handling				     */
/*****************************************************************************/



static O65Option* NewO65Option (unsigned Type, const void* Data, unsigned DataLen)
/* Allocate and initialize a new option struct */
{
    O65Option* O;

    /* Check the length */
    CHECK (DataLen <= 253);

    /* Allocate memory */
    O = xmalloc (sizeof (O65Option) - 1 + DataLen);

    /* Initialize the structure */
    O->Next    	= 0;
    O->Type    	= Type;
    O->Len     	= DataLen;
    memcpy (O->Data, Data, DataLen);

    /* Return the created struct */
    return O;
}



static void FreeO65Option (O65Option* O)
/* Free	an O65Option struct */
{
    xfree (O);
}



/*****************************************************************************/
/*	       	       Subroutines to write o65 sections	       	     */
/*****************************************************************************/



static void O65WriteHeader (O65Desc* D)
/* Write the header of the executable to the given file */
{
    static unsigned char Trailer [5] = {
       	0x01, 0x00, 0x6F, 0x36, 0x35
    };

    O65Option* O;

    /* Write the fixed header */
    WriteData (D->F, Trailer, sizeof (Trailer));
    Write8    (D->F, D->Header.Version);
    Write16   (D->F, D->Header.Mode);
    WriteSize (D, D->Header.TextBase);
    WriteSize (D, D->Header.TextSize);
    WriteSize (D, D->Header.DataBase);
    WriteSize (D, D->Header.DataSize);
    WriteSize (D, D->Header.BssBase);
    WriteSize (D, D->Header.BssSize);
    WriteSize (D, D->Header.ZPBase);
    WriteSize (D, D->Header.ZPSize);
    WriteSize (D, D->Header.StackSize);

    /* Write the options */
    O = D->Options;
    while (O) {
       	Write8 (D->F, O->Len + 2);		/* Account for len and type bytes */
	Write8 (D->F, O->Type);
	if (O->Len) {
	    WriteData (D->F, O->Data, O->Len);
	}
	O = O->Next;
    }

    /* Write the end-of-options byte */
    Write8 (D->F, 0);
}



static unsigned O65WriteExpr (ExprNode* E, int Signed, unsigned Size,
       	       	  	      unsigned long Offs, void* Data)
/* Called from SegWrite for an expression. Evaluate the expression, check the
 * range and write the expression value to the file, update the relocation
 * table.
 */
{
    long          Diff;
    unsigned      RefCount;
    long          BinVal;
    ExprNode*     Expr;
    ExprDesc      ED;
    unsigned char RelocType;

    /* Cast the Data pointer to its real type, an O65Desc */
    O65Desc* D = (O65Desc*) Data;

    /* Check for a constant expression */
    if (IsConstExpr (E)) {
       	/* Write out the constant expression */
       	return SegWriteConstExpr (((O65Desc*)Data)->F, E, Signed, Size);
    }

    /* We have a relocatable expression that needs a relocation table entry.
     * Calculate the number of bytes between this entry and the last one, and
     * setup all necessary intermediate bytes in the relocation table.
     */
    Offs += D->SegSize;	   	/* Calulate full offset */
    Diff = ((long) Offs) - D->LastOffs;
    while (Diff > 0xFE) {
       	O65RelocPutByte (D->CurReloc, 0xFF);
       	Diff -= 0xFE;
    }
    O65RelocPutByte (D->CurReloc, (unsigned char) Diff);

    /* Remember this offset for the next time */
    D->LastOffs = Offs;

    /* Determine the expression to relocate */
    Expr = E;
    if (E->Op == EXPR_BYTE0 || E->Op == EXPR_BYTE1 ||
       	E->Op == EXPR_BYTE2 || E->Op == EXPR_BYTE3 ||
       	E->Op == EXPR_WORD0 || E->Op == EXPR_WORD1) {
       	/* Use the real expression */
       	Expr = E->Left;
    }

    /* Recursively collect information about this expression */
    O65ParseExpr (Expr, InitExprDesc (&ED, D), 1);

    /* We cannot handle more than one external reference */
    RefCount = (ED.MemRef != 0) + (ED.SegRef != 0) +
               (ED.SecRef != 0) + (ED.ExtRef != 0);
    if (RefCount > 1) {
       	ED.TooComplex = 1;
    }

    /* If we have a memory area reference, we need to convert it into a
     * segment reference. If we cannot do that, we cannot handle the
     * expression.
     */
    if (ED.MemRef) {
        CvtMemoryToSegment (&ED);
        if (ED.SegRef == 0) {
            return SEG_EXPR_TOO_COMPLEX;
        }
    }

    /* Bail out if we cannot handle the expression */
    if (ED.TooComplex) {
       	return SEG_EXPR_TOO_COMPLEX;
    }

    /* Safety: Check that we have exactly one reference */
    CHECK (RefCount == 1);

    /* Write out the offset that goes into the segment. */
    BinVal = ED.Val;
    switch (E->Op) {
       	case EXPR_BYTE0:    BinVal &= 0xFF;			break;
       	case EXPR_BYTE1:    BinVal = (BinVal >>  8) & 0xFF;	break;
       	case EXPR_BYTE2:    BinVal = (BinVal >> 16) & 0xFF;	break;
       	case EXPR_BYTE3:    BinVal = (BinVal >> 24) & 0xFF;	break;
       	case EXPR_WORD0:    BinVal &= 0xFFFF;			break;
       	case EXPR_WORD1:    BinVal = (BinVal >> 16) & 0xFFFF;	break;
    }
    WriteVal (D->F, BinVal, Size);

    /* Determine the actual type of relocation entry needed from the
     * information gathered about the expression.
     */
    if (E->Op == EXPR_BYTE0) {
       	RelocType = O65RELOC_LOW;
    } else if (E->Op == EXPR_BYTE1) {
       	RelocType = O65RELOC_HIGH;
    } else if (E->Op == EXPR_BYTE2) {
       	RelocType = O65RELOC_SEG;
    } else {
       	switch (Size) {

       	    case 1:
       	   	RelocType = O65RELOC_LOW;
       	   	break;

       	    case 2:
       	       	RelocType = O65RELOC_WORD;
       	   	break;

       	    case 3:
       	   	RelocType = O65RELOC_SEGADR;
       	   	break;

       	    case 4:
       	   	/* 4 byte expression not supported by o65 */
       	   	return SEG_EXPR_TOO_COMPLEX;

       	    default:
       	   	Internal ("O65WriteExpr: Invalid expression size: %u", Size);
       	   	RelocType = 0;	  	/* Avoid gcc warnings */
       	}
    }

    /* Determine which segment we're referencing */
    if (ED.SegRef || ED.SecRef) {

        const SegDesc* Seg;

        /* Segment or section reference. */
        if (ED.SecRef) {
            /* Get segment from section */
            ED.SegRef = ED.SecRef->Seg;
        }

       	/* Search for the segment and map it to it's o65 segmentID */
       	Seg = O65FindSeg (D, ED.SegRef);
    	if (Seg == 0) {
    	    /* For some reason, we didn't find this segment in the list of
    	     * segments written to the o65 file.
    	     */
    	    return SEG_EXPR_INVALID;
    	}
    	RelocType |= O65SegType (Seg);
    	O65RelocPutByte (D->CurReloc, RelocType);

    	/* Output additional data if needed */
    	switch (RelocType & O65RELOC_MASK) {
    	    case O65RELOC_HIGH:
    	        O65RelocPutByte (D->CurReloc, ED.Val & 0xFF);
    	        break;
    	    case O65RELOC_SEG:
    	        O65RelocPutWord (D->CurReloc, ED.Val & 0xFFFF);
    	        break;
    	}

    } else if (ED.ExtRef) {
       	/* Imported symbol */
       	RelocType |= O65SEG_UNDEF;
       	O65RelocPutByte (D->CurReloc, RelocType);
       	/* Put the number of the imported symbol into the table */
       	O65RelocPutWord (D->CurReloc, ExtSymNum (ED.ExtRef));

    } else {

        /* OOPS - something bad happened */
        Internal ("External reference not handled");

    }

    /* Success */
    return SEG_EXPR_OK;
}



static void O65WriteSeg (O65Desc* D, SegDesc** Seg, unsigned Count, int DoWrite)
/* Write one segment to the o65 output file */
{
    SegDesc* S;
    unsigned I;

    /* Initialize variables */
    D->SegSize  = 0;
    D->LastOffs = -1;

    /* Write out all segments */
    for (I = 0; I < Count; ++I) {

	/* Get the segment from the list node */
       	S = Seg [I];

	/* Keep the user happy */
	Print (stdout, 1, "    Writing `%s'\n", GetString (S->Name));

	/* Write this segment */
       	if (DoWrite) {
       	    RelocLineInfo (S->Seg);
       	    SegWrite (D->F, S->Seg, O65WriteExpr, D);
       	}

       	/* Mark the segment as dumped */
       	S->Seg->Dumped = 1;

       	/* Calculate the total size */
       	D->SegSize += S->Seg->Size;
    }

    /* Terminate the relocation table for this segment */
    if (D->CurReloc) {
        O65RelocPutByte (D->CurReloc, 0);
    }

    /* Check the size of the segment for overflow */
    if ((D->Header.Mode & MF_SIZE_MASK) == MF_SIZE_16BIT && D->SegSize > 0xFFFF) {
       	Error ("Segment overflow in file `%s'", D->Filename);
    }

}



static void O65WriteTextSeg (O65Desc* D)
/* Write the code segment to the o65 output file */
{
    /* Initialize variables */
    D->CurReloc	= D->TextReloc;

    /* Dump all text segments */
    O65WriteSeg (D, D->TextSeg, D->TextCount, 1);

    /* Set the size of the segment */
    D->Header.TextSize = D->SegSize;
}



static void O65WriteDataSeg (O65Desc* D)
/* Write the data segment to the o65 output file */
{
    /* Initialize variables */
    D->CurReloc	= D->DataReloc;

    /* Dump all data segments */
    O65WriteSeg (D, D->DataSeg, D->DataCount, 1);

    /* Set the size of the segment */
    D->Header.DataSize = D->SegSize;
}



static void O65WriteBssSeg (O65Desc* D)
/* "Write" the bss segments to the o65 output file. This will only update
 * the relevant header fields.
 */
{
    /* Initialize variables */
    D->CurReloc	= 0;

    /* Dump all data segments */
    O65WriteSeg (D, D->BssSeg, D->BssCount, 0);

    /* Set the size of the segment */
    D->Header.BssSize = D->SegSize;
}



static void O65WriteZPSeg (O65Desc* D)
/* "Write" the zeropage segments to the o65 output file. This will only update
 * the relevant header fields.
 */
{
    /* Initialize variables */
    D->CurReloc	= 0;

    /* Dump all data segments */
    O65WriteSeg (D, D->ZPSeg, D->ZPCount, 0);

    /* Set the size of the segment */
    D->Header.ZPSize = D->SegSize;
}



static void O65WriteImports (O65Desc* D)
/* Write the list of imported symbols to the O65 file */
{
    const ExtSym* S;

    /* Write the number of imports */
    WriteSize (D, ExtSymCount (D->Imports));

    /* Write out the symbol names, zero terminated */
    S = ExtSymList (D->Imports);
    while (S) {
     	/* Get the name */
     	const char* Name = GetString (ExtSymName (S));
     	/* And write it to the output file */
     	WriteData (D->F, Name, strlen (Name) + 1);
     	/* Next symbol */
     	S = ExtSymNext (S);
    }
}



static void O65WriteTextReloc (O65Desc* D)
/* Write the relocation for the text segment to the output file */
{
    O65WriteReloc (D->TextReloc, D->F);
}



static void O65WriteDataReloc (O65Desc* D)
/* Write the relocation for the data segment to the output file */
{
    O65WriteReloc (D->DataReloc, D->F);
}



static void O65WriteExports (O65Desc* D)
/* Write the list of exports */
{
    const ExtSym* S;

    /* Write the number of exports */
    WriteSize (D, ExtSymCount (D->Exports));

    /* Write out the symbol information */
    S = ExtSymList (D->Exports);
    while (S) {

	ExprNode* Expr;
	unsigned char SegmentID;
	ExprDesc ED;

	/* Get the name */
	unsigned NameIdx = ExtSymName (S);
        const char* Name = GetString (NameIdx);

	/* Get the export for this symbol. We've checked before that this
	 * export does really exist, so if it is unresolved, or if we don't
	 * find it, there is an error in the linker code.
	 */
	Export*	E = FindExport (NameIdx);
	if (E == 0 || IsUnresolvedExport (E)) {
	    Internal ("Unresolved export `%s' found in O65WriteExports", Name);
	}

	/* Get the expression for the symbol */
	Expr = E->Expr;

	/* Recursively collect information about this expression */
	O65ParseExpr (Expr, InitExprDesc (&ED, D), 1);

       	/* We cannot handle expressions with imported symbols, or expressions
         * with more than one segment reference here
         */
	if (ED.ExtRef != 0 || (ED.SegRef != 0 && ED.SecRef != 0)) {
	    ED.TooComplex = 1;
	}

	/* Bail out if we cannot handle the expression */
	if (ED.TooComplex) {
	    Error ("Expression for symbol `%s' is too complex", Name);
	}

	/* Determine the segment id for the expression */
        if (ED.SegRef != 0 || ED.SecRef != 0) {

            const SegDesc* Seg;

            /* Segment or section reference */
            if (ED.SecRef != 0) {
                ED.SegRef = ED.SecRef->Seg;     /* Get segment from section */
            }

       	    /* Search for the segment and map it to it's o65 segmentID */
	    Seg = O65FindSeg (D, ED.SegRef);
	    if (Seg == 0) {
		/* For some reason, we didn't find this segment in the list of
		 * segments written to the o65 file.
		 */
		Error ("Segment for symbol `%s' is undefined", Name);
	    }
	    SegmentID = O65SegType (Seg);

        } else {

	    /* Absolute value */
	    SegmentID = O65SEG_ABS;

	}

	/* Write the name to the output file */
	WriteData (D->F, Name, strlen (Name) + 1);

	/* Output the segment id followed by the literal value */
       	Write8 (D->F, SegmentID);
	WriteSize (D, ED.Val);

	/* Next symbol */
	S = ExtSymNext (S);
    }
}



/*****************************************************************************/
/*  		   	 	  Public code				     */
/*****************************************************************************/



O65Desc* NewO65Desc (void)
/* Create, initialize and return a new O65 descriptor struct */
{
    /* Allocate a new structure */
    O65Desc* D = xmalloc (sizeof (O65Desc));

    /* Initialize the header */
    D->Header.Version	= 0;
    D->Header.Mode      = 0;
    D->Header.TextBase	= 0;
    D->Header.TextSize  = 0;
    D->Header.DataBase  = 0;
    D->Header.DataSize  = 0;
    D->Header.BssBase   = 0;
    D->Header.BssSize   = 0;
    D->Header.ZPBase    = 0;
    D->Header.ZPSize    = 0;
    D->Header.StackSize = 0;		/* Let OS choose a good value */

    /* Initialize other data */
    D->Options	    	= 0;
    D->Exports		= NewExtSymTab ();
    D->Imports		= NewExtSymTab ();
    D->Undef	   	= 0;
    D->F	   	= 0;
    D->Filename	   	= 0;
    D->TextReloc   	= NewO65RelocTab ();
    D->DataReloc   	= NewO65RelocTab ();
    D->TextCount 	= 0;
    D->TextSeg		= 0;
    D->DataCount	= 0;
    D->DataSeg		= 0;
    D->BssCount		= 0;
    D->BssSeg		= 0;
    D->ZPCount		= 0;
    D->ZPSeg		= 0;

    /* Return the created struct */
    return D;
}



void FreeO65Desc (O65Desc* D)
/* Delete the descriptor struct with cleanup */
{
    /* Free the segment arrays */
    xfree (D->ZPSeg);
    xfree (D->BssSeg);
    xfree (D->DataSeg);
    xfree (D->TextSeg);

    /* Free the relocation tables */
    FreeO65RelocTab (D->DataReloc);
    FreeO65RelocTab (D->TextReloc);

    /* Free the option list */
    while (D->Options) {
        O65Option* O = D->Options;
	D->Options = D->Options->Next;
	FreeO65Option (O);
    }

    /* Free the external symbol tables */
    FreeExtSymTab (D->Exports);
    FreeExtSymTab (D->Imports);

    /* Free the struct itself */
    xfree (D);
}



void O65Set6502 (O65Desc* D)
/* Enable 6502 mode */
{
    D->Header.Mode = (D->Header.Mode & ~MF_CPU_MASK) | MF_CPU_6502;
}



void O65Set65816 (O65Desc* D)
/* Enable 816 mode */
{
    D->Header.Mode = (D->Header.Mode & ~MF_CPU_MASK) | MF_CPU_65816;
}



void O65SetSmallModel (O65Desc* D)
/* Enable a small memory model executable */
{
    D->Header.Mode = (D->Header.Mode & ~MF_SIZE_MASK) | MF_SIZE_16BIT;
}



void O65SetLargeModel (O65Desc* D)
/* Enable a large memory model executable */
{
    D->Header.Mode = (D->Header.Mode & ~MF_SIZE_MASK) | MF_SIZE_32BIT;
}



void O65SetAlignment (O65Desc* D, unsigned Align)
/* Set the executable alignment */
{
    /* Remove all alignment bits from the mode word */
    D->Header.Mode &= ~MF_ALIGN_MASK;

    /* Set the alignment bits */
    switch (Align) {
	case 1:	  D->Header.Mode |= MF_ALIGN_1;   break;
    	case 2:   D->Header.Mode |= MF_ALIGN_2;   break;
	case 4:   D->Header.Mode |= MF_ALIGN_4;   break;
        case 256: D->Header.Mode |= MF_ALIGN_256; break;
        default:  Error ("Invalid alignment for O65 format: %u", Align);
    }
}



void O65SetOption (O65Desc* D, unsigned Type, const void* Data, unsigned DataLen)
/* Set an o65 header option */
{
    /* Create a new option structure */
    O65Option* O = NewO65Option (Type, Data, DataLen);

    /* Insert it into the linked list */
    O->Next = D->Options;
    D->Options = O;
}



void O65SetOS (O65Desc* D, unsigned OS, unsigned Version, unsigned Id)
/* Set an option describing the target operating system */
{
    /* Setup the option data */
    unsigned char Opt[4];
    Opt[0] = OS;
    Opt[1] = Version;

    /* Write the correct option length */
    switch (OS) {

    	case O65OS_CC65:
            /* Set the 16 bit id */
            Opt[2] = (unsigned char) Id;
            Opt[3] = (unsigned char) (Id >> 8);
	    O65SetOption (D, O65OPT_OS, Opt, 4);
	    break;

	default:
            /* No id for OS/A65, Lunix, and unknown OSes */
       	    O65SetOption (D, O65OPT_OS, Opt, 2);
	    break;

    }
}



ExtSym* O65GetImport (O65Desc* D, unsigned Ident)
/* Return the imported symbol or NULL if not found */
{
    /* Retrieve the symbol from the table */
    return GetExtSym (D->Imports, Ident);
}



void O65SetImport (O65Desc* D, unsigned Ident)
/* Set an imported identifier */
{
    /* Insert the entry into the table */
    NewExtSym (D->Imports, Ident);
}



ExtSym* O65GetExport (O65Desc* D, unsigned Ident)
/* Return the exported symbol or NULL if not found */
{
    /* Retrieve the symbol from the table */
    return GetExtSym (D->Exports, Ident);
}



void O65SetExport (O65Desc* D, unsigned Ident)
/* Set an exported identifier */
{
    /* Get the export for this symbol and check if it does exist and is
     * a resolved symbol.
     */
    Export* E = FindExport (Ident);
    if (E == 0 || IsUnresolvedExport (E)) {
	Error ("Unresolved export: `%s'", GetString (Ident));
    }

    /* Insert the entry into the table */
    NewExtSym (D->Exports, Ident);
}



static void O65SetupSegments (O65Desc* D, File* F)
/* Setup segment assignments */
{
    Memory* M;
    MemListNode* N;
    SegDesc* S;
    unsigned TextIdx, DataIdx, BssIdx, ZPIdx;

    /* Initialize the counters */
    D->TextCount = 0;
    D->DataCount = 0;
    D->BssCount  = 0;
    D->ZPCount   = 0;

    /* Walk over the memory list */
    M = F->MemList;
    while (M) {
        /* Walk through the segment list and count the segment types */
        N = M->SegList;
        while (N) {

            /* Get the segment from the list node */
            S = N->Seg;

            /* Check the segment type. */
            switch (O65SegType (S)) {
                case O65SEG_TEXT:   D->TextCount++; break;
                case O65SEG_DATA:   D->DataCount++; break;
                case O65SEG_BSS:    D->BssCount++;  break;
                case O65SEG_ZP:	    D->ZPCount++;   break;
                default:	    Internal ("Invalid return from O65SegType");
            }

            /* Next segment node */
            N = N->Next;
        }
        /* Next memory area */
        M = M->FNext;
    }

    /* Allocate memory according to the numbers */
    D->TextSeg = xmalloc (D->TextCount * sizeof (SegDesc*));
    D->DataSeg = xmalloc (D->DataCount * sizeof (SegDesc*));
    D->BssSeg  = xmalloc (D->BssCount  * sizeof (SegDesc*));
    D->ZPSeg   = xmalloc (D->ZPCount   * sizeof (SegDesc*));

    /* Walk again through the list and setup the segment arrays */
    TextIdx = DataIdx = BssIdx = ZPIdx = 0;
    M = F->MemList;
    while (M) {

        N = M->SegList;
        while (N) {

            /* Get the segment from the list node */
            S = N->Seg;

            /* Check the segment type. */
            switch (O65SegType (S)) {
                case O65SEG_TEXT:   D->TextSeg [TextIdx++] = S;	break;
                case O65SEG_DATA:   D->DataSeg [DataIdx++] = S;	break;
                case O65SEG_BSS:    D->BssSeg [BssIdx++]   = S; break;
                case O65SEG_ZP:	    D->ZPSeg [ZPIdx++]     = S; break;
                default:	    Internal ("Invalid return from O65SegType");
            }

            /* Next segment node */
            N = N->Next;
        }
        /* Next memory area */
        M = M->FNext;
    }
}



static int O65Unresolved (unsigned Name, void* D)
/* Called if an unresolved symbol is encountered */
{
    /* Check if the symbol is an imported o65 symbol */
    if (O65GetImport (D, Name) != 0) {
	/* This is an external symbol, relax... */
	return 1;
    } else {
	/* This is actually an unresolved external. Bump the counter */
	((O65Desc*) D)->Undef++;
	return 0;
    }
}



static void O65SetupHeader (O65Desc* D)
/* Set additional stuff in the header */
{
    /* Set the base addresses of the segments */
    if (D->TextCount > 0) {
        SegDesc* FirstSeg  = D->TextSeg [0];
        D->Header.TextBase = FirstSeg->Seg->PC;
    }
    if (D->DataCount > 0) {
        SegDesc* FirstSeg  = D->DataSeg [0];
        D->Header.DataBase = FirstSeg->Seg->PC;
    }
    if (D->BssCount > 0) {
        SegDesc* FirstSeg  = D->BssSeg [0];
        D->Header.BssBase = FirstSeg->Seg->PC;
    }
    if (D->ZPCount > 0) {
        SegDesc* FirstSeg = D->ZPSeg [0];
        D->Header.ZPBase  = FirstSeg->Seg->PC;
    }

    /* If we have byte wise relocation and an alignment of 1, we can set
     * the "simple addressing" bit in the header.
     */
    if ((D->Header.Mode & MF_RELOC_MASK) == MF_RELOC_BYTE &&
        (D->Header.Mode & MF_ALIGN_MASK) == MF_ALIGN_1) {
        D->Header.Mode = (D->Header.Mode & ~MF_ADDR_MASK) | MF_ADDR_SIMPLE;
    }
}



void O65WriteTarget (O65Desc* D, File* F)
/* Write an o65 output file */
{
    char        OptBuf [256];	/* Buffer for option strings */
    unsigned    OptLen;
    time_t      T;

    /* Place the filename in the control structure */
    D->Filename = GetString (F->Name);

    /* Check for unresolved symbols. The function O65Unresolved is called
     * if we get an unresolved symbol.
     */
    D->Undef = 0;		/* Reset the counter */
    CheckUnresolvedImports (O65Unresolved, D);
    if (D->Undef > 0) {
     	/* We had unresolved symbols, cannot create output file */
       	Error ("%u unresolved external(s) found - cannot create output file", D->Undef);
    }

    /* Setup the segment arrays */
    O65SetupSegments (D, F);

    /* Setup additional stuff in the header */
    O65SetupHeader (D);

    /* Open the file */
    D->F = fopen (D->Filename, "wb");
    if (D->F == 0) {
     	Error ("Cannot open `%s': %s", D->Filename, strerror (errno));
    }

    /* Keep the user happy */
    Print (stdout, 1, "Opened `%s'...\n", D->Filename);

    /* Define some more options: A timestamp and the linker version */
    T = time (0);
    strcpy (OptBuf, ctime (&T));
    OptLen = strlen (OptBuf);
    while (OptLen > 0 && IsControl (OptBuf[OptLen-1])) {
        --OptLen;
    }
    OptBuf[OptLen] = '\0';
    O65SetOption (D, O65OPT_TIMESTAMP, OptBuf, OptLen + 1);
    sprintf (OptBuf, "ld65 V%s", GetVersionAsString ());
    O65SetOption (D, O65OPT_ASM, OptBuf, strlen (OptBuf) + 1);

    /* Write the header */
    O65WriteHeader (D);

    /* Write the text segment */
    O65WriteTextSeg (D);

    /* Write the data segment */
    O65WriteDataSeg (D);

    /* "Write" the bss segments */
    O65WriteBssSeg (D);

    /* "Write" the zeropage segments */
    O65WriteZPSeg (D);

    /* Write the undefined references list */
    O65WriteImports (D);

    /* Write the text segment relocation table */
    O65WriteTextReloc (D);

    /* Write the data segment relocation table */
    O65WriteDataReloc (D);

    /* Write the list of exports */
    O65WriteExports (D);

    /* Seek back to the start and write the updated header */
    fseek (D->F, 0, SEEK_SET);
    O65WriteHeader (D);

    /* Close the file */
    if (fclose (D->F) != 0) {
	Error ("Cannot write to `%s': %s", D->Filename, strerror (errno));
    }

    /* Reset the file and filename */
    D->F        = 0;
    D->Filename = 0;
}




