view objlink.c @ 510:43ea59887c69

Start work on making C64 formats encoding possible by changing DMDecodeOps to DMEncDecOps and adding fields and op enums for custom encode functions, renaming, etc. Split generic op sanity checking into a separate function in preparation for its use in generic encoding function.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 19 Nov 2012 15:06:01 +0200
parents b0ee847a14d3
children
line wrap: on
line source

/*
 * objlink - Link files (RAW and PRG) into one PRG object
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2002-2012 Tecnic Software productions (TNSP)
 *
 * Please read file 'COPYING' for information on license and distribution.
 */
#include <errno.h>
#include "dmlib.h"
#include "dmargs.h"
#include "dmfile.h"
#include "dmmutex.h"

#define MAX_FILENAMES    (128)
#define MAX_MEMBLOCKS    (128)


/* Typedefs
 */
typedef struct
{
    ssize_t start, end;	// Start and end address
    int type;           // Type
    char *name;         // Name of the block
    int placement;
} DMMemBlock;

typedef struct
{
    char *name;         // Description of memory model
    char *desc;
    ssize_t size;        // Total addressable memory size
    ssize_t nmemBlocks;  // Defined memory areas
    DMMemBlock memBlocks[MAX_MEMBLOCKS];
} DMMemModel;

typedef struct
{
    char *filename;
    int type;
    int placement;
    ssize_t addr;
} DMSourceFile;

// Source file type
enum
{
    STYPE_RAW = 1,
    STYPE_PRG,
    STYPE_PRGA
};

// How to determine block placement / address
enum
{
    PLACE_STATIC = 1,  // Already known
    PLACE_ARGUMENT,   // Commandline argument
    PLACE_FILE,       // From file
};

enum
{
    FMT_GENERIC = 1,
    FMT_PLAIN,
    FMT_DECIMAL
};

enum
{
    MTYPE_NONE = 0,
    MTYPE_ROM,        // Hard ROM
    MTYPE_ROM_WT,     // Write to RAM through ROM
    MTYPE_IO,         // I/O lines
    MTYPE_RES         // RESERVED
};

enum
{
    LA_NONE = -2,
    LA_AUTO = -1
};

/* Memory models
 */
const DMMemModel memoryModels[] = {
    { "C64 unrestricted", "$01 = $34", (64*1024), 0, {
    { 0, 0, 0, NULL, 0 }
    }},

    { "C64 normal (IO+Basic+Kernal)", "$01 = $37", (64*1024), 3, {
    { 0xA000, 0xBFFF,    MTYPE_ROM_WT,    "Basic ROM",  PLACE_STATIC },
    { 0xD000, 0xDFFF,    MTYPE_IO,        "I/O",  PLACE_STATIC },
    { 0xE000, 0xFFFF,    MTYPE_ROM_WT,    "Kernal ROM",  PLACE_STATIC },
    }},

    { "C64 modified (IO+Kernal)", "$01 = $36", (64*1024), 2, {
    { 0xD000, 0xDFFF,    MTYPE_IO,        "I/O",  PLACE_STATIC },
    { 0xE000, 0xFFFF,    MTYPE_ROM_WT,    "Kernal ROM",  PLACE_STATIC },
    }},

    { "C64 modified (IO only)", "$01 = $35", (64*1024), 1, {
    { 0xD000, 0xDFFF,    MTYPE_IO,        "I/O",  PLACE_STATIC },
    }},

    { "C64 modified (Char+Kernal+Basic)", "$01 = $33", (64*1024), 3, {
    { 0xA000, 0xBFFF,    MTYPE_ROM_WT,    "Basic ROM",  PLACE_STATIC },
    { 0xD000, 0xDFFF,    MTYPE_ROM,       "Char ROM", PLACE_STATIC },
    { 0xE000, 0xFFFF,    MTYPE_ROM_WT,    "Kernal ROM", PLACE_STATIC },
    }},

/*
    { "C64 normal", "$01 = $37", (64*1024), 0, {
    { 0x0000, 0x0000,    MTYPE_RAM,    "" },
    }},
*/
};

static const int nmemoryModels = sizeof(memoryModels) / sizeof(memoryModels[0]);


/* Global variables
 */
