view tools/objlink.c @ 2561:56510bd4c66b

Cleanup.
author Matti Hamalainen <ccr@tnsp.org>
date Sun, 27 Feb 2022 17:58:33 +0200
parents ac2e60f4bfc3
children 5c9056f381ae
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-2022 Tecnic Software productions (TNSP)
 *
 * Please read file 'COPYING' for information on license and distribution.
 */
#include "dmtool.h"
#include "dmlib.h"
#include "dmargs.h"
#include "dmfile.h"


#define SET_MAX_FILENAMES    (128)
#define SET_MAX_MEMBLOCKS    (128)


/* Typedefs
 */
typedef struct
{
    int 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;
    int size;           // Total addressable memory size
    int nmemBlocks;     // Defined memory areas
    DMMemBlock memBlocks[SET_MAX_MEMBLOCKS];
} DMMemModel;


typedef struct
{
    char *filename;
    int type;
    int placement;
    int 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,         // ROM
    MTYPE_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,
        {
            { 0x0000, 0x0000, 0            , NULL         , 0 }
        }
    },

    { "C64 normal (IO+Basic+Kernal)", "$01 = $37",
        64 * 1024,
        3,
        {
            { 0xA000, 0xBFFF,  MTYPE_WT    , "Basic ROM"  ,  PLACE_STATIC },
            { 0xD000, 0xDFFF,  MTYPE_IO    , "I/O"        ,  PLACE_STATIC },
            { 0xE000, 0xFFFF,  MTYPE_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_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_WT    , "Basic ROM"  , PLACE_STATIC },
            { 0xD000, 0xDFFF,  MTYPE_ROM   , "Char ROM"   , PLACE_STATIC },
            { 0xE000, 0xFFFF,  MTYPE_WT    , "Kernal ROM" , PLACE_STATIC },
        }
    },
};

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


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

int        nmemBlocks = 0;
DMMemBlock memBlocks[SET_MAX_FILENAMES];

char       *optDestName = NULL;
char       *optLinkFileName = NULL;
int        optLinkFileFormat = FMT_GENERIC;
BOOL       optDescribe = FALSE,
           optAllowOverlap = FALSE,
           optCropOutput = FALSE;
unsigned int optCropStart, optCropEnd;
unsigned int optInitValue = 0;
int        optInitValueType = 1;
int        optLoadAddress = LA_AUTO;
int        optMemModel = 0;
const DMMemModel *memModel = NULL;
Uint8      *memory = NULL;


/* Arguments
 */
