view tools/objlink.c @ 2576:812b16ee49db

I had been living under apparent false impression that "realfft.c" on which the FFT implementation in DMLIB was basically copied from was released in public domain at some point, but it could very well be that it never was. Correct license is (or seems to be) GNU GPL. Thus I removing the code from DMLIB, and profusely apologize to the author, Philip Van Baren. It was never my intention to distribute code based on his original work under a more liberal license than originally intended.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 11 Mar 2022 16:32:50 +0200
parents d56a0e86067a
children 9807ae37ad69
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
{
    int noverlaps;
    int overlaps[SET_MAX_FILENAMES];
} DMMemBlockOverlap;


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];
DMMemBlockOverlap memBlockOverlaps[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': %s.\n",
            filename, dmErrorStr(res));
        goto out;
    }

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

    // Get loading address
    if (!dm_fread_le16(fh, &tmpAddr))
    {
        res = dmGetErrno();
        dmErrorMsg("Error reading input file '%s': %s.\n",
            filename, 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: %s.\n",
            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': %s.\n",
            filename, 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: %s.\n",
            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);
    }
}


int dmAddOverlap(DMMemBlockOverlap *olp, const int index)
{
    if (olp->noverlaps < SET_MAX_FILENAMES)
    {
        olp->overlaps[olp->noverlaps++] = index;
        return DMERR_OK;
    }
    else
    {
        return dmError(DMERR_BOUNDS,
            "Too many memory block overlaps (%d >= %d).\n",
            olp->noverlaps, SET_MAX_FILENAMES);
    }
}

/*
 * 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)
    {
        argShowHelp();
        res = dmError(DMERR_INVALID_ARGS,
            "No input file(s) specified.\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)
    {
        res = dmError(DMERR_MALLOC,
            "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)
    {
        DMMemBlockOverlap
            *olp1 = &memBlockOverlaps[bk1],
            *olp2 = &memBlockOverlaps[bk2];
        DMMemBlock
            *mb1 = &memBlocks[bk1],
            *mb2 = &memBlocks[bk2];
        BOOL found = FALSE;

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

        for (int no = 0; no < olp1->noverlaps; no++)
        {
            if (olp1->overlaps[no] == bk2)
            {
                found = TRUE;
                break;
            }
        }
        if (found)
            continue;

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

            if ((res = dmAddOverlap(olp1, bk2)) != DMERR_OK ||
                (res = dmAddOverlap(olp2, bk1)) != DMERR_OK)
                goto out;

            hasOverlaps = TRUE;
        }
    }

    if (!optAllowOverlap && hasOverlaps)
    {
        res = dmError(DMERR_BOUNDS,
            "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)
    {
        res = dmError(DMERR_BOUNDS,
            "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;
}