int           nsrcFiles = 0;              // Number of source files
DMSourceFile  srcFiles[MAX_FILENAMES];    // Source file names

int        nmemBlocks = 0;
DMMemBlock memBlocks[MAX_FILENAMES];

char       *optLinkFileName = NULL;
int        optLinkFileFormat = FMT_GENERIC;

BOOL       optDescribe = FALSE,
           optAllowOverlap = FALSE;

Uint32     optInitValue = 0;
int        optInitValueType = 1;
ssize_t    optCropStart, optCropEnd;
BOOL       optCropOutput = FALSE;

ssize_t    optLoadAddress = LA_AUTO;

int        optMemModel = 0;
const DMMemModel *memModel = NULL;
Uint8      *memory = NULL;

char       *optDestName = NULL;


/* Arguments
 */
static DMOptArg optList[] = {
    {  0, '?', "help",        "Show this help", OPT_NONE },
    {  1, 'r', "input-raw",   "RAW input: -r <file>:<addr>", OPT_ARGREQ },
    {  2, 'p', "input-prg",   "PRG input: -p <file>[:<addr>]", OPT_ARGREQ },
    { 12, 's', "section",     "Reserved section: -s <start>-<end>[,name] or <start>:<len>[,name]", OPT_ARGREQ },
    {  5, 'o', "output",      "Specify output file, -o <file>", OPT_ARGREQ },
    {  6, 'O', "overlap",     "Allow overlapping memory areas", OPT_NONE },
    {  7, 'm', "model",       "Set memory model", OPT_ARGREQ },
    {  8, 'l', "link-file",   "Output addresses and labels into file", OPT_ARGREQ },
    {  9, 'f', "format",      "Format of link-file: (g)eneric, (p)lain, (d)ecimal", OPT_ARGREQ },
    { 10, 'i', "initvalue",   "Initialize memory with: -i <byte/word/dword>:[bwd]", OPT_ARGREQ },
    { 11, 'd', "describe",    "Output ASCII memory map description", OPT_NONE },
    { 13, 'c', "crop",        "Crop output file to: -c <start>-<end> or <start>:<len>", OPT_ARGREQ },
    { 14, 'L', "load-address","Set output file load address (or 'none' for 'raw' output)", OPT_ARGREQ },
};

static const int optListN = sizeof(optList) / sizeof(optList[0]);


void argShowHelp()
{
    int i;

    dmPrintBanner(stdout, dmProgName, "[options]");
    dmArgsPrintHelp(stdout, optList, optListN);

    printf(
    "\n"
    "Each numeric argument can be prefixed with $ or 0x for hexadecimal values.\n"
    "NOTICE! -p filename:<addr> will ignore load address and use <addr> instead!\n"
    "\n"
    "Available memory models:\n");

    for (i = 0; i < nmemoryModels; i++)
    {
        const DMMemModel *m = &memoryModels[i];
        printf("  %d = %-40s [%s] (%d kB)\n",
            i, m->name, m->desc, m->size / 1024);
    }
}


off_t dmGetFileSize(FILE *f)
{
    off_t len, pos = ftell(f);
    fseeko(f, 0, SEEK_END);
    len = ftell(f);
    fseek(f, pos, SEEK_SET);
    return len;
}


/* Memory block handling
 */
void reserveMemBlock(ssize_t startAddr, ssize_t endAddr, const char *blockName, int blockType)
{
    if (startAddr > endAddr)
    {
        dmError("ERROR! Block '%s' has startAddr=$%.4x > endAddr=$%.4x!\n",
            blockName, startAddr, endAddr);
        exit(4);
    }

    if (nmemBlocks < MAX_FILENAMES)
    {
        memBlocks[nmemBlocks].start = startAddr;
        memBlocks[nmemBlocks].end   = endAddr;
        memBlocks[nmemBlocks].name  = dm_strdup(blockName);
        memBlocks[nmemBlocks].type  = blockType;
        nmemBlocks++;
    }
    else
    {
        dmError("Maximum number of memBlock definitions (%d) exceeded!\n",
            MAX_FILENAMES);
        exit(4);
    }
}


int compareMemBlock(const void *cva, const void *cvb)
{
    const DMMemBlock *a = cva, *b = cvb;
    return a->start - b->start;
}