static const DMOptArg optList[] =
{
    {  0, '?', "help"            , "Show this help", OPT_NONE },
    {  1,   0, "license"         , "Print out this program's license agreement", OPT_NONE },
    {  2, 'v', "verbose"         , "Be more verbose", OPT_NONE },

    { 10, 'r', "input-raw"       , "RAW input: -r <file>:<addr>", OPT_ARGREQ },
    { 12, 'p', "input-prg"       , "PRG input: -p <file>[:<addr>]", OPT_ARGREQ },
    { 14, 's', "section"         , "Reserved section: -s <start>-<end>[,name] or <start>:<len>[,name]", OPT_ARGREQ },
    { 16, 'o', "output"          , "Specify output file, -o <file>", OPT_ARGREQ },
    { 18, 'O', "overlap"         , "Allow overlapping memory areas", OPT_NONE },
    { 20, 'm', "model"           , "Set memory model", OPT_ARGREQ },
    { 22, 'l', "link-file"       , "Output addresses and labels into file", OPT_ARGREQ },
    { 24, 'f', "format"          , "Format of link-file: (g)eneric, (p)lain, (d)ecimal", OPT_ARGREQ },
    { 26, 'i', "init-value"      , "Initialize memory with: -i <byte/word/dword>:[bwd]", OPT_ARGREQ },
    { 28, 'd', "describe"        , "Output ASCII memory map description", OPT_NONE },
    { 30, 'c', "crop"            , "Crop output file to: -c <start>-<end> or <start>:<len>", OPT_ARGREQ },
    { 32, '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()
{
    dmPrintBanner(stdout, dmProgName, "[options]");
    dmArgsPrintHelp(stdout, optList, optListN, 0, 80 - 2);

    fprintf(stdout,
    "\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 (int i = 0; i < nmemoryModels; i++)
    {
        const DMMemModel *m = &memoryModels[i];
        fprintf(stdout,
            "  %d = %-40s [%s] (%d kB)\n",
            i, m->name, m->desc, m->size / 1024);
    }

    fprintf(stdout,
    "\n"
    "Memory block types:\n"
    "  NC     = Not Connected\n"
    "  RSVD   = Reserved\n"
    "  WT     = RAM under 'write-through' ROM\n"
    "\n"
    );
}


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


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

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


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


BOOL dmParseSection(const char *arg, unsigned int *sectStart, unsigned int *sectEnd, char **sectName, BOOL canHasName)
{
    char sectMode, *sep, *str, *namesep;
    unsigned int tmpi;
    BOOL res = FALSE;

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

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

    // Get value
    if (!dmGetIntVal(str, sectStart, NULL))
    {
        dmErrorMsg("Section start address '%s' in '%s' invalid.\n", str, arg);
        goto out;
    }

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

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

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

    res = TRUE;

out:
    dmFree(str);
    return res;
}


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

    if ((sep = strrchr(arg, ':')) != NULL)
    {
        *sep = 0;
        if (!dmGetIntVal(sep + 1, &tmpi, NULL))
        {
            dmErrorMsg("Invalid %s address '%s' specified for '%s'.\n",
                desc, sep + 1, arg);
            return FALSE;
        }
        hasAddr = TRUE;
    }
    else
    if (requireAddr)
    {
        dmErrorMsg("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)
{

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

        case 1:
            dmPrintLicense(stdout);
            exit(0);
            break;

        case 2:
            dmVerbosity++;
            break;

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

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

        case 14:
            {
                char *sectName = "Clear";
                unsigned int 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);

                if (dmReserveMemBlock(sectStart, sectEnd, sectName, MTYPE_RES) != DMERR_OK)
                    return FALSE;
            }
            break;

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

        case 18:
            // Allow overlapping segments
            optAllowOverlap = TRUE;
            break;

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

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

        case 24:
            // 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:
                    dmErrorMsg("Invalid/unknown linker file format '%s'!\n",
                        optArg);
                    return FALSE;
            }
            break;

        case 26:
            // Initialization value
            {
                char *p;
                unsigned int tmpi;

                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:
                            dmErrorMsg("Invalid init value type '%c' specified for '%s'.\n",
                                p[1], optArg);
                            return FALSE;
                    }
                }
                if (!dmGetIntVal(optArg, &tmpi, NULL))
                {
                    dmErrorMsg("Invalid initvalue '%s'.\n", optArg);
                    return FALSE;
                }
                optInitValue = tmpi;
            }
            break;

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

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

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

                optCropOutput = TRUE;
            }
            break;

        case 32:
            // Set loading address
            if (strcasecmp(optArg, "none") == 0)
            {
                optLoadAddress = LA_NONE;
            }
            else
            {
                unsigned int tmpi;

                if (!dmGetIntVal(optArg, &tmpi, NULL))
                {
                    dmErrorMsg("Invalid loading address '%s'.\n", optArg);
                    return FALSE;
                }
                if (tmpi >= 64*1024)
                {
                    dmErrorMsg("Invalid or insane loading address %d/$%x!\n",
                        tmpi, tmpi);
                    return FALSE;
                }
                optLoadAddress = tmpi;
            }
            break;

        default:
            dmErrorMsg("Unimplemented option argument '%s'.\n", currArg);
            return FALSE;
    }

    return TRUE;
}


int dmLoadPRG(const char *filename, const BOOL forceAddr, const int destAddr)
{
    FILE *fh;
    int dataSize, loadAddr, endAddr, res = DMERR_OK;
    Uint16 tmpAddr;

    // Open the input file
    if ((fh = fopen(filename, "rb")) == NULL)
    {
        res = dmGetErrno();
        dmErrorMsg("Error opening input file '%s' #%d: %s.\n",
            filename, res, dmErrorStr(res));
        goto out;
    }

    // Get filesize
    if ((dataSize = dmGetFileSize(fh) - 2) < 0)
    {
        res = dmGetErrno();
        dmErrorMsg("Error getting file size for '%s' #%d: %s.\n",
            filename, res, dmErrorStr(res));
        goto out;
    }

    // Get loading address
    if (!dm_fread_le16(fh, &tmpAddr))
    {
        res = dmGetErrno();
        dmErrorMsg("Error reading input file '%s' #%d: %s.\n",
            filename, res, dmErrorStr(res));
        goto out;
    }

    // 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)
    {
        res = DMERR_BOUNDS;
        dmPrint(1, " .. Does not fit into the memory!\n");
        goto out;
    }

    // Load data
    if (fread(&memory[loadAddr], dataSize, 1, fh) < 1)
    {
        res = dmGetErrno();
        dmPrint(1, " .. Error #%d: %s.\n",
            res, dmErrorStr(res));
        goto out;
    }

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

    // Add to list of blocks
    if ((res = dmReserveMemBlock(loadAddr, endAddr, filename, MTYPE_RES)) != DMERR_OK)
        goto out;

out:
    if (fh != NULL)
        fclose(fh);

    return res;
}


