diff objlink.c @ 407:59244a7ae37f

Move c64 utilities to the engine lib, as we benefit from a common framework.
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 03 Nov 2012 02:19:51 +0200
parents
children 81b92a0e754b
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/objlink.c	Sat Nov 03 02:19:51 2012 +0200
@@ -0,0 +1,899 @@
+/*
+ * 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
+    ssize_t size;
+    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
+};
+
+
+/* Memory models
+ */
+const DMMemModel memoryModels[] = {
+    { "C64 unrestricted", "$01 = $34", (64*1024), 0, {
+    { 0, 0, 0, NULL, 0, 0 }
+    }},
+
+    { "C64 normal (IO+Basic+Kernal)", "$01 = $37", (64*1024), 3, {
+    { 0xA000, 0xBFFF,    MTYPE_ROM_WT,    "Basic ROM", 0, PLACE_STATIC },
+    { 0xD000, 0xDFFF,    MTYPE_IO,        "I/O", 0, PLACE_STATIC },
+    { 0xE000, 0xFFFF,    MTYPE_ROM_WT,    "Kernal ROM", 0, PLACE_STATIC },
+    }},
+
+    { "C64 modified (IO+Kernal)", "$01 = $36", (64*1024), 2, {
+    { 0xD000, 0xDFFF,    MTYPE_IO,        "I/O", 0, PLACE_STATIC },
+    { 0xE000, 0xFFFF,    MTYPE_ROM_WT,    "Kernal ROM", 0, PLACE_STATIC },
+    }},
+
+    { "C64 modified (IO only)", "$01 = $35", (64*1024), 1, {
+    { 0xD000, 0xDFFF,    MTYPE_IO,        "I/O", 0, PLACE_STATIC },
+    }},
+
+    { "C64 modified (Char+Kernal+Basic)", "$01 = $33", (64*1024), 3, {
+    { 0xA000, 0xBFFF,    MTYPE_ROM_WT,    "Basic ROM", 0, PLACE_STATIC },
+    { 0xD000, 0xDFFF,    MTYPE_ROM,       "Char ROM", 0, PLACE_STATIC },
+    { 0xE000, 0xFFFF,    MTYPE_ROM_WT,    "Kernal ROM", 0, 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;
+
+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> or <start>:<len>", 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 },
+};
+
+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].size = (endAddr - startAddr + 1);
+        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 argHandleOpt(const int optN, char *optArg, char *currArg)
+{
+    char *p, *s;
+    ssize_t tmpi;
+
+    switch (optN) {
+    case 0:
+        argShowHelp();
+        exit(0);
+        break;
+
+    case 1:
+        // Add RAW
+        if ((p = strrchr(optArg, ':')) != NULL)
+        {
+            *p = 0;
+            if (!dmGetIntVal(p + 1, &tmpi))
+            {
+                dmError("Invalid RAW address '%s' specified for '%s'.\n",
+                    p + 1, optArg);
+                return FALSE;
+            }
+        }
+        else
+        {
+            dmError("No RAW loading address specified for '%s'.\n", optArg);
+            return FALSE;
+        }
+        srcFiles[nsrcFiles].filename = optArg;
+        srcFiles[nsrcFiles].type = STYPE_RAW;
+        srcFiles[nsrcFiles].addr = tmpi;
+        nsrcFiles++;
+        break;
+
+    case 2:
+        // Add PRG
+        if ((p = strrchr(optArg, ':')) != NULL)
+        {
+            *p = 0;
+            if (!dmGetIntVal(p + 1, &tmpi))
+            {
+                dmError("Invalid PRG address '%s' specified for '%s'.\n",
+                    p + 1, optArg);
+                return FALSE;
+            }
+            srcFiles[nsrcFiles].addr = tmpi;
+            srcFiles[nsrcFiles].type = STYPE_PRGA;
+        }
+        else
+        {
+            srcFiles[nsrcFiles].type = STYPE_PRG;
+        }
+
+        srcFiles[nsrcFiles].filename = optArg;
+        nsrcFiles++;
+        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:
+        {
+            ssize_t sectStart, sectEnd, sectLen;
+            char sectMode;
+            
+            // Define reserved section
+            // Create a copy of the argument
+            if ((s = dm_strdup(optArg)) == NULL)
+            {
+                dmError("Could not allocate temporary string!\n");
+                exit(128);
+            }
+
+            // Get start address
+            if ((p = strchr(s, '-')) == NULL &&
+                (p = strchr(s, ':')) == NULL)
+            {
+                dmFree(s);
+                dmError("Section definition '%s' invalid.\n", optArg);
+                return FALSE;
+            }
+            sectMode = *p;
+            *p = 0;
+
+            // Get value
+            if (!dmGetIntVal(s, &sectStart))
+            {
+                dmError("Section start address '%s' in '%s' invalid.\n", s, optArg);
+                dmFree(s);
+                return FALSE;
+            }
+
+            // Get end address or length
+            if (!dmGetIntVal(p + 1, &tmpi))
+            {
+                dmError("Section %s '%s' in '%s' invalid.\n",
+                    sectMode == '-' ? "end address" : "length",
+                    p, optArg);
+                dmFree(s);
+                return FALSE;
+            }
+
+            dmFree(s);
+
+            if (sectMode == ':')
+            {
+                sectEnd = sectStart + tmpi;
+                sectLen = tmpi;
+            }
+            else
+            {
+                if (tmpi < sectStart)
+                {
+                    dmError("Section start address > end address in '%s'.\n",
+                        optArg);
+                    return FALSE;
+                }
+                sectLen = tmpi - sectStart + 1;
+                sectEnd = tmpi;
+            }
+
+            // Allocate memory block
+            dmMsg(1, "Reserve $%.4x - $%.4x ($%x, %d bytes) as 'Clear'\n",
+                sectStart, sectEnd, sectLen, sectLen);
+
+            reserveMemBlock(sectStart, sectEnd, "Clear", MTYPE_RES);
+        }
+        break;
+
+    default:
+        dmError("Unknown argument '%s'.\n", currArg);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+int dmLoadPRG(const char *fname, BOOL forceAddr, int destAddr)
+{
+    FILE *f;
+    ssize_t a, b, s;
+
+    // Open the input file
+    if ((f = fopen(fname, "rb")) == NULL)
+    {
+        dmError("Error opening input file '%s' (%s).\n",
+            fname, strerror(errno));
+        return 1;
+    }
+
+    // Get filesize
+    if ((s = dmGetFileSize(f) - 2) < 0)
+    {
+        dmError("Error getting file size for '%s'.\n", fname);
+        return 6;
+    }
+
+    // Get loading address
+    if ((a = fgetc(f)) < 0)
+    {
+        dmError("Error reading input file '%s' (%s).\n",
+            fname, strerror(errno));
+        return 2;
+    }
+
+    if ((b = fgetc(f)) < 0)
+    {
+        dmError("Error reading input file '%s' (%s).\n",
+            fname, strerror(errno));
+        return 3;
+    }
+
+    // Show information
+    if (forceAddr)
+        a = destAddr;
+    else
+        a = a + (b * 0x100);
+
+    dmPrint(1, "* Loading '%s', %s at $%.4x-$%.4x",
+        fname, forceAddr ? "PRGA" : "PRG", a, (a + s - 1));
+
+    if (a + s >= memModel->size)
+    {
+        dmPrint(1, " .. Does not fit into the memory!\n");
+        return 5;
+    }
+
+    // Load data
+    if (fread(&memory[a], s, 1, f) < 1)
+    {
+        dmPrint(1, " .. Error: %s.\n",
+            strerror(errno));
+        return 4;
+    }
+
+    dmPrint(1, " .. OK\n");
+
+    // Add info to list
+    reserveMemBlock(a, (a + s + 1), fname, MTYPE_RES);
+
+    return 0;
+}
+
+
+int dmLoadRAW(const char *fname, int destAddr)
+{
+    FILE *f;
+    ssize_t s;
+
+    // Open the input file
+    if ((f = fopen(fname, "rb")) == NULL)
+    {
+        dmError("Error opening input file '%s' (%s).\n",
+            fname, strerror(errno));
+        return 1;
+    }
+
+    // Get filesize
+    s = dmGetFileSize(f);
+    if (s < 0)
+    {
+        dmError("Error getting file size for '%s'.\n", fname);
+        return 6;
+    }
+
+    // Show information
+    dmPrint(1, "* Loading '%s', RAW at $%.4x-$%.4x",
+        fname, destAddr, (destAddr + s - 1));
+
+    if (destAddr + s >= memModel->size)
+    {
+        dmPrint(1, " .. Does not fit into the memory!\n");
+        return 5;
+    }
+
+    // Load data
+    if (fread(&memory[destAddr], s, 1, f) < 1)
+    {
+        dmPrint(1, " .. Error: %s.\n",
+            strerror(errno));
+        return 4;
+    }
+
+    dmPrint(1, " .. OK\n");
+
+    // Add info to list
+    reserveMemBlock(destAddr, (destAddr + s + 1), fname, 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
+        if (prev != NULL && prev->start - 1 > curr->end + 1)
+        {
+            siz = (prev->start - 1) - (curr->end + 1);
+            kz = siz / (1024 * 2);
+
+            if (kz > 1) memPrintEmpty(f, kz);
+
+            snprintf(desc, sizeof(desc), "EMPTY (%d)", siz);
+            fprintf(f, "$%.4x - $%.4x | %-40s |\n", curr->end + 1, prev->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
+    dataSize = endAddr - startAddr + 1;
+    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
+    dm_fwrite_le16(dfile, startAddr);
+
+    // 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;
+}