BOOL dmParseSection(const char *arg, ssize_t *sectStart, ssize_t *sectEnd, char **sectName, BOOL canHasName)
{
    char sectMode, *sep, *str, *namesep;
    ssize_t tmpi;

    // Define reserved section
    // Create a copy of the argument
    if ((str = dm_strdup(arg)) == NULL)
    {
        dmError("Could not allocate temporary string!\n");
        exit(128);
    }

    // Get start address
    if ((sep = strchr(str, '-')) == NULL &&
        (sep = strchr(str, ':')) == NULL)
    {
        dmError("Section definition '%s' invalid.\n", arg);
        goto error;
    }
    sectMode = *sep;
    *sep = 0;

    // Get value
    if (!dmGetIntVal(str, sectStart))
    {
        dmError("Section start address '%s' in '%s' invalid.\n", str, arg);
        goto error;
    }
    
    // Check for name
    namesep = strchr(sep + 1, ',');
    if (canHasName && namesep != NULL)
    {
        *namesep = 0;
        namesep++;
        if (*namesep == 0)
        {
            dmError("Section definition '%s' name is empty. Either specify name or leave it out.\n",
                arg);
            goto error;
        }
        *sectName = dm_strdup(namesep);
    }
    else
    if (namesep != NULL)
    {
        dmError("Section definition does not allow a name, syntax error in '%s' at '%s'.\n",
            arg, namesep);
        goto error;
    }

    // Get end address or length
    if (!dmGetIntVal(sep + 1, &tmpi))
    {
        dmError("Section %s '%s' in '%s' invalid.\n",
            sectMode == '-' ? "end address" : "length",
            sep + 1, arg);
        goto error;
    }

    if (sectMode == ':')
    {
        *sectEnd = *sectStart + tmpi - 1;
    }
    else
    {
        if (tmpi < *sectStart)
        {
            dmError("Section start address > end address in '%s'.\n",
                arg);
            goto error;
        }
        *sectEnd = tmpi;
    }

    dmFree(str);
    return TRUE;

error:
    dmFree(str);
    return FALSE;
}


BOOL dmParseInputFile(char *arg, const int type1, const int type2, const char *desc, BOOL requireAddr)
{
    ssize_t tmpi = 0;
    BOOL hasAddr = FALSE;
    char *sep;

    if ((sep = strrchr(arg, ':')) != NULL)
    {
        *sep = 0;
        if (!dmGetIntVal(sep + 1, &tmpi))
        {
            dmError("Invalid %s address '%s' specified for '%s'.\n",
                desc, sep + 1, arg);
            return FALSE;
        }
        hasAddr = TRUE;
    }
    else
    if (requireAddr)
    {
        dmError("No %s loading address specified for '%s'.\n", desc, arg);
        return FALSE;
    }

    srcFiles[nsrcFiles].filename = arg;
    srcFiles[nsrcFiles].type = hasAddr ? type1 : type2;
    srcFiles[nsrcFiles].addr = tmpi;
    nsrcFiles++;
    return TRUE;
}


BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
{
    char *p;
    ssize_t tmpi;

    switch (optN) {
    case 0:
        argShowHelp();
        exit(0);
        break;

    case 1:
        // Add RAW
        if (!dmParseInputFile(optArg, STYPE_RAW, STYPE_RAW, "RAW", TRUE))
            return FALSE;
        break;

    case 2:
        // Add PRG
        if (!dmParseInputFile(optArg, STYPE_PRGA, STYPE_PRG, "PRG", FALSE))
            return FALSE;
        break;

    case 5:
        // Set output file name
        optDestName = optArg;
        break;

    case 6:
        // Allow overlapping segments
        optAllowOverlap = TRUE;
        dmError("Warning, allowing overlapping data.\n");
        break;

    case 7:
        // Set memory model
        optMemModel = atoi(optArg);
        if (optMemModel < 0 || optMemModel >= nmemoryModels)
        {
            dmError("Invalid memory model number %i!\n", optMemModel);
            return FALSE;
        }
        break;

    case 8:
        // Linker file
        optLinkFileName = optArg;
        break;

    case 9:
        // Linker file format
        switch (tolower(optArg[0]))
        {
            case 'g':
                optLinkFileFormat = FMT_GENERIC;
                break;
            case 'p':
                optLinkFileFormat = FMT_PLAIN;
                break;
            case 'd':
                optLinkFileFormat = FMT_DECIMAL;
                break;

            default:
                dmError("Invalid/unknown linker file format '%s'!\n",
                    optArg);
                return FALSE;
        }
        break;

    case 10:
        // Initialization value
        optInitValueType = 1;
        if ((p = strrchr(optArg, ':')) != NULL)
        {
            *p = 0;
            switch (tolower(p[1]))
            {
                case 'b': optInitValueType = 1; break;
                case 'w': optInitValueType = 2; break;
                case 'd': optInitValueType = 4; break;
                default:
                    dmError("Invalid init value type '%c' specified for '%s'.\n",
                        p[1], optArg);
                    return FALSE; 
            }
        }
        if (!dmGetIntVal(optArg, &tmpi))
        {
            dmError("Invalid initvalue '%s'.\n", optArg);
            return FALSE;
        }
        optInitValue = tmpi;
        break;

    case 11:
        // Set describe mode
        optDescribe = TRUE;
        break;

    case 12:
        {
            char *sectName = "Clear";
            ssize_t sectStart, sectEnd, sectLen;
            if (!dmParseSection(optArg, &sectStart, &sectEnd, &sectName, TRUE))
                return FALSE;

            // Allocate memory block
            sectLen = sectEnd - sectStart + 1;
            dmMsg(1, "Reserve $%.4x - $%.4x ($%x, %d bytes) as '%s'\n",
                sectStart, sectEnd, sectLen, sectLen, sectName);

            reserveMemBlock(sectStart, sectEnd, sectName, MTYPE_RES);
        }
        break;

    case 13:
        {
            size_t cropLen;
            if (!dmParseSection(optArg, &optCropStart, &optCropEnd, NULL, FALSE))
                return FALSE;

            cropLen = optCropEnd - optCropEnd + 1;
            dmMsg(1, "Cutting output to $%.4x - $%.4x ($%x, %d bytes)\n",
                optCropStart, optCropEnd, cropLen, cropLen);

            optCropOutput = TRUE;
        }
        break;

    case 14:
        // Set loading address
        if (strcasecmp(optArg, "none") == 0)
            optLoadAddress = LA_NONE;
        else
        {
            if (!dmGetIntVal(optArg, &tmpi))
            {
                dmError("Invalid loading address '%s'.\n", optArg);
                return FALSE;
            }
            if (tmpi < 0 || tmpi >= 64*1024)
            {
                dmError("Invalid or insane loading address %d/$%x!\n",
                    tmpi);
                return FALSE;
            }
            optLoadAddress = tmpi;
        }
        break;

    default:
        dmError("Unknown argument '%s'.\n", currArg);
        return FALSE;
    }

    return TRUE;
}


int dmLoadPRG(const char *filename, BOOL forceAddr, const ssize_t destAddr)
{
    FILE *f;
    ssize_t dataSize, loadAddr, endAddr;
    Uint16 tmpAddr;

    // Open the input file
    if ((f = fopen(filename, "rb")) == NULL)
    {
        dmError("Error opening input file '%s' (%s).\n",
            filename, strerror(errno));
        return 1;
    }

    // Get filesize
    if ((dataSize = dmGetFileSize(f) - 2) < 0)
    {
        dmError("Error getting file size for '%s'.\n", filename);
        return 6;
    }

    // Get loading address
    if (!dm_fread_le16(f, &tmpAddr))
    {
        dmError("Error reading input file '%s' (%s).\n",
            filename, strerror(errno));
        return 2;
    }

    // Show information
    loadAddr = forceAddr ? destAddr : tmpAddr;
    endAddr = loadAddr + dataSize - 1;

    dmPrint(1, "* Loading '%s', %s at $%.4x-$%.4x",
        filename, forceAddr ? "PRGA" : "PRG", loadAddr, endAddr);

    if (endAddr >= memModel->size)
    {
        dmPrint(1, " .. Does not fit into the memory!\n");
        return 5;
    }

    // Load data
    if (fread(&memory[loadAddr], dataSize, 1, f) < 1)
    {
        dmPrint(1, " .. Error: %s.\n",
            strerror(errno));
        return 4;
    }

    dmPrint(1, " .. OK\n");

    // Add to list of blocks
    reserveMemBlock(loadAddr, endAddr, filename, MTYPE_RES);

    return 0;
}