int dmLoadRAW(const char *filename, const int destAddr)
{
    FILE *fh;
    int dataSize, endAddr, res = DMERR_OK;

    // Open the input file
    if ((fh = fopen(filename, "rb")) == NULL)
    {
        res = dmGetErrno();
        dmErrorMsg("Error opening input file '%s' #%d: %s.\n",
            filename, res, dmErrorStr(res));
        goto out;
    }

    // Get filesize
    if ((dataSize = dmGetFileSize(fh)) < 0)
    {
        res = dmError(DMERR_FREAD,
            "Error getting file size for '%s'.\n", filename);
        goto out;
    }

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

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

    // Load data
    if (fread(&memory[destAddr], dataSize, 1, fh) < 1)
    {
        res = dmGetErrno();
        dmPrint(1, " .. Error reading data #%d: %s.\n",
            res, dmErrorStr(res));
        goto out;
    }

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

    // Add info to list
    if ((res = dmReserveMemBlock(destAddr, endAddr, filename, MTYPE_RES)) != DMERR_OK)
        goto out;

out:
    if (fh != NULL)
        fclose(fh);

    return res;
}


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
    if ((tmpStr = dm_strdup(blockName)) == NULL)
    {
        return dmError(DMERR_MALLOC,
            "Could not allocate memory for string '%s'!\n",
            blockName);
    }

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


/* Print out an ASCII presentation of memory map
 */
#define BOX_WIDTH 60
#define TO_STR1(x) #x
#define TO_STR(x) TO_STR1(x)

void memPrintLine(FILE *fh)
{
    fputs("              +", fh);
    for (int i = 0; i < BOX_WIDTH + 2; i++)
        fputc('-', fh);
    fputs("+\n", fh);
}

void memPrintEmpty(FILE *fh, const int n)
{
    for (int i = 0; i < n; i++)
    {
        fputs("              | ", fh);
        for (int i = 0; i < BOX_WIDTH; i++)
            fputc(' ', fh);
        fputs(" |\n", fh);
    }
}

void memPrintBox(FILE *fh, const int bsize, const int bstart, const int bend, const char *bdesc)
{
    int bksize = bsize / (1024 * 2);
    if (bksize > 1) memPrintEmpty(fh, bksize);
    fprintf(fh, "$%.4x - $%.4x | %-" TO_STR(BOX_WIDTH) "s |\n", bstart, bend, bdesc);
    if (bksize > 1) memPrintEmpty(fh, bksize);
    memPrintLine(fh);
}

void dmDescribeMemory(FILE *fh)
{
    DMMemBlock *bprev = NULL;

    memPrintLine(fh);

    for (int i = 0; i < nmemBlocks; i++)
    {
        DMMemBlock *bcurr = &memBlocks[i];
        char bdesc[128];
        const char *btype;
        int bsize;

        // Check for empty, unreserved areas
        if (bprev != NULL && (bsize = (bcurr->start - 1) - (bprev->end + 1) + 1) > 1)
        {
            snprintf(bdesc, sizeof(bdesc), "EMPTY (%d)", bsize);
            memPrintBox(fh, bsize, bprev->end + 1, bcurr->start - 1, bdesc);
        }
        bprev = bcurr;

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

        bsize = bcurr->end - bcurr->start + 1;
        snprintf(bdesc, sizeof(bdesc), "%.40s (%s, %d)", bcurr->name, btype, bsize);
        memPrintBox(fh, bsize, bcurr->start, bcurr->end, bdesc);
    }
}


/*
 * The main program
 */
