view 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 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
    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;
}