int dmLoadRAW(const char *filename, const ssize_t destAddr)
{
    FILE *f;
    ssize_t dataSize, endAddr;

    // Open the input file
    if ((f = fopen(filename, "rb")) == NULL)
    {
        dmError("Error opening input file '%s' (%s).\n",
            filename, strerror(errno));
        return 1;
    }

    // Get filesize
    if ((dataSize = dmGetFileSize(f)) < 0)
    {
        dmError("Error getting file size for '%s'.\n", filename);
        return 6;
    }

    // Show information
    endAddr = destAddr + dataSize - 1;
    dmPrint(1, "* Loading '%s', RAW at $%.4x-$%.4x",
        filename, destAddr, endAddr);

    if (endAddr >= memModel->size)
    {
        dmPrint(1, " .. Does not fit into the memory!\n");
        return 5;
    }

    // Load data
    if (fread(&memory[destAddr], dataSize, 1, f) < 1)
    {
        dmPrint(1, " .. Error: %s.\n",
            strerror(errno));
        return 4;
    }

    dmPrint(1, " .. OK\n");

    // Add info to list
    reserveMemBlock(destAddr, endAddr, filename, MTYPE_RES);

    return 0;
}


int outputLinkData(FILE *dfile, const char *blockName, const int blockStart, const int blockEnd)
{
    char *tmpStr, *s, *t;
    int blockSize;

    blockSize = (blockEnd - blockStart + 1);

    // Create label name from filename
    tmpStr = dm_strdup(blockName);
    if (tmpStr == NULL)
    {
        dmError("Could not allocate memory for string '%s'!\n",
            blockName);
        return -1;
    }

    if ((t = strrchr(tmpStr, '/')))
        s = (t + 1);
    else if ((t = strrchr(tmpStr, '\\')))
        s = (t + 1);
    else
        s = tmpStr;

    if ((t = strrchr(s, '.')))
        *t = 0;

    for (t = s; *t; t++)
    {
        if (!isalnum(*t))
            *t = '_';
    }

    // Print the label line
    switch (optLinkFileFormat)
    {
        case FMT_PLAIN:
            fprintf(dfile, "%s = $%.4x\n", tmpStr, blockStart);
            break;

        case FMT_DECIMAL:
            fprintf(dfile, "%s = %d\n", tmpStr, blockStart);
            break;

        case FMT_GENERIC:
        default:
            fprintf(dfile, "; %s ($%.4x - $%.4x, %d/$%x bytes)\n",
                blockName, blockStart, blockEnd, blockSize, blockSize);
            fprintf(dfile, "%s = $%.4x\n", s, blockStart);
            break;
    }

    dmFree(tmpStr);
    return 0;
}


/* Print out an ASCII presentation of memory map
 */
void memPrintLine(FILE *f)
{
    fprintf(f, "              +------------------------------------------+\n");
}

void memPrintEmpty(FILE *f, ssize_t n)
{
    ssize_t i;
    for (i = 0; i < n; i++)
    fprintf(f, "              |                                          |\n");
}