int main(int argc, char *argv[])
{
    FILE *outFile = NULL;
    BOOL hasOverlaps;
    int res = DMERR_OK, startAddr, endAddr, dataSize, totalSize;

    dmInitProg("objlink", "Simple file-linker", "0.83", NULL, NULL);

    // Parse arguments
    if (!dmArgsProcess(argc, argv, optList, optListN,
        argHandleOpt, NULL, OPTH_BAILOUT))
        goto out;

    if (nsrcFiles < 1)
    {
        dmErrorMsg("Nothing to do. (try --help)\n");
        goto out;
    }

    // Warn about overlaps if enabled
    if (optAllowOverlap)
        dmMsg(-1, "WARNING: Overlapping data has been allowed!\n");

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

    if ((memory = (Uint8 *) dmMalloc(memModel->size + 32)) == NULL)
    {
        dmErrorMsg("Could not allocate memory for memory model!\n");
        goto out;
    }

    // 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 (int i = memModel->size / sizeof(*mp); i; i--)
        {
            *mp++ = optInitValue;
        }
    }
    else
    {
        Uint32 *mp = (Uint32 *) memory;
        dmPrint(1, "DWORD 0x%.8x\n", optInitValue);
        for (int i = memModel->size / sizeof(*mp); i; i--)
        {
            *mp++ = optInitValue;
        }
    }

    // Load the datafiles
    for (int 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 (int i = 0; i < memModel->nmemBlocks; i++)
    {
        if ((res = dmReserveMemBlock(
            memModel->memBlocks[i].start,
            memModel->memBlocks[i].end,
            memModel->memBlocks[i].name,
            memModel->memBlocks[i].type)) != DMERR_OK)
            goto out;
    }

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

    // Check for overlapping conflicts
    hasOverlaps = FALSE;
    for (int bk1 = 0; bk1 < nmemBlocks; bk1++)
    for (int bk2 = 0; bk2 < nmemBlocks; bk2++)
    if (bk1 != bk2)
    {
        DMMemBlock *mb1 = &memBlocks[bk1],
                   *mb2 = &memBlocks[bk2];

        if (mb1->type != MTYPE_RES)
            continue;

        // Check for per-file conflicts
        if ((mb2->start >= mb1->start && mb2->start <= mb1->end) ||
            (mb2->end >= mb1->start && mb2->end <= mb1->end))
        {
            dmErrorMsg("* '%s' and '%s' overlap ($%.4x-$%.4x  vs  $%.4x-$%.4x)\n",
                mb1->name, mb2->name, mb1->start,
                mb1->end, mb2->start, mb2->end);

            hasOverlaps = TRUE;
        }
    }

    if (!optAllowOverlap && hasOverlaps)
    {
        dmErrorMsg("Error occured, overlaps not allowed.\n");
        goto out;
    }

    // Find out start and end-addresses
    startAddr = memModel->size;
    totalSize = endAddr = 0;
    for (int 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)
    {
        dmErrorMsg("Invalid saveblock addresses (start=$%.4x, end=$%.4x)!\n",
            startAddr, endAddr);
        goto out;
    }

    // Output linkfile
    if (optLinkFileName)
    {
        dmMsg(1, "Writing linkfile to '%s'\n", optLinkFileName);
        if ((outFile = fopen(optLinkFileName, "wb")) == NULL)
        {
            res = dmGetErrno();
            dmErrorMsg("Error creating file '%s': %s.\n",
                optLinkFileName, dmErrorStr(res));
            goto out;
        }

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

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

        fclose(outFile);
    }

    // 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)
    {
        outFile = stdout;
        dmPrint(1, "...\n");
    }
    else if ((outFile = fopen(optDestName, "wb")) == NULL)
    {
        res = dmGetErrno();
        dmErrorMsg("Error creating output file '%s': %s.\n",
            optDestName, dmErrorStr(res));
        goto out;
    }
    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(outFile, optLoadAddress);
    }
    else
    if (optLoadAddress == LA_AUTO)
    {
        dmMsg(1, "Using automatic loading address $%.4x\n", startAddr);
        dm_fwrite_le16(outFile, startAddr);
    }
    else
    {
        dmMsg(1, "Writing raw output, without loading address.\n");
    }

    // Save the data
    if (fwrite(&memory[startAddr], dataSize, 1, outFile) < 1)
    {
        res = dmGetErrno();
        dmErrorMsg(
            "Error writing to file: %s.\n",
            dmErrorStr(res));
        goto out;
    }

    // Describe the memory layout
    if (optDescribe)
        dmDescribeMemory(outFile != stdout ? stdout : stderr);

out:
    if (outFile != NULL && outFile != stdout)
        fclose(outFile);

    return res;
}