void dmDescribeMemory(FILE *f)
{
    int i;
    DMMemBlock *prev = NULL;

    memPrintLine(f);

    for (i = 0; i < nmemBlocks; i++)
    {
        DMMemBlock *curr = &memBlocks[i];
        char desc[512], *s;
        ssize_t siz, kz;

        // Check for empty, unreserved areas
        siz = (curr->start - 1) - (prev->end + 1) + 1;
        if (prev != NULL && siz > 1)
        {
            kz = siz / (1024 * 2);

            if (kz > 1) memPrintEmpty(f, kz);

            snprintf(desc, sizeof(desc), "EMPTY (%d)", siz);
            fprintf(f, "$%.4x - $%.4x | %-40s |\n", prev->end + 1, curr->start - 1, desc);

            if (kz > 1) memPrintEmpty(f, kz);
            memPrintLine(f);
        }
        prev = curr;

        // Print current block
        switch (curr->type)
        {
            case MTYPE_NONE:   s = "N/A (NC)"; break;
            case MTYPE_ROM:    s = "ROM"; break;
            case MTYPE_ROM_WT: s = "ROM/WT"; break;
            case MTYPE_IO:     s = "I/O"; break;
            case MTYPE_RES:    s = "RSVD"; break;
            default:           s = "????"; break;
        }

        siz = curr->end - curr->start + 1;
        kz = siz / (1024 * 2);

        if (kz > 1) memPrintEmpty(f, kz);
        snprintf(desc, sizeof(desc), "%s (%s, %d)", curr->name, s, siz);
        fprintf(f, "$%.4x - $%.4x | %-40s |\n", curr->start, curr->end, desc);
        if (kz > 1) memPrintEmpty(f, kz);
        memPrintLine(f);

    }

    fprintf(f,
    "\n"
    "NC     = Not Connected\n"
    "RSVD   = Reserved\n"
    "ROM/WT = RAM under 'write-through' ROM\n"
    "\n"
    );
}


/*
 * The main program
 */
int main(int argc, char *argv[])
{
    FILE *dfile = NULL;
    BOOL hasOverlaps;
    int i, j;
    ssize_t startAddr, endAddr, dataSize, totalSize;

    dmInitProg("objlink", "Simple file-linker", "0.80", NULL, NULL);
    dmVerbosity = 1;

    // Parse arguments
    if (!dmArgsProcess(argc, argv, optList, optListN,
        argHandleOpt, NULL, TRUE))
        exit(1);

    if (nsrcFiles < 1)
    {
        dmError("Nothing to do. (try --help)\n");
        exit(0);
    }

    // Allocate memory
    memModel = &memoryModels[optMemModel];
    dmMsg(1, "Using memory model #%d '%s', %d bytes.\n",
        optMemModel, memModel->name, memModel->size);

    memory = (Uint8 *) dmMalloc(memModel->size + 32);
    if (memory == NULL)
    {
        dmError("Could not allocate memory.\n");
        exit(2);
    }

    // Initialize memory
    dmMsg(1, "Initializing memory with ");

    if (optInitValueType == 1 || optInitValue <= 0xff)
    {
        dmPrint(1, "BYTE 0x%.2x\n", optInitValue);
        memset(memory, optInitValue, memModel->size);
    }
    else
    if (optInitValueType == 2 || optInitValue <= 0xffff)
    {
        uint16_t *mp = (uint16_t *) memory;
        dmPrint(1, "WORD 0x%.4x\n", optInitValue);
        for (i = memModel->size / sizeof(*mp); i; i--)
        {
            *mp++ = optInitValue;
        }
    }
    else
    {
        Uint32 *mp = (Uint32 *) memory;
        dmPrint(1, "DWORD 0x%.8x\n", optInitValue);
        for (i = memModel->size / sizeof(*mp); i; i--)
        {
            *mp++ = optInitValue;
        }
    }

    // Load the datafiles
    for (i = 0; i < nsrcFiles; i++)
    switch (srcFiles[i].type)
    {
        case STYPE_RAW:
            dmLoadRAW(srcFiles[i].filename, srcFiles[i].addr);
            break;

        case STYPE_PRG:
            dmLoadPRG(srcFiles[i].filename, FALSE, 0);
            break;

        case STYPE_PRGA:
            dmLoadPRG(srcFiles[i].filename, TRUE, srcFiles[i].addr);
            break;
    }

    // Add memory model blocks
    dmMsg(1, "Applying memory model restrictions...\n");
    for (i = 0; i < memModel->nmemBlocks; i++)
    {
        reserveMemBlock(
            memModel->memBlocks[i].start,
            memModel->memBlocks[i].end,
            memModel->memBlocks[i].name,
            memModel->memBlocks[i].type);
    }

    // Sort the blocks
    qsort(memBlocks, nmemBlocks, sizeof(DMMemBlock), compareMemBlock);

    // Check for overlapping conflicts
    hasOverlaps = FALSE;
    for (i = 0; i < nmemBlocks; i++)
    for (j = 0; j < nmemBlocks; j++)
    if (j != i && memBlocks[i].type == MTYPE_RES)
    {
        DMMemBlock *mbi = &memBlocks[i],
                   *mbj = &memBlocks[j];

        // Check for per-file conflicts
        if ((mbj->start >= mbi->start && mbj->start <= mbi->end) ||
            (mbj->end >= mbi->start && mbj->end <= mbi->end))
        {
            dmPrint(1, "* '%s' and '%s' overlap ($%.4x-$%.4x  vs  $%.4x-$%.4x)\n",
                mbi->name, mbj->name, mbi->start,
                mbi->end, mbj->start, mbj->end);
            hasOverlaps = TRUE;
        }
    }

    if (!optAllowOverlap && hasOverlaps)
    {
        dmError("Error occured, overlaps not allowed.\n");
        exit(5);
    }

    // Find out start and end-addresses
    startAddr = memModel->size;
    totalSize = endAddr = 0;
    for (i = 0; i < nmemBlocks; i++)
    {
        DMMemBlock *mbi = &memBlocks[i];
        if (mbi->type == MTYPE_RES)
        {
            if (mbi->start < startAddr)
                startAddr = mbi->start;

            if (mbi->end > endAddr)
                endAddr = mbi->end;

            totalSize += (mbi->end - mbi->start + 1);
        }
    }

    if (startAddr >= memModel->size || endAddr < startAddr)
    {
        dmError("Invalid saveblock addresses (start=$%.4x, end=$%.4x)!\n", startAddr, endAddr);
        exit(8);
    }

    // Output linkfile
    if (optLinkFileName)
    {
        dmMsg(1, "Writing linkfile to '%s'\n", optLinkFileName);
        if ((dfile = fopen(optLinkFileName, "wb")) == NULL)
        {
            dmError("Error creating file '%s' (%s).\n", optLinkFileName, strerror(errno));
            exit(1);
        }

        switch (optLinkFileFormat)
        {
            case FMT_GENERIC:
            default:
                fprintf(dfile, "; Definitions generated by %s v%s\n",
                    dmProgName, dmProgVersion);
                break;
        }

        for (i = 0; i < nmemBlocks; i++)
        {
            DMMemBlock *mbi = &memBlocks[i];
            outputLinkData(dfile, mbi->name, mbi->start, mbi->end);
        }

        fclose(dfile);
    }

    // Show some information
    if (optCropOutput)
    {
        startAddr = optCropStart;
        endAddr = optCropEnd;
    }

    dataSize = endAddr - startAddr + 1;

    if (dataSize - totalSize > 0)
    {
        dmMsg(1, "Total of %d/$%x bytes unused(?) areas.\n",
            dataSize - totalSize, dataSize - totalSize);
    }

    dmMsg(1, "Writing $%.4x - $%.4x (%d/$%x bytes) ",
        startAddr, endAddr, dataSize, dataSize);


    // Open the destination file
    if (optDestName == NULL)
    {
        dfile = stdout;
        dmPrint(1, "...\n");
    }
    else if ((dfile = fopen(optDestName, "wb")) == NULL)
    {
        dmError("Error creating output file '%s' (%s).\n", optDestName, strerror(errno));
        exit(1);
    }
    else
        dmPrint(1, "to '%s'\n", optDestName);

    // Save loading address
    if (optLoadAddress >= 0)
    {
        dmMsg(1, "Using specified loading address $%.4x\n", optLoadAddress);
        dm_fwrite_le16(dfile, optLoadAddress);
    }
    else
    if (optLoadAddress == LA_AUTO)
    {
        dmMsg(1, "Using automatic loading address $%.4x\n", startAddr);
        dm_fwrite_le16(dfile, startAddr);
    }
    else
    {
        dmMsg(1, "Writing raw output, without loading address.\n");
    }

    // Save the data
    if (fwrite(&memory[startAddr], dataSize, 1, dfile) < 1)
    {
        dmError("Error writing to file (%s)\n", strerror(errno));
    }

    fclose(dfile);

    // Describe
    if (optDescribe)
        dmDescribeMemory(stdout);

    exit(0);
    return 0;
}