changeset 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 a0160ffdf7e5
children 37e65cdcc749
files Makefile Makefile.gen data2inc.c gfxconv.c lib64gfx.c lib64gfx.h objlink.c view64.c
diffstat 8 files changed, 4218 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Sat Nov 03 01:54:00 2012 +0200
+++ b/Makefile	Sat Nov 03 02:19:51 2012 +0200
@@ -3,9 +3,13 @@
 #
 SDL_CFLAGS=`sdl-config --cflags`
 SDL_LDFLAGS=`sdl-config --static-libs`
+
 TREMOR_CFLAGS=-I/usr/local/lib/
 TREMOR_LDFLAGS=/usr/local/lib/libvorbisidec.a /usr/lib/i386-linux-gnu/libogg.a
 
+LIBPNG_CFLAGS=`pkg-config --cflags libpng` -DHAVE_LIBPNG
+LIBPNG_CFLAGS=`pkg-config --libs libpng`
+
 RANLIB=ranlib
 
 DMLIB = ./
--- a/Makefile.gen	Sat Nov 03 01:54:00 2012 +0200
+++ b/Makefile.gen	Sat Nov 03 02:19:51 2012 +0200
@@ -189,11 +189,14 @@
 
 ### Dependancies
 ifeq ($(DM_BUILD_TOOLS),yes)
+DMLIB_OBJS += lib64gfx.o
 ifeq ($(DM_USE_STDIO),yes)
+BINARIES+= objlink data2inc
 ifeq ($(SUP_MODLOAD),yes)
 BINARIES+= viewmod mod2wav testpl
+ifeq ($(DM_GFX_BLITS),yes)
+BINARIES+= view64
 ifeq ($(DM_GFX_BM_TEXT),yes)
-ifeq ($(DM_GFX_BLITS),yes)
 ifeq ($(DM_GFX_MISC),yes)
 BINARIES+= ppl
 endif
@@ -366,6 +369,24 @@
 	@echo " LINK $+"
 	@$(CC) -o $@ $(filter %.o %.a,$+) $(DM_LDFLAGS) $(SDL_LDFLAGS)
 
+
+$(BINPATH)objlink$(EXEEXT): $(OBJPATH)objlink.o $(DMLIB_A)
+	@echo " LINK $+"
+	@$(CC) -o $@ $(filter %.o %.a,$+) $(DM_LDFLAGS)
+
+$(BINPATH)data2inc$(EXEEXT): $(OBJPATH)data2inc.o $(DMLIB_A)
+	@echo " LINK $+"
+	@$(CC) -o $@ $(filter %.o %.a,$+) $(DM_LDFLAGS)
+
+$(BINPATH)gfxconv$(EXEEXT): $(OBJPATH)gfxconv.o $(DMLIB_A)
+	@echo " LINK $+"
+	@$(CC) -o $@ $(filter %.o %.a,$+) $(DM_LDFLAGS) $(LIBPNG_CFLAGS) $(LIBPNG_LDFLAGS)
+
+$(BINPATH)view64$(EXEEXT): $(OBJPATH)view64.o $(DMLIB_A)
+	@echo " LINK $+"
+	@$(CC) -o $@ $(filter %.o %.a,$+) $(DM_LDFLAGS) $(SDL_LDFLAGS)
+
+
 ###
 ### Editor targets
 ###
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data2inc.c	Sat Nov 03 02:19:51 2012 +0200
@@ -0,0 +1,404 @@
+/*
+ * data2inc - Convert binary data to "C"-source or XA-compatible include file
+ * Programmed and designed by Matti 'ccr' Hamalainen
+ * (C) Copyright 2003,2009-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 "dmmutex.h"
+
+#define RA_LINEBUF    (16)
+
+enum
+{
+    FMT_AUTO = 0,
+    FMT_C,
+    FMT_ASM
+};
+
+char    *optInFilename = NULL,
+        *optOutFilename = NULL,
+        *optObjName = "default_object",
+        *optDataType = NULL,
+        *optAddLine = NULL;
+
+int     optIndentation = -1;
+int     optFormat = FMT_AUTO;
+BOOL    optHexMode = FALSE,
+        optQuiet = FALSE,
+        optExtraData = FALSE,
+        optFormatting = TRUE;
+
+
+void (*writeHeader) (FILE *, char *) = NULL;
+void (*writeDecl) (FILE *, size_t, char *) = NULL;
+void (*writeData) (FILE *, Uint8 *, size_t) = NULL;
+void (*writeFooter) (FILE *, size_t, char *) = NULL;
+
+
+static DMOptArg optList[] =
+{
+    {  0, '?', "help",           "Show this help", OPT_NONE },
+    {  4, 'A', "format-asm",     "Output in XA-compatible asm", OPT_NONE },
+    {  5, 'C', "format-c",       "Output in ANSI C", OPT_NONE },
+    {  1, 'n', "name",           "Set object name", OPT_ARGREQ },
+    {  2, 't', "type",           "Set datatype (unsigned char/byte)", OPT_ARGREQ },
+    {  3, 'a', "add-line",       "Add this line to start of file", OPT_ARGREQ },
+    {  6, 'x', "hexadecimal",    "Use hexadecimal output", OPT_NONE },
+    {  7, 'q', "quiet",          "Do not add comments", OPT_NONE },
+    {  8, 'f', "no-formatting",  "Disable additional output formatting", OPT_NONE },
+    {  9, 'i', "indentation",    "Set indentation (negative value = tab)", OPT_ARGREQ },
+    { 10, 'e', "extra-data",     "Add object end labels and size in asm output", OPT_NONE },
+};
+
+static const int optListN = sizeof(optList) / sizeof(optList[0]);
+
+
+void argShowHelp()
+{
+    dmPrintBanner(stdout, dmProgName,
+        "[options] [sourcefile] [destfile]");
+
+    dmArgsPrintHelp(stdout, optList, optListN);
+    
+    printf(
+    "\n"
+    "To convert a data file to a C structure using 'Uint8' as type:\n"
+    "$ data2inc -C -n variable_name -t Uint8 input.bin output.h\n"
+    "\n"
+    );
+}
+
+
+BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
+{
+    switch (optN)
+    {
+        case 0:
+            argShowHelp();
+            exit(0);
+            break;
+
+        case 1:
+            optObjName = optArg;
+            break;
+
+        case 2:
+            optDataType = optArg;
+            break;
+
+        case 3:
+            optAddLine = optArg;
+            break;
+
+        case 4:
+            optFormat = FMT_ASM;
+            break;
+        case 5:
+            optFormat = FMT_C;
+            break;
+        case 6:
+            optHexMode = TRUE;
+            break;
+        case 7:
+            optQuiet = TRUE;
+            break;
+        case 8:
+            optFormatting = FALSE;
+            break;
+
+        case 9:
+            optIndentation = atoi(optArg);
+            break;
+
+        case 10:
+            optExtraData = TRUE;
+            break;
+
+        default:
+            dmError("Unknown option '%s'.\n", currArg);
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+BOOL argHandleFile(char * currArg)
+{
+    if (!optInFilename)
+        optInFilename = currArg;
+    else
+    if (!optOutFilename)
+        optOutFilename = currArg;
+    else
+        dmError("Source and destination filenames already specified, extraneous argument '%s'.\n", currArg);
+
+    return TRUE;
+}
+
+
+/* Assembler include data output functions
+ */
+void writeHeader_ASM(FILE * f, char *name)
+{
+    if (name)
+        fprintf(f, "; '%s'", name);
+    else
+        fprintf(f, "; Generated");
+    fprintf(f, " by %s v%s\n",
+        dmProgName, dmProgVersion);
+}
+
+void writeDecl_ASM(FILE * f, size_t len, char *name)
+{
+    if (optExtraData)
+        fprintf(f, "%s_size = %u\n", name, len);
+    fprintf(f, "%s:\n", name);
+}
+
+void writeData_ASM(FILE * f, Uint8 * buf, size_t len)
+{
+    size_t i;
+
+    fprintf(f, "%s ", optDataType);
+    for (i = 0; i < len; i++)
+    {
+        if (optFormatting)
+        {
+            if (optHexMode)
+                fprintf(f, "$%.2x", buf[i]);
+            else
+                fprintf(f, "%3d", buf[i]);
+        }
+        else
+        {
+            if (optHexMode)
+                fprintf(f, "$%x", buf[i]);
+            else
+                fprintf(f, "%d", buf[i]);
+        }
+
+        if (i < (len - 1))
+            fprintf(f, ",");
+    }
+}
+
+void writeFooter_ASM(FILE * f, size_t len, char *name)
+{
+    (void) len;
+    if (optExtraData)
+        fprintf(f, "%s_end: \n", name);
+    else
+        fprintf(f, "\n");
+}
+
+
+/* ANSI-C include data output functions
+ */
+void writeHeader_C(FILE * f, char *name)
+{
+    if (name)
+        fprintf(f, "/* '%s' generated", name);
+    else
+        fprintf(f, "/* Generated");
+
+    fprintf(f, " by %s v%s\n" " */\n",
+        dmProgName, dmProgVersion);
+}
+
+void writeDecl_C(FILE * f, size_t len, char *name)
+{
+    fprintf(f, "const %s %s[%u] = {\n",
+        optDataType, name, len);
+
+    printf("extern const %s %s[%u];\n",
+        optDataType, name, len);
+}
+
+void writeData_C(FILE * f, Uint8 * buf, size_t len)
+{
+    size_t i;
+
+    for (i = 0; i < len; i++)
+    {
+        if (optFormatting)
+        {
+            if (optHexMode)
+                fprintf(f, "0x%.2x", buf[i]);
+            else
+                fprintf(f, "%3d", buf[i]);
+        }
+        else
+        {
+            if (optHexMode)
+                fprintf(f, "0x%x", buf[i]);
+            else
+                fprintf(f, "%d", buf[i]);
+        }
+
+        fprintf(f, ",");
+    }
+}
+
+void writeFooter_C(FILE * f, size_t len, char *name)
+{
+    (void) len;
+    (void) name;
+    fprintf(f, "};\n");
+}
+
+
+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;
+}
+
+
+int main(int argc, char *argv[])
+{
+    FILE *sfile = NULL, *dfile = NULL;
+    off_t inSize;
+    Uint8 inBuf[RA_LINEBUF];
+    int tmpRes;
+
+    /* Initialize */
+    dmInitProg("data2inc", "Data to include converter", "0.6", NULL, NULL);
+    dmVerbosity = 0;
+
+    /* Parse arguments */
+    if (!dmArgsProcess(argc, argv, optList, optListN,
+        argHandleOpt, argHandleFile, TRUE))
+        exit(1);
+
+    /* Determine output type, if not specified */
+    if (optFormat == FMT_AUTO)
+    {
+        char *dext;
+
+        if (optOutFilename == NULL)
+        {
+            dmError("Output format not specified and no output filename given (try --help)\n");
+            exit(1);
+        }
+
+        /* Check filename extension */
+        dext = strrchr(optOutFilename, '.');
+        if (dext)
+        {
+            dext++;
+            if (!strcasecmp(dext, "c") || !strcasecmp(dext, "h") ||
+                !strcasecmp(dext, "cc") || !strcasecmp(dext, "cpp") ||
+                !strcasecmp(dext, "hpp") || !strcasecmp(dext, "c++"))
+                optFormat = FMT_C;
+            else
+                optFormat = FMT_ASM;
+        } else
+            optFormat = FMT_ASM;
+    }
+
+    /* Set functions */
+    switch (optFormat)
+    {
+        case FMT_ASM:
+            if (!optDataType)
+                optDataType = ".byte";
+
+            writeHeader = writeHeader_ASM;
+            writeDecl = writeDecl_ASM;
+            writeData = writeData_ASM;
+            writeFooter = writeFooter_ASM;
+            break;
+
+        case FMT_C:
+            if (!optDataType)
+                optDataType = "unsigned char";
+
+            writeHeader = writeHeader_C;
+            writeDecl = writeDecl_C;
+            writeData = writeData_C;
+            writeFooter = writeFooter_C;
+            break;
+
+        case FMT_AUTO:
+        default:
+            dmError("Internal error, FMT_AUTO at output function init.\n");
+            exit(2);
+    }
+
+    /* Open the files */
+    if (optInFilename == NULL)
+        sfile = stdin;
+    else
+    if ((sfile = fopen(optInFilename, "rb")) == NULL)
+    {
+        tmpRes = errno;
+        dmError("Error opening input file '%s'. (%s)\n",
+            optInFilename, strerror(tmpRes));
+        exit(3);
+    }
+
+    if (optOutFilename == NULL)
+        dfile = stdout;
+    else
+    if ((dfile = fopen(optOutFilename, "wa")) == NULL)
+    {
+        tmpRes = errno;
+        dmError("Error creating output file '%s'. (%s)\n",
+            optOutFilename, strerror(tmpRes));
+        exit(4);
+    }
+
+    /* Get sourcefile size */
+    inSize = dmGetFileSize(sfile);
+
+    /* Output header */
+    if (!optQuiet)
+        writeHeader(dfile, optOutFilename);
+
+    if (optAddLine)
+        fprintf(dfile, "%s\n", optAddLine);
+
+    /* Output declaration */
+    writeDecl(dfile, inSize, optObjName);
+
+    /* Output data */
+    while (!feof(sfile))
+    {
+        tmpRes = fread(inBuf, sizeof(Uint8), RA_LINEBUF, sfile);
+        if (tmpRes > 0)
+        {
+            if (optIndentation < 0)
+                fprintf(dfile, "\t");
+            else
+            if (optIndentation > 0)
+            {
+                int i;
+                for (i = 0; i < optIndentation; i++)
+                    fputs(" ", dfile);
+            }
+
+            writeData(dfile, inBuf, tmpRes);
+
+            fprintf(dfile, "\n");
+        }
+    }
+
+
+    /* Output footer */
+    writeFooter(dfile, inSize, optObjName);
+
+    /* Exit */
+    fclose(sfile);
+    fclose(dfile);
+
+    exit(0);
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gfxconv.c	Sat Nov 03 02:19:51 2012 +0200
@@ -0,0 +1,1630 @@
+/*
+ * gfxconv - Convert various graphics formats
+ * Programmed and designed by Matti 'ccr' Hamalainen
+ * (C) Copyright 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"
+#include "lib64gfx.h"
+
+//#define UNFINISHED 1
+
+#ifdef HAVE_LIBPNG
+#include <png.h>
+#endif
+
+enum
+{
+    INFMT_AUTO = 0,
+    INFMT_CHAR,
+    INFMT_SPRITE,
+    INFMT_BITMAP,
+    INFMT_IMAGE,
+};
+
+enum
+{
+    OUTFMT_ASCII,
+    OUTFMT_ANSI,
+    OUTFMT_PNG,
+    OUTFMT_PPM,
+    OUTFMT_PCX,
+    OUTFMT_ARAW,
+
+#ifdef UNFINISHED
+    OUTFMT_SPRITE,
+    OUTFMT_CHAR,
+#endif
+
+    OUTFMT_LAST
+};
+
+char * outFormatList[OUTFMT_LAST] =
+{
+    "ascii",
+    "ansi",
+    "png",
+    "ppm",
+    "pcx",
+    "araw",
+#ifdef UNFINISHED
+    "spr",
+    "char",
+#endif
+};
+
+static const int noutFormatList = sizeof(outFormatList) / sizeof(outFormatList[0]);
+
+
+#define ASC_NBITS    8
+#define ASC_NCOLORS  4
+static const char dmASCIIPalette[ASC_NCOLORS] = ".:X#";
+
+
+char    *optInFilename = NULL,
+        *optOutFilename = NULL;
+int     optInFormat = INFMT_AUTO,
+        optOutFormat = OUTFMT_ASCII,
+        optItemCount = -1,
+        optScale = 2,
+        optPlanedWidth = 1,
+        optBPP = 4;
+int     optInSkip = 0;
+BOOL    optInMulticolor = FALSE,
+        optSequential = FALSE,
+        optPaletted = FALSE;
+int     optColors[C64_MAX_COLORS];
+
+
+static DMOptArg optList[] =
+{
+    { 0, '?', "help",         "Show this help", OPT_NONE },
+    { 3, 'o', "output",       "Output filename", OPT_ARGREQ },
+    { 1, 'i', "informat",     "Set input format ([s]prite, [c]har, [b]itmap)", OPT_ARGREQ },
+    { 2, 'm', "multicolor",   "Input is multicolor", OPT_NONE },
+    { 4, 's', "skip",         "Skip bytes in input", OPT_ARGREQ },
+    { 5, 'f', "format",       "Output format (see list below)", OPT_ARGREQ },
+    { 8, 'q', "sequential",   "Output sequential files (image output only)", OPT_NONE },
+    { 6, 'c', "colormap",     "Color mappings (see below for information)", OPT_ARGREQ },
+    { 7, 'n', "numitems",     "How many 'items' to view (default: all)", OPT_ARGREQ },
+    { 9, 'S', "scale",        "Scale output by x (image output only)", OPT_ARGREQ },
+#ifdef UNFINISHED
+    {10, 'b', "bformat",      "Force input bitmap format (see below)", OPT_ARGREQ },
+#endif
+    {11, 'w', "width",        "Item width (number of items per row, min 1)", OPT_ARGREQ },
+    {12, 'P', "paletted",     "Use indexed/paletted output (png, pcx output only)", OPT_NONE },
+    {13, 'b', "bpp",          "Bits per pixel (certain image output formats)", OPT_ARGREQ },
+};
+
+static const int optListN = sizeof(optList) / sizeof(optList[0]);
+
+
+void argShowHelp()
+{
+    int i;
+
+    dmPrintBanner(stdout, dmProgName, "[options] <input file>");
+    dmArgsPrintHelp(stdout, optList, optListN);
+
+    printf("\nAvailable output formats: ");
+    for (i = 0; i < noutFormatList; i++)
+    {
+        printf("%s", outFormatList[i]);
+        if (i < noutFormatList - 1)
+            printf(", ");
+        else
+            printf("\n");
+    }
+
+#ifdef UNFINISHED
+    printf("\nAvailable bitmap formats:\n");
+    for (i = 0; i < ndmC64ImageFormats; i++)
+    {
+        DM64ImageFormat *fmt = &dmC64ImageFormats[i];
+        printf("%3d | %-5s | %-15s | %s\n",
+            i, fmt->extension,
+            dmC64ImageTypeNames[fmt->type],
+            fmt->name);
+    }
+#endif
+
+    printf(
+    "\n"
+    "Color map definitions are used for ANSI, PCX, PPM and PNG output, to declare what\n"
+    "output colors of the C64 palette are used for each single color/multi color\n"
+    "bit-combination. For example, if the input is multi color sprite or char,\n"
+    "you can define colors like: -c 0,8,3,15 .. for single color: -c 0,1\n"
+    "The numbers are palette indexes, and the order is for bit(pair)-values\n"
+    "00, 01, 10, 11 (multi color) and 0, 1 (single color). NOTICE! 255 is the\n"
+    "special color that can be used for transparency.\n"
+    );
+}
+
+
+BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
+{
+    switch (optN)
+    {
+        case 0:
+            argShowHelp();
+            exit(0);
+            break;
+
+        case 1:
+            switch (tolower(optArg[0]))
+            {
+                case 's':
+                    optInFormat = INFMT_SPRITE;
+                    break;
+                case 'c':
+                    optInFormat = INFMT_CHAR;
+                    break;
+                default:
+                    dmError("Invalid input format '%s'.\n", optArg);
+                    return FALSE;
+            }
+            break;
+        
+        case 2:
+            optInMulticolor = TRUE;
+            break;
+
+        case 3:
+            optOutFilename = optArg;
+            break;
+        
+        case 4:
+            if (!dmGetIntVal(optArg, &optInSkip))
+            {
+                dmError("Invalid skip value argument '%s'.\n", optArg);
+                return FALSE;
+            }
+            break;
+
+        case 5:
+            {
+                int i, format = -1;
+                for (i = 0; i < noutFormatList; i++)
+                if (strcasecmp(optArg, outFormatList[i]) == 0)
+                {
+                    format = i;
+                    break;
+                }
+                
+                if (format < 0)
+                {
+                    dmError("Invalid output format '%s'.\n", optArg);
+                    return FALSE;
+                }
+                
+                optOutFormat = format;
+            }
+            break;
+
+        case 6:
+            {
+                int index = 0, tmp;
+                char *s, *p = optArg;
+
+                while (index < C64_MAX_COLORS && *p != 0 && (s = strchr(p, ':')) != NULL)
+                {
+                    *s = 0;
+                    if (sscanf(p, "%d", &tmp) == 1)
+                        optColors[index++] = tmp;
+                    p = s + 1;
+                }
+                
+                if (*p && index < C64_MAX_COLORS)
+                {
+                    if (sscanf(p, "%d", &tmp) == 1)
+                        optColors[index++] = tmp;
+                }
+                
+                dmMsg(1, "Set color table: ");
+                for (tmp = 0; tmp < index; tmp++)
+                {
+                    dmPrint(1, "[%d:%d]%s",
+                        tmp, optColors[tmp],
+                        (tmp < index - 1) ? ", " : "");
+                }
+                dmPrint(1, "\n");
+            }
+            break;
+
+        case 7:
+            if (sscanf(optArg, "%d", &optItemCount) != 1)
+            {
+                dmError("Invalid count value argument '%s'.\n", optArg);
+                return FALSE;
+            }
+            break;
+
+        case 8:
+            optSequential = TRUE;
+            break;
+
+        case 9:
+            {
+                int tmp = atoi(optArg);
+                if (tmp < 1 || tmp > 50)
+                {
+                    dmError("Invalid scale value '%s'.\n", optArg);
+                    return FALSE;
+                }
+                optScale = tmp;
+            }
+            break;
+
+        case 11:
+            {
+                int tmp = atoi(optArg);
+                if (tmp < 1 || tmp > 512)
+                {
+                    dmError("Invalid width value '%s'.\n", optArg);
+                    return FALSE;
+                }
+                optPlanedWidth = tmp;
+            }
+            break;
+
+        case 12:
+            optPaletted = TRUE;
+            break;
+
+        default:
+            dmError("Unknown option '%s'.\n", currArg);
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+BOOL argHandleFile(char *currArg)
+{
+    if (!optInFilename)
+        optInFilename = currArg;
+    else
+    {
+        dmError("Source filename already specified, extraneous argument '%s'.\n",
+             currArg);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+void dmPrintByte(FILE *out, int byte, int format, BOOL multicolor)
+{
+    int i;
+    
+    if (multicolor)
+    {
+        for (i = ASC_NBITS; i; i -= 2)
+        {
+            int val = (byte & (3ULL << (i - 2))) >> (i - 2);
+            char ch;
+            switch (format)
+            {
+                case OUTFMT_ASCII:
+                    ch = dmASCIIPalette[val];
+                    fprintf(out, "%c%c", ch, ch);
+                    break;
+                case OUTFMT_ANSI:
+                    fprintf(out, "%c[0;%d;%dm##%c[0m",
+                        0x1b,
+                        1,
+                        31 + optColors[val],
+                        0x1b);
+                    break;
+            }
+        }
+    }
+    else
+    {
+        for (i = ASC_NBITS; i; i--)
+        {
+            int val = (byte & (1ULL << (i - 1))) >> (i - 1);
+            char ch;
+            switch (format)
+            {
+                case OUTFMT_ASCII:
+                    ch = val ? '#' : '.';
+                    fputc(ch, out);
+                    break;
+                case OUTFMT_ANSI:
+                    fprintf(out, "%c[0;%d;%dm %c[0m",
+                        0x1b,
+                        1,
+                        31 + optColors[val],
+                        0x1b);
+                    break;
+            }
+        }
+    }
+}
+
+
+void dmDumpCharASCII(FILE *outFile, const uint8_t *buf, int *offs, int format, BOOL multicolor)
+{
+    int yc;
+
+    for (yc = 0; yc < C64_CHR_HEIGHT; yc++)
+    {
+        fprintf(outFile, "%04x : ", *offs);
+        dmPrintByte(outFile, buf[yc], format, multicolor);
+        fprintf(outFile, "\n");
+        (*offs)++;
+    }
+}
+
+
+void dmDumpSpriteASCII(FILE *outFile, const uint8_t *buf, int *offs, int format, BOOL multicolor)
+{
+    int bufOffs, xc, yc;
+
+    for (bufOffs = yc = 0; yc < C64_SPR_HEIGHT; yc++)
+    {
+        fprintf(outFile, "%04x : ", *offs);
+        for (xc = 0; xc < C64_SPR_WIDTH; xc++)
+        {
+            dmPrintByte(outFile, buf[bufOffs], format, multicolor);
+            fprintf(outFile, " ");
+            bufOffs++;
+            (*offs)++;
+        }
+        fprintf(outFile, "\n");
+    }
+    (*offs)++;
+}
+
+
+int dmWriteImageData(DMImage *img, void *cbdata, BOOL (*writeRowCB)(void *, uint8_t *, size_t), int scale, int format)
+{
+    int x, y, yscale, xscale, res = 0, rowSize, rowWidth;
+    uint8_t *row = NULL;
+
+    // Allocate memory for row buffer
+    rowWidth = img->width * scale;
+    rowSize = rowWidth * dmImageGetBytesPerPixel(format);
+
+    if ((row = dmMalloc(rowSize + 16)) == NULL)
+    {
+        res = -16;
+        goto done;
+    }
+
+    // Generate the image
+    for (y = 0; y < img->height; y++)
+    {
+        uint8_t *ptr = row,
+                *ptr1 = row,
+                *ptr2 = ptr1 + rowWidth,
+                *ptr3 = ptr2 + rowWidth;
+
+        for (x = 0; x < img->width; x++)
+        {
+            uint8_t c = img->data[(y * img->pitch) + x], qr, qg, qb, qa;
+            switch (format)
+            {
+                case DM_IFMT_PALETTE:
+                    for (xscale = 0; xscale < scale; xscale++)
+                        *ptr++ = c;
+                    break;
+
+                case DM_IFMT_RGBA:
+                    qr = img->pal[c].r;
+                    qg = img->pal[c].g;
+                    qb = img->pal[c].b;
+                    qa = (c == img->ctrans) ? 0 : 255;
+                
+                    for (xscale = 0; xscale < scale; xscale++)
+                    {
+                        *ptr++ = qr;
+                        *ptr++ = qg;
+                        *ptr++ = qb;
+                        *ptr++ = qa;
+                    }
+                    break;
+
+                case DM_IFMT_RGB:
+                    qr = img->pal[c].r;
+                    qg = img->pal[c].g;
+                    qb = img->pal[c].b;
+                
+                    for (xscale = 0; xscale < scale; xscale++)
+                    {
+                        *ptr++ = qr;
+                        *ptr++ = qg;
+                        *ptr++ = qb;
+                    }
+                    break;
+
+                case DM_IFMT_RGB_PLANE:
+                    qr = img->pal[c].r;
+                    qg = img->pal[c].g;
+                    qb = img->pal[c].b;
+                
+                    for (xscale = 0; xscale < scale; xscale++)
+                    {
+                        *ptr1++ = qr;
+                        *ptr2++ = qg;
+                        *ptr3++ = qb;
+                    }
+                    break;
+            }
+        }
+
+        for (yscale = 0; yscale < scale; yscale++)
+        {
+            if (!writeRowCB(cbdata, row, rowSize))
+            {
+                res = -32;
+                goto done;
+            }
+        }
+    }
+
+done:
+    dmFree(row);    
+    return res;
+}
+
+
+#define DMCOL(x) (((x) >> 4) & 0xf)
+
+int dmWriteIFFMasterRAWPalette(const char *filename, DMImage *img, int ncolors)
+{
+    FILE *fp;
+    int i;
+
+    if ((fp = fopen(filename, "w")) == NULL)
+    {
+        dmError("IFFMasterRAW: Could not open file '%s' for writing.\n", filename);
+        return -15;
+    }
+
+    for (i = 0; i < ncolors; i++)
+    {
+        int color;
+        if (i < img->ncolors)
+        {
+            color = (DMCOL(img->pal[i].r) << 8) |
+                    (DMCOL(img->pal[i].g) << 4) |
+                    (DMCOL(img->pal[i].b));
+        }
+        else
+            color = 0;
+
+        fprintf(fp, "\tdc.w $%04X\n", color);
+    }
+
+    return 0;    
+}
+
+
+typedef struct
+{
+    int bpp;
+    DMImage *img;
+    FILE *fp;
+} DMRawData;
+
+
+static BOOL dmWriteIFFMasterRAWRow(void *cbdata, uint8_t *row, size_t len)
+{
+    DMRawData *raw = (DMRawData *) cbdata;
+    size_t i;
+
+    for (i = 0; i < len; i++)
+    {
+    }
+
+    return fwrite(row, sizeof(uint8_t), len, raw->fp) == len;
+}
+
+
+int dmWriteIFFMasterRAWImageFILE(FILE *fp, DMImage *img, int scale, int bpp)
+{
+    DMRawData raw;
+    
+    raw.fp  = fp;
+    raw.img = img;
+    raw.bpp = bpp;
+
+    return dmWriteImageData(img, (void *) &raw, dmWriteIFFMasterRAWRow, scale, DM_IFMT_PALETTE);
+}
+
+int dmWriteIFFMasterRAWImage(const char *filename, DMImage *img, int scale, int bpp)
+{
+    FILE *fp;
+    int res;
+
+    if ((fp = fopen(filename, "wb")) == NULL)
+    {
+        dmError("IFFMasterRAW: Could not open file '%s' for writing.\n", filename);
+        return -15;
+    }
+
+    res = dmWriteIFFMasterRAWImageFILE(fp, img, scale, bpp);
+
+    fclose(fp);
+    return res;
+}
+
+
+static BOOL dmWritePPMRow(void *cbdata, uint8_t *row, size_t len)
+{
+    return fwrite(row, sizeof(uint8_t), len, (FILE *) cbdata) == len;
+}
+
+
+int dmWritePPMImageFILE(FILE *fp, DMImage *img, int scale)
+{
+    // Write PPM header
+    fprintf(fp,
+        "P6\n%d %d\n255\n",
+        img->width * scale, img->height * scale);
+
+    // Write image data
+    return dmWriteImageData(img, (void *) fp, dmWritePPMRow, scale, DM_IFMT_RGB);
+}
+
+
+int dmWritePPMImage(const char *filename, DMImage *img, int scale)
+{
+    FILE *fp;
+    int res;
+
+    // Create output file
+    if ((fp = fopen(filename, "wb")) == NULL)
+    {
+        dmError("PPM: could not open file '%s' for writing.\n", filename);
+        return -15;
+    }
+
+    res = dmWritePPMImageFILE(fp, img, scale);
+
+    fclose(fp);
+    return res;
+}
+
+
+#ifdef HAVE_LIBPNG
+static BOOL dmWritePNGRow(void *cbdata, uint8_t *row, size_t len)
+{
+    png_structp png_ptr = cbdata;
+    (void) len;
+
+    if (setjmp(png_jmpbuf(png_ptr)))
+        return FALSE;
+
+    png_write_row(png_ptr, row);
+
+    return TRUE;
+}
+
+
+int dmWritePNGImageFILE(FILE *fp, DMImage *img, int scale, int format)
+{
+    png_structp png_ptr = NULL;
+    png_infop info_ptr = NULL;
+    png_colorp palette = NULL;
+    int fmt;
+
+    // Create PNG structures
+    png_ptr = png_create_write_struct(
+        PNG_LIBPNG_VER_STRING,
+        NULL, NULL, NULL);
+
+    if (png_ptr == NULL)
+    {
+        dmError("PNG: png_create_write_struct() failed.\n");
+        goto error;
+    }
+    
+    info_ptr = png_create_info_struct(png_ptr);
+    if (info_ptr == NULL)
+    {
+        dmError("PNG: png_create_info_struct(%p) failed.\n", png_ptr);
+        goto error;
+    }
+    
+    if (setjmp(png_jmpbuf(png_ptr)))
+    {
+        dmError("PNG: Error during image writing..\n");
+        goto error;
+    }
+
+    png_init_io(png_ptr, fp);
+
+    // Write PNG header info
+    switch (format)
+    {
+        case DM_IFMT_PALETTE: fmt = PNG_COLOR_TYPE_PALETTE; break;
+        case DM_IFMT_RGB    : fmt = PNG_COLOR_TYPE_RGB; break;
+        case DM_IFMT_RGBA   : fmt = PNG_COLOR_TYPE_RGB_ALPHA; break;
+        default:
+            dmError("PNG: Internal error, unsupported image format %d.\n", format);
+            goto error;
+    }
+ 
+    png_set_IHDR(png_ptr, info_ptr,
+        img->width * scale,
+        img->height * scale,
+        8,                    /* bits per component */
+        fmt,
+        PNG_INTERLACE_NONE,
+        PNG_COMPRESSION_TYPE_DEFAULT,
+        PNG_FILTER_TYPE_DEFAULT);
+
+    // Palette
+    if (format == DM_IFMT_PALETTE)
+    {
+        int i;
+
+        palette = png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH * sizeof(png_color));
+        if (palette == NULL)
+        {
+            dmError("PNG: Could not allocate palette structure.");
+            goto error;
+        }
+        
+        memset(palette, 0, PNG_MAX_PALETTE_LENGTH * sizeof(png_color));
+
+        for (i = 0; i < img->ncolors; i++)
+        {
+            palette[i].red   = img->pal[i].r;
+            palette[i].green = img->pal[i].g;
+            palette[i].blue  = img->pal[i].b;
+        }
+
+        png_set_PLTE(png_ptr, info_ptr, palette, PNG_MAX_PALETTE_LENGTH);
+    }
+
+//    png_set_gAMA(png_ptr, info_ptr, 2.2);
+
+    png_write_info(png_ptr, info_ptr);
+
+
+    // Write compressed image data
+    dmWriteImageData(img, (void *) png_ptr, dmWritePNGRow, scale, format);
+
+    // Write footer
+    png_write_end(png_ptr, NULL);
+
+    png_free(png_ptr, palette);
+    palette = NULL;
+
+    // Deallocate shit
+    if (png_ptr && info_ptr)
+    {
+        png_destroy_write_struct(&png_ptr, &info_ptr);
+    }
+
+    return 0;
+
+error:
+    png_free(png_ptr, palette);
+    palette = NULL;
+
+    if (png_ptr && info_ptr)
+    {
+        png_destroy_write_struct(&png_ptr, &info_ptr);
+    }
+    return -15;
+}
+
+
+int dmWritePNGImage(const char *filename, DMImage *img, int scale, int format)
+{
+    int res;
+    FILE *fp;
+
+    if ((fp = fopen(filename, "wb")) == NULL)
+    {
+        dmError("PNG: could not open file '%s' for writing.\n", filename);
+        return -15;
+    }
+
+    res = dmWritePNGImageFILE(fp, img, scale, format);
+
+    fclose(fp);
+    return res;
+}
+#endif
+
+
+typedef struct
+{
+    uint8_t r,g,b;
+} DMPCXColor;
+
+
+typedef struct
+{
+    uint8_t manufacturer,
+            version,
+            encoding,
+            bpp;
+    uint16_t xmin, ymin, xmax, ymax;
+    uint16_t hres, vres;
+    DMPCXColor colormap[16];
+    uint8_t reserved;
+    uint8_t nplanes;
+    uint16_t bpl;
+    uint16_t palinfo;
+    uint8_t filler[58];
+} DMPCXHeader;
+
+typedef struct
+{
+    DMPCXHeader *header;
+    uint8_t *buf;
+    size_t bufLen, bufOffs;
+    int format;
+    FILE *fp;
+} DMPCXData;
+
+
+static inline uint8_t dmPCXGetByte(uint8_t *row, const size_t len, const size_t soffs)
+{
+    return (soffs < len) ? row[soffs] : 0;
+}
+
+static BOOL dmPCXFlush(DMPCXData *pcx)
+{
+    BOOL ret = fwrite(pcx->buf, sizeof(uint8_t), pcx->bufOffs, pcx->fp) == pcx->bufOffs;
+    pcx->bufOffs = 0;
+    return ret;
+}
+
+static inline BOOL dmPCXPutByte(DMPCXData *pcx, const uint8_t val)
+{
+    if (pcx->bufOffs < pcx->bufLen)
+    {
+        pcx->buf[pcx->bufOffs++] = val;
+        return TRUE;
+    }
+    else
+        return dmPCXFlush(pcx);
+}
+
+BOOL dmWritePCXRow(void *cbdata, uint8_t *row, size_t len)
+{
+    DMPCXData *pcx = (DMPCXData *) cbdata;
+    int plane;
+    size_t soffs = 0;
+    
+    for (plane = 0; plane < pcx->header->nplanes; plane++)
+    {
+        uint8_t data = dmPCXGetByte(row, len, soffs++),
+                count = 1;
+
+        pcx->bufOffs = 0;
+
+        while (soffs < pcx->header->bpl)
+        {
+            if (data == dmPCXGetByte(row, len, soffs) && count < 63)
+            {
+                count++;
+                soffs++;
+            }
+            else
+            {
+                if (count == 1 && (data & 0xC0) != 0xC0)
+                {
+                    if (!dmPCXPutByte(pcx, data))
+                        return FALSE;
+                }
+                else
+                {
+                    if (!dmPCXPutByte(pcx, 0xC0 | count) ||
+                        !dmPCXPutByte(pcx, data))
+                        return FALSE;
+                }
+
+                data = dmPCXGetByte(row, len, soffs++);
+                count = 1;
+            }
+        }
+        
+        if (count > 1)
+        {
+            if (!dmPCXPutByte(pcx, 0xC0 | count) ||
+                !dmPCXPutByte(pcx, data))
+                return FALSE;
+        }
+
+        if (!dmPCXFlush(pcx))
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+int dmWritePCXImage(const char *filename, DMImage *img, int scale, BOOL paletted)
+{
+    DMPCXData pcx;
+    DMPCXHeader hdr;
+    int res;
+
+    // Create output file
+    pcx.buf    = NULL;
+    pcx.format = paletted ? DM_IFMT_PALETTE : DM_IFMT_RGB_PLANE;
+    pcx.header = &hdr;
+    if ((pcx.fp = fopen(filename, "wb")) == NULL)
+    {
+        dmError("PCX: Could not open file '%s' for writing.\n", filename);
+        res = -15;
+        goto error;
+    }
+
+    // Create PCX header
+    memset(&hdr, 0, sizeof(hdr));
+    if (paletted)
+    {
+        int i;
+        for (i = 0; i < (img->ncolors > 16 ? 16 : img->ncolors); i++)
+        {
+            hdr.colormap[i].r = img->pal[i].r;
+            hdr.colormap[i].g = img->pal[i].g;
+            hdr.colormap[i].b = img->pal[i].b;
+        }
+    }
+    hdr.manufacturer = 10;
+    hdr.version      = 5;
+    hdr.encoding     = 1;
+    hdr.bpp          = 8;
+    hdr.hres         = img->width * scale;
+    hdr.vres         = img->height * scale;
+    hdr.xmin         = hdr.ymin = 0;
+    hdr.xmax         = hdr.hres - 1;
+    hdr.ymax         = hdr.vres - 1;
+    hdr.nplanes      = dmImageGetBytesPerPixel(pcx.format);
+    hdr.bpl          = (((img->width * scale) / 2) + 1) * 2;
+    hdr.palinfo      = 1;
+
+    dmMsg(1, "PCX: paletted=%d, nplanes=%d, bpp=%d, bpl=%d\n",
+        paletted, hdr.nplanes, hdr.bpp, hdr.bpl);
+
+    pcx.bufLen       = hdr.bpl * 4;
+    if ((pcx.buf = dmMalloc(pcx.bufLen)) == NULL)
+    {
+        dmError("PCX: Could not allocate %d bytes for RLE compression buffer.\n",
+            pcx.bufLen);
+        res = -11;
+        goto error;
+    }
+
+    // Write PCX header
+    if (!dm_fwrite_byte(pcx.fp, hdr.manufacturer) ||
+        !dm_fwrite_byte(pcx.fp, hdr.version) ||
+        !dm_fwrite_byte(pcx.fp, hdr.encoding) ||
+        !dm_fwrite_byte(pcx.fp, hdr.bpp))
+    {
+        dmError("PCX: Could not write basic header data.\n");
+        res = -10;
+        goto error;
+    }
+    
+    if (!dm_fwrite_le16(pcx.fp, hdr.xmin) ||
+        !dm_fwrite_le16(pcx.fp, hdr.ymin) ||
+        !dm_fwrite_le16(pcx.fp, hdr.xmax) ||
+        !dm_fwrite_le16(pcx.fp, hdr.ymax) ||
+        !dm_fwrite_le16(pcx.fp, hdr.hres) ||
+        !dm_fwrite_le16(pcx.fp, hdr.vres))
+    {
+        dmError("PCX: Could not write image dimensions.\n");
+        res = -9;
+        goto error;
+    }
+
+    if (!dm_fwrite_str(pcx.fp, (uint8_t *) &hdr.colormap, sizeof(hdr.colormap)))
+    {
+        dmError("PCX: Could not write colormap.\n");
+        res = -8;
+        goto error;
+    }
+    
+    if (!dm_fwrite_byte(pcx.fp, hdr.reserved) ||
+        !dm_fwrite_byte(pcx.fp, hdr.nplanes) ||
+        !dm_fwrite_le16(pcx.fp, hdr.bpl) ||
+        !dm_fwrite_le16(pcx.fp, hdr.palinfo) ||
+        !dm_fwrite_str(pcx.fp, (uint8_t *) &hdr.filler, sizeof(hdr.filler)))
+    {
+        dmError("PCX: Could not write header remainder.\n");
+        res = -7;
+        goto error;
+    }
+
+    // Write image data
+    res = dmWriteImageData(img, (void *) &pcx, dmWritePCXRow, scale, pcx.format);
+
+    // Write VGA palette
+    if (paletted)
+    {
+        int i;
+        dm_fwrite_byte(pcx.fp, 0x0C);
+
+        for (i = 0; i < img->ncolors; i++)
+        {
+            dm_fwrite_byte(pcx.fp, img->pal[i].r);
+            dm_fwrite_byte(pcx.fp, img->pal[i].g);
+            dm_fwrite_byte(pcx.fp, img->pal[i].b);
+        }
+
+        // Pad the palette, if necessary        
+        for (; i < 256; i++)
+        {
+            dm_fwrite_byte(pcx.fp, 0);
+            dm_fwrite_byte(pcx.fp, 0);
+            dm_fwrite_byte(pcx.fp, 0);
+        }
+    }
+    
+error:
+    if (pcx.fp != NULL)
+        fclose(pcx.fp);
+
+    dmFree(pcx.buf);
+    
+    return res;
+}
+
+
+static BOOL dmPCXDecodeRLERow(FILE *fp, uint8_t *buf, const size_t bufLen)
+{
+    size_t offs = 0;
+    do
+    {
+        int count;
+        uint8_t data;
+
+        if (!dm_fread_byte(fp, &data))
+            return FALSE;
+        
+        if ((data & 0xC0) == 0xC0)
+        {
+            count = data & 0x3F;
+            if (!dm_fread_byte(fp, &data))
+                return FALSE;
+        }
+        else
+            count = 1;
+
+        while (count-- && offs < bufLen)
+            buf[offs++] = data;
+
+    } while (offs < bufLen);
+
+    return TRUE;
+}
+
+
+int dmReadPCXImageFILE(FILE *fp, DMImage **pimg)
+{
+    DMImage *img;
+    DMPCXData pcx;
+    DMPCXHeader hdr;
+    BOOL paletted;
+    int res = 0, yc, xc;
+    uint8_t *dp;
+
+    pcx.buf = NULL;
+
+    // Read PCX header
+    if (!dm_fread_byte(fp, &hdr.manufacturer) ||
+        !dm_fread_byte(fp, &hdr.version) ||
+        !dm_fread_byte(fp, &hdr.encoding) ||
+        !dm_fread_byte(fp, &hdr.bpp))
+    {
+        dmError("PCX: Could not read basic header data.\n");
+        res = -9;
+    }
+    
+    if (hdr.manufacturer != 10 ||
+        hdr.version != 5 ||
+        hdr.encoding != 1 ||
+        hdr.bpp != 8)
+    {
+        dmError("PCX: Not a PCX file, or unsupported variant.\n");
+        res = -11;
+        goto error;
+    }
+    
+    if (!dm_fread_le16(fp, &hdr.xmin) ||
+        !dm_fread_le16(fp, &hdr.ymin) ||
+        !dm_fread_le16(fp, &hdr.xmax) ||
+        !dm_fread_le16(fp, &hdr.ymax) ||
+        !dm_fread_le16(fp, &hdr.hres) ||
+        !dm_fread_le16(fp, &hdr.vres))
+    {
+        dmError("PCX: Could not read image dimensions.\n");
+        res = -8;
+        goto error;
+    }
+
+    if (!dm_fread_str(fp, (uint8_t *) &hdr.colormap, sizeof(hdr.colormap)))
+    {
+        dmError("PCX: Could not read colormap.\n");
+        res = -7;
+        goto error;
+    }
+    
+    if (!dm_fread_byte(fp, &hdr.reserved) ||
+        !dm_fread_byte(fp, &hdr.nplanes) ||
+        !dm_fread_le16(fp, &hdr.bpl) ||
+        !dm_fread_le16(fp, &hdr.palinfo) ||
+        !dm_fread_str(fp, (uint8_t *) &hdr.filler, sizeof(hdr.filler)))
+    {
+        dmError("PCX: Could not read header remainder.\n");
+        res = -6;
+        goto error;
+    }
+    
+    if (hdr.nplanes != 3 && hdr.nplanes != 1)
+    {
+        dmError("PCX: Unsupported number of bitplanes %d.\n", hdr.nplanes);
+        res = -4;
+        goto error;
+    }
+
+    // Allocate image
+    if ((*pimg = img = dmImageAlloc(hdr.xmax - hdr.xmin + 1, hdr.ymax - hdr.ymin + 1)) == NULL)
+    {
+        dmError("PCX: Could not allocate image structure.\n");
+        res = -5;
+        goto error;
+    }
+    
+    paletted = hdr.nplanes == 1;
+    pcx.bufLen = hdr.nplanes * hdr.bpl;
+    if ((pcx.buf = dmMalloc(pcx.bufLen)) == NULL)
+    {
+        dmError("PCX: Could not allocate RLE buffer.\n");
+        res = -3;
+        goto error;
+    }
+
+    // Read image data
+    dp = img->data;
+    for (yc = 0; yc < img->height; yc++)
+    {
+        // Decode row of RLE'd data
+        if (!dmPCXDecodeRLERow(fp, pcx.buf, pcx.bufLen))
+        {
+            dmError("PCX: Error decoding RLE data.\n");
+            res = -100;
+            goto error;
+        }
+        
+        // Decode bitplanes
+        switch (hdr.nplanes)
+        {
+            case 1:
+                memcpy(dp, pcx.buf, img->width);
+                break;
+            
+            case 3:
+                {
+                    uint8_t *dptr = dp,
+                            *sptr1 = pcx.buf,
+                            *sptr2 = sptr1 + hdr.bpl,
+                            *sptr3 = sptr2 + hdr.bpl;
+
+                    for (xc = 0; xc < img->width; xc++)
+                    {
+                        *dptr++ = *sptr1++;
+                        *dptr++ = *sptr2++;
+                        *dptr++ = *sptr3++;
+                    }
+                }
+                break;
+        }
+        
+        dp += img->pitch;
+    }
+
+    // Read VGA palette
+    if (paletted)
+    {
+        int i;
+        uint8_t tmpb;
+
+        if (!dm_fread_byte(fp, &tmpb) || tmpb != 0x0C)
+            goto error;
+
+        for (i = 0; i < img->ncolors; i++)
+        {
+            if (!dm_fread_byte(fp, &tmpb))
+                goto error;
+            img->pal[i].r = tmpb;
+
+            if (!dm_fread_byte(fp, &tmpb))
+                goto error;
+            img->pal[i].g = tmpb;
+
+            if (!dm_fread_byte(fp, &tmpb))
+                goto error;
+            img->pal[i].b = tmpb;
+        }
+    }
+
+error:
+    dmFree(pcx.buf);
+    return res;
+}
+
+
+int dmReadPCXImage(const char *filename, DMImage **pimg)
+{
+    FILE *fp;
+    int res;
+
+    if ((fp = fopen(filename, "rb")) == NULL)
+    {
+        dmError("PCX: Could not open file '%s' for reading.\n", filename);
+        return -15;
+    }
+    
+    res = dmReadPCXImageFILE(fp, pimg);
+
+    fclose(fp);
+    return res;
+}
+
+
+int fmtProbePNGImageFILE(FILE *fp)
+{
+    uint8_t buf[6];
+//    if (!dm_fread_str(fp, 
+    return DM_PROBE_SCORE_FALSE;
+}
+
+
+int fmtProbePCXImageFILE(FILE *fp)
+{
+    DMPCXHeader hdr;
+
+    if (!dm_fread_byte(fp, &hdr.manufacturer) ||
+        !dm_fread_byte(fp, &hdr.version) ||
+        !dm_fread_byte(fp, &hdr.encoding) ||
+        !dm_fread_byte(fp, &hdr.bpp))
+        return DM_PROBE_SCORE_FALSE;
+    
+    if (hdr.manufacturer == 10 &&
+        hdr.version == 5 &&
+        hdr.encoding == 1 &&
+        hdr.bpp == 8)
+        return DM_PROBE_SCORE_GOOD;
+
+    return DM_PROBE_SCORE_FALSE;
+}
+
+
+#ifdef UNFINISHED
+int dmConvertBMP2(DMImage *screen, const DM64Image *img)
+{
+    int yc;
+    uint8_t *dp = screen->data;
+    
+    for (yc = 0; yc < screen->height; yc++)
+    {
+        uint8_t *d = dp;
+        const int y = yc / 8, yb = yc & 7;
+        const int scroffsy = y * C64_SCR_CH_WIDTH;
+        const int bmoffsy = y * C64_SCR_WIDTH;
+        int xc;
+
+        for (xc = 0; xc < screen->width / 2; xc++)
+        {
+            const int x = xc / 4;
+            const int scroffs = scroffsy + x;
+            const int b = img->bitmap[0][bmoffsy + (x * 8) + yb];
+            const int v = 6 - ((xc * 2) & 6);
+            uint8_t c;
+
+            switch ((b >> v) & 3)
+            {
+                case 0: c = img->bgcolor; break;
+                case 1: c = img->screen[0][scroffs] >> 4; break;
+                case 2: c = img->screen[0][scroffs] & 15; break;
+                case 3: c = img->color[0][scroffs] & 15; break;
+            }
+            
+            *d++ = c;
+            *d++ = c;
+        }
+
+        dp += screen->pitch;
+    }
+    
+    return 0;
+}
+#endif
+
+
+int dmWriteImage(char *filename, DMImage *image, int format, BOOL paletted, int scale, int bpp)
+{
+    switch (format)
+    {
+#ifdef HAVE_LIBPNG
+        case OUTFMT_PNG:
+            return dmWritePNGImage(filename, image, scale, paletted ? DM_IFMT_PALETTE : DM_IFMT_RGBA);
+#endif
+
+        case OUTFMT_PPM:
+            return dmWritePPMImage(filename, image, scale);
+
+        case OUTFMT_PCX:
+            return dmWritePCXImage(filename, image, scale, paletted);
+
+        case OUTFMT_ARAW:
+            {
+                int res;
+                char *palFilename = dm_strdup_printf("%s.pal", filename);
+                res = dmWriteIFFMasterRAWPalette(palFilename, image, 1 << bpp);
+                dmFree(palFilename);
+                if (res != 0)
+                    return res;
+
+                return dmWriteIFFMasterRAWImage(filename, image, scale, bpp);
+            }
+
+        default:
+            return FALSE;
+    }
+}
+
+
+int dmDumpSpritesAndChars(FILE *inFile)
+{
+    int dataOffs, itemCount, outWidth, outWidthPX, outHeight;
+    size_t bufSize;
+    uint8_t *bufData;
+
+    switch (optInFormat)
+    {
+        case INFMT_CHAR:
+            bufSize = C64_CHR_SIZE;
+            outWidth = C64_CHR_WIDTH;
+            outWidthPX = C64_CHR_WIDTH_PX;
+            outHeight = C64_CHR_HEIGHT;
+            break;
+        case INFMT_SPRITE:
+            bufSize = C64_SPR_SIZE;
+            outWidth = C64_SPR_WIDTH;
+            outWidthPX = C64_SPR_WIDTH_PX;
+            outHeight = C64_SPR_HEIGHT;
+            break;
+        default:
+            dmError("Invalid input format %d, internal error.\n", optInFormat);
+            return -1;
+    }
+
+    if ((bufData = dmMalloc(bufSize)) == NULL)
+    {
+        dmError("Could not allocate temporary buffer of %d bytes.\n", bufSize);
+        return -2;
+    }
+
+
+    dataOffs = optInSkip;
+    itemCount = 0;
+
+    if (optOutFormat == OUTFMT_ANSI || optOutFormat == OUTFMT_ASCII)
+    {
+        BOOL error = FALSE;
+        FILE *outFile;
+
+        if (optOutFilename == NULL)
+            outFile = stdout;
+        else
+        if ((outFile = fopen(optOutFilename, "w")) == NULL)
+        {
+            int res = errno;
+            dmError("Error opening output file '%s'. (%s)\n",
+                  optOutFilename, strerror(res));
+            goto error;
+        }
+
+        while (!feof(inFile) && !error && (optItemCount < 0 || itemCount < optItemCount))
+        {
+            memset(bufData, 0, bufSize);
+
+            if (fread(bufData, 1, bufSize, inFile) != bufSize)
+            {
+                dmError("Could not read full bufferful (%d bytes) of data at 0x%x.\n",
+                    bufSize, dataOffs);
+                error = TRUE;
+            }
+            
+            fprintf(outFile, "---- : -------------- #%d\n", itemCount);
+
+            switch (optInFormat)
+            {
+                case INFMT_CHAR:
+                    dmDumpCharASCII(outFile, bufData, &dataOffs, optOutFormat, optInMulticolor);
+                    break;
+                case INFMT_SPRITE:
+                    dmDumpSpriteASCII(outFile, bufData, &dataOffs, optOutFormat, optInMulticolor);
+                    break;
+            }
+            itemCount++;
+        }
+
+        fclose(outFile);
+    }
+    else
+    if (optOutFormat == OUTFMT_PNG || optOutFormat == OUTFMT_PPM || optOutFormat == OUTFMT_PCX)
+    {
+        DMImage *outImage = NULL;
+        char *outFilename = NULL;
+        int outX = 0, outY = 0, err;
+
+#ifndef HAVE_LIBPNG
+        if (optOutFormat == OUTFMT_PNG)
+        {
+            dmError("PNG output format support not compiled in, sorry.\n");
+            goto error;
+        }
+#endif
+
+        if (optSequential)
+        {
+            if (optOutFilename == NULL)
+            {
+                dmError("Sequential image output requires filename template.\n");
+                goto error;
+            }
+
+            outImage = dmImageAlloc(outWidthPX, outHeight);
+            dmMsg(1, "Outputting sequence of %d images @ %d x %d -> %d x %d.\n",
+                optItemCount,
+                outImage->width, outImage->height,
+                outImage->width * optScale, outImage->height * optScale);
+        }
+        else
+        {
+            int outIWidth, outIHeight;
+            if (optItemCount <= 0)
+            {
+                dmError("Single-image output requires count to be set (-n).\n");
+                goto error;
+            }
+            
+            outIWidth = optPlanedWidth;
+            outIHeight = (optItemCount / optPlanedWidth);
+            if (optItemCount % optPlanedWidth)
+                outIHeight++;
+            
+            outImage = dmImageAlloc(outWidthPX * outIWidth, outIHeight * outHeight);
+            dmMsg(1, "Outputting image %d x %d -> %d x %d.\n",
+                outImage->width, outImage->height,
+                outImage->width * optScale, outImage->height * optScale);
+        }
+
+        outImage->constpal = TRUE;
+        outImage->pal      = dmC64Palette;
+        outImage->ncolors  = C64_NCOLORS;
+        outImage->ctrans   = 255;
+        
+        while (!feof(inFile) && (optItemCount < 0 || itemCount < optItemCount))
+        {
+            memset(bufData, 0, bufSize);
+
+            if (fread(bufData, 1, bufSize, inFile) != bufSize)
+            {
+                dmError("Could not read full bufferful (%d bytes) of data at 0x%x.\n",
+                    bufSize, dataOffs);
+                break;
+            }
+
+            if ((err = dmC64ConvertCSData(outImage, outX * outWidthPX, outY * outHeight,
+                bufData, outWidth, outHeight, optInMulticolor, optColors)) != 0)
+            {
+                dmError("Internal error in conversion of raw data to bitmap: %d.\n", err);
+                break;
+            }
+
+            if (optSequential)
+            {
+                outFilename = dm_strdup_printf("%s%04d.%s", optOutFilename, itemCount, outFormatList[optOutFormat]);
+                if (outFilename == NULL)
+                {
+                    dmError("Could not allocate memory for filename template?\n");
+                    goto error;
+                }
+                
+                dmWriteImage(outFilename, outImage, optOutFormat, optPaletted, optScale, optBPP);
+                dmFree(outFilename);
+            }
+            else
+            {
+                if (++outX >= optPlanedWidth)
+                {
+                    outX = 0;
+                    outY++;
+                }
+            }
+            
+            itemCount++;
+        }
+
+        if (!optSequential)
+        {
+            dmWriteImage(optOutFilename, outImage, optOutFormat, optPaletted, optScale, optBPP);
+        }
+        
+        dmImageFree(outImage);
+    }
+
+    dmFree(bufData);
+    return 0;
+
+error:
+    dmFree(bufData);
+    return -1;
+}
+
+
+int main(int argc, char *argv[])
+{
+    FILE *inFile;
+    int i, optInImageFormat;
+
+    // Default colors
+    for (i = 0; i < C64_MAX_COLORS; i++)
+        optColors[i] = i + 1;
+
+    // Initialize and parse commandline
+    dmInitProg("gfxconv", "Simple c64 graphics converter", "0.4", NULL, NULL);
+
+    if (!dmArgsProcess(argc, argv, optList, optListN,
+        argHandleOpt, argHandleFile, TRUE))
+        exit(1);
+
+    // Determine input format, if not specified'
+    if (optInFormat == INFMT_AUTO && optInFilename != NULL)
+    {
+        char *dext = strrchr(optInFilename, '.');
+        if (dext)
+        {
+            dext++;
+            if (!strcasecmp(dext, "fnt") || !strcasecmp(dext, "chr"))
+                optInFormat = INFMT_CHAR;
+            else if (!strcasecmp(dext, "spr"))
+                optInFormat = INFMT_SPRITE;
+            else if (!strcasecmp(dext, "png") || !strcasecmp(dext, "pcx"))
+                optInFormat = INFMT_IMAGE;
+        }
+    }
+
+    if (optInFilename == NULL)
+    {
+        if (optInFormat == INFMT_AUTO)
+        {
+            dmError("Standard input cannot be used without specifying input format.\n");
+            exit(3);
+        }
+        inFile = stdin;
+    }
+    else
+    if ((inFile = fopen(optInFilename, "rb")) == NULL)
+    {
+        int res = errno;
+        dmError("Error opening input file '%s'. (%s)\n",
+              optInFilename, strerror(res));
+        exit(3);
+    }
+
+    if (optInFormat == INFMT_AUTO)
+    {
+        // Skip, if needed
+        if (fseek(inFile, optInSkip, SEEK_SET) != 0)
+        {
+            int res = errno;
+            dmError("Could not seek to file position %d (0x%x): %s\n",
+                optInSkip, optInSkip, strerror(res));
+            exit(3);
+        }
+#if 0
+        if (optInFormat == INFMT_AUTO)
+        {
+            int ret = dmC64ProbeGeneric
+        }
+#endif
+
+        if (optInFormat == INFMT_AUTO || optInFormat == INFMT_IMAGE)
+        {
+            if (fmtProbePNGImageFILE(inFile))
+            {
+                optInFormat = INFMT_IMAGE;
+                optInImageFormat = OUTFMT_PNG;
+            }
+            else
+            if (fmtProbePCXImageFILE(inFile))
+            {
+                optInFormat = INFMT_IMAGE;
+                optInImageFormat = OUTFMT_PCX;
+            }
+            else
+            if (optInFormat == INFMT_IMAGE)
+            {
+                dmError("Unsupported image input format.\n");
+                exit(4);
+            }
+        }
+    }
+
+    if (optInFormat == INFMT_AUTO)
+    {
+        dmError("No input format specified, and could not be determined automatically.\n");
+        exit(1);
+    }
+
+    // Skip, if needed
+    if (fseek(inFile, optInSkip, SEEK_SET) != 0)
+    {
+        int res = errno;
+        dmError("Could not seek to file position %d (0x%x): %s\n",
+            optInSkip, optInSkip, strerror(res));
+        exit(3);
+    }
+
+    switch (optInFormat)
+    {
+        case INFMT_SPRITE:
+        case INFMT_CHAR:
+            dmDumpSpritesAndChars(inFile);
+            break;
+        
+        case INFMT_BITMAP:
+        case INFMT_IMAGE:
+            {
+                DMImage *img;
+                int res;
+
+                if (optOutFilename == NULL)
+                {
+                    dmError("Output filename not set, required for image formats.\n");
+                    exit(3);
+                }
+
+                // Read input
+                switch (optInImageFormat)
+                {
+                    case OUTFMT_PCX:
+                        res = dmReadPCXImageFILE(inFile, &img);
+                        break;
+                    case OUTFMT_PNG:
+//                        res = dmReadPNGImageFILE(inFile, &img);
+                        break;
+                }
+                
+                switch (optOutFormat)
+                {
+                    case OUTFMT_PCX:
+                    case OUTFMT_PPM:
+                    case OUTFMT_PNG:
+                    case OUTFMT_ARAW:
+                        res = dmWriteImage(optOutFilename, img, optOutFormat, optPaletted, optScale, optBPP);
+                        break;
+                }
+            }
+            break;
+    }
+
+    fclose(inFile);
+
+    exit(0);
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib64gfx.c	Sat Nov 03 02:19:51 2012 +0200
@@ -0,0 +1,727 @@
+/*
+ * Functions for reading and converting various restricted
+ * C64/etc and/or indexed/paletted graphics formats.
+ * Programmed and designed by Matti 'ccr' Hamalainen
+ * (C) Copyright 2012 Tecnic Software productions (TNSP)
+ *
+ * Please read file 'COPYING' for information on license and distribution.
+ */
+#include "lib64gfx.h"
+#include <errno.h>
+
+const char *dmC64ImageTypeNames[DM_C64IFMT_LAST_TYPE] =
+{
+    "hires",
+    "multicolor",
+    "hires interlace",
+    "mc interlace",
+    "hires fli",
+    "mc fli",
+};
+
+
+// Based on Pepto's palette, stolen from VICE
+DMColor dmC64Palette[C64_NCOLORS] =
+{
+    { 0x00, 0x00, 0x00, 0 },
+    { 0xFF, 0xFF, 0xFF, 0 },
+    { 0x68, 0x37, 0x2B, 0 },
+    { 0x70, 0xA4, 0xB2, 0 },
+    { 0x6F, 0x3D, 0x86, 0 },
+    { 0x58, 0x8D, 0x43, 0 },
+    { 0x35, 0x28, 0x79, 0 },
+    { 0xB8, 0xC7, 0x6F, 0 },
+    { 0x6F, 0x4F, 0x25, 0 },
+    { 0x43, 0x39, 0x00, 0 },
+    { 0x9A, 0x67, 0x59, 0 },
+    { 0x44, 0x44, 0x44, 0 },
+    { 0x6C, 0x6C, 0x6C, 0 },
+    { 0x9A, 0xD2, 0x84, 0 },
+    { 0x6C, 0x5E, 0xB5, 0 },
+    { 0x95, 0x95, 0x95, 0 },
+};
+
+
+const size_t dmC64DefaultSizes[DT_LAST] =
+{
+    C64_SCR_COLOR_SIZE,
+    C64_SCR_BITMAP_SIZE,
+    C64_SCR_SCREEN_SIZE,
+    1,
+    C64_SCR_EXTRADATA,
+};
+
+
+
+DMImage * dmImageAlloc(int width, int height)
+{
+    DMImage *img = dmCalloc(1, sizeof(DMImage));
+    if (img == NULL)
+        return NULL;
+    
+    img->width = img->pitch = width;
+    img->height = height;
+    img->data = dmMalloc(width * height * sizeof(uint8_t));
+    if (img->data == NULL)
+    {
+        dmFree(img);
+        return NULL;
+    }
+    
+    return img;
+}
+
+
+void dmImageFree(DMImage *img)
+{
+    if (img != NULL)
+    {
+        if (!img->constpal)
+        {
+            dmFree(img->pal);
+        }
+        dmFree(img->data);
+        dmFree(img);
+    }
+}
+
+
+int dmImageGetBytesPerPixel(int format)
+{
+    switch (format)
+    {
+        case DM_IFMT_PALETTE   : return 1;
+        case DM_IFMT_RGB       : return 3;
+        case DM_IFMT_RGBA      : return 4;
+        case DM_IFMT_RGB_PLANE : return 3;
+        default:                 return 0;
+    }
+}
+
+
+int dmC64ConvertCSData(DMImage *img,
+    int xoffs, int yoffs, const uint8_t *buf,
+    int width, int height, BOOL multicolor, int *colors)
+{
+    int yc, widthpx = width * 8;
+    uint8_t *dp;
+
+    if (img == NULL)
+        return -1;
+    if (xoffs < 0 || yoffs < 0)
+        return -2;
+    if (xoffs > img->width - widthpx ||
+        yoffs > img->height - height)
+        return -3;
+
+    dp = img->data + (yoffs * img->pitch) + xoffs;
+
+    if (multicolor)
+    {
+        for (yc = 0; yc < height; yc++)
+        {
+            const int offs = yc * width;
+            int xc;
+            uint8_t *d = dp;
+
+            for (xc = 0; xc < widthpx / 2; xc++)
+            {
+                const int b = buf[offs + (xc / 4)];
+                const int v = 6 - ((xc * 2) & 6);
+                const uint8_t c = colors[(b >> v) & 3];
+                
+                *d++ = c;
+                *d++ = c;
+            }
+
+            dp += img->pitch;
+        }
+    }
+    else
+    {
+        for (yc = 0; yc < height; yc++)
+        {
+            const int offs = yc * width;
+            int xc;
+            uint8_t *d = dp;
+
+            for (xc = 0; xc < widthpx; xc++)
+            {
+                const int b = buf[offs + (xc / 8)];
+                const int v = 7 - (xc & 7);
+                const uint8_t c = colors[(b >> v) & 1];
+                
+                *d++ = c;
+            }
+
+            dp += img->pitch;
+        }
+    }
+    
+    return 0;
+}
+
+
+static int fmtProbeDrazPaint(const uint8_t *buf, const size_t len)
+{
+    if (len == 10051 && buf[0] == 0x00 && buf[1] == 0x58)
+        return DM_PROBE_SCORE_GOOD;
+
+    return DM_PROBE_SCORE_FALSE;
+}
+
+
+static int fmtProbeDrazPaint20Packed(const uint8_t *buf, const size_t len)
+{
+    const char *ident = (const char *) buf + 2;
+    if (len > 22 && buf[0] == 0x00 && buf[1] == 0x58 &&
+            strncmp(ident, "DRAZPAINT ", 10) == 0 &&
+            ident[11] == '.' && (
+            (ident[10] == '1' && ident[12] == '4') ||
+            (ident[10] == '2' && ident[12] == '0')
+            ))
+        return DM_PROBE_SCORE_MAX;
+
+    return DM_PROBE_SCORE_FALSE;
+}
+
+
+static int fmtDecodeDrazPaintPacked(DMC64Image *img, const uint8_t *buf, const size_t len, const DMC64ImageFormat *fmt)
+{
+    int res;
+    uint8_t rleMarker;
+    uint8_t *mem, *dst, *dstEnd;
+    const uint8_t *src, *srcEnd;
+
+    if ((mem = dmMalloc(C64_RAM_SIZE)) == NULL)
+        return -1;
+    
+    rleMarker = *(buf + 0x0d);
+    src       = buf + 0x0e;
+    srcEnd    = buf + len;
+    dst       = mem;
+    dstEnd    = mem + C64_RAM_SIZE;
+
+    while (src <= srcEnd && dst <= dstEnd)
+    {
+        int c = *src++;
+        if (c == rleMarker && src + 2 <= srcEnd)
+        {
+            int cnt = *src++;
+            c = *src++;
+            while (cnt-- && dst <= dstEnd)
+                *dst++ = c;
+        }
+        else
+            *dst++ = c;
+    }
+    
+    res = dmC64DecodeGenericBMP(img, mem, dst - mem + 1, fmt);
+    
+    dmFree(mem);
+
+    return res;
+}
+
+
+static int fmtProbeDrazLace10(const uint8_t *buf, const size_t len)
+{
+    if (len == 18242 && buf[0] == 0x00 && buf[1] == 0x58)
+        return DM_PROBE_SCORE_GOOD;
+    return DM_PROBE_SCORE_FALSE;
+}
+
+
+static int fmtProbeDrazLace10Packed(const uint8_t *buf, const size_t len)
+{
+    const char *ident = (const char *) buf + 2;
+    if (len > 22 && buf[0] == 0x00 && buf[1] == 0x58 &&
+            strncmp(ident, "DRAZLACE! 1.0", 13) == 0)
+        return DM_PROBE_SCORE_MAX;
+    
+    return DM_PROBE_SCORE_FALSE;
+}
+
+
+static BOOL fmtDrazLaceSetLaceType(DMC64Image *img, const struct _DMDecodeOp *op, const uint8_t *buf, const size_t len)
+{
+    img->laceType = buf[op->offs] ? DM_C64ILACE_RES : DM_C64ILACE_COLOR;
+    img->laceBank2 = 0;
+    return TRUE;
+}
+
+
+#define AMICA_DM_PROBE_SIZE 1024
+static int fmtProbeAmicaPaintPacked(const uint8_t *buf, const size_t len)
+{
+    int i, n;
+    if (len < AMICA_DM_PROBE_SIZE || buf[0] != 0x00 || buf[1] != 0x40)
+        return DM_PROBE_SCORE_FALSE;
+    
+    for (n = 0, i = 2; i < AMICA_DM_PROBE_SIZE; i++)
+        if (buf[i] == 0xC2) n++;
+    
+    if (n > 5)
+        return DM_PROBE_SCORE_GOOD;
+    if (n > 3)
+        return DM_PROBE_SCORE_AVG;
+
+    return DM_PROBE_SCORE_MAYBE;
+}
+
+
+static int fmtDecodeAmicaPaintPacked(DMC64Image *img, const uint8_t *buf, const size_t len, const DMC64ImageFormat *fmt)
+{
+    int res;
+    uint8_t *mem, *dst, *dstEnd;
+    const uint8_t *src, *srcEnd;
+
+    if ((mem = dmMalloc(C64_RAM_SIZE)) == NULL)
+        return -1;
+    
+    src    = buf;
+    srcEnd = buf + len;
+    dst    = mem;
+    dstEnd = mem + C64_RAM_SIZE;
+
+    while (src <= srcEnd && dst <= dstEnd)
+    {
+        int c = *src++;
+        if (c == 0xC2 && src + 2 <= srcEnd)
+        {
+            int cnt = *src++;
+            c = *src++;
+            while (cnt-- && dst <= dstEnd)
+                *dst++ = c;
+        }
+        else
+            *dst++ = c;
+    }
+    
+    res = dmC64DecodeGenericBMP(img, mem, dst - mem + 1, fmt);
+    
+    dmFree(mem);
+
+    return res;
+}
+
+
+static int fmtProbeKoalaPaint(const uint8_t *buf, const size_t len)
+{
+    if (len == 10003 && buf[0] == 0x00 && buf[1] == 0x60)
+        return DM_PROBE_SCORE_AVG;
+    return DM_PROBE_SCORE_FALSE;
+}
+
+
+static int fmtProbeTruePaint(const uint8_t *buf, const size_t len)
+{
+    if (len == 19434 && buf[0] == 0x00 && buf[1] == 0x9c)
+        return DM_PROBE_SCORE_GOOD;
+    return DM_PROBE_SCORE_FALSE;
+}
+
+
+static BOOL fmtTruePaintSetLaceType(DMC64Image *img, const struct _DMDecodeOp *op, const uint8_t *buf, const size_t len)
+{
+    img->laceType = DM_C64ILACE_RES;
+    img->laceBank2 = 1;
+    return TRUE;
+}
+
+
+DMC64ImageFormat dmC64ImageFormats[] =
+{
+    {
+        DM_C64IFMT_MC, ".drp", "DrazPaint 2.0 (packed)",
+        fmtProbeDrazPaint20Packed, fmtDecodeDrazPaintPacked, NULL,
+        4,
+        {
+            { DT_COLOR_RAM,  0x0000, 0,  0, NULL },
+            { DT_BITMAP,     0x0800, 0,  0, NULL },
+            { DT_SCREEN_RAM, 0x0400, 0,  0, NULL },
+            { DT_BGCOLOR,    0x2740, 0,  0, NULL },
+        }
+    },
+
+    {
+        DM_C64IFMT_MC_ILACE, ".dlp", "DrazLace 1.0 (packed)",
+        fmtProbeDrazLace10Packed, fmtDecodeDrazPaintPacked, NULL,
+        6,
+        {
+            { DT_COLOR_RAM,  0x0000, 0,  0, NULL },
+            { DT_BITMAP,     0x0800, 0,  0, NULL },
+            { DT_SCREEN_RAM, 0x0400, 0,  0, NULL },
+            { DT_BGCOLOR,    0x2740, 0,  0, NULL },
+            { DT_BITMAP,     0x2800, 1,  0, NULL },
+            { DT_FUNCTION,   0x2742, 0,  1, fmtDrazLaceSetLaceType },
+        }
+    },
+    
+    {
+        DM_C64IFMT_MC, ".drp", "DrazPaint (unpacked)",
+        fmtProbeDrazPaint, NULL, NULL,
+        4,
+        {
+            { DT_COLOR_RAM,  0x0000, 0,  0, NULL },
+            { DT_BITMAP,     0x0800, 0,  0, NULL },
+            { DT_SCREEN_RAM, 0x0400, 0,  0, NULL },
+            { DT_BGCOLOR,    0x2740, 0,  0, NULL },
+        }
+    },
+
+    {
+        DM_C64IFMT_MC_ILACE, ".drl", "DrazLace 1.0 (unpacked)",
+        fmtProbeDrazLace10, NULL, NULL,
+        6,
+        {
+            { DT_COLOR_RAM,  0x0000, 0,  0, NULL },
+            { DT_BITMAP,     0x0800, 0,  0, NULL },
+            { DT_SCREEN_RAM, 0x0400, 0,  0, NULL },
+            { DT_BGCOLOR,    0x2740, 0,  0, NULL },
+            { DT_BITMAP,     0x2800, 1,  0, NULL },
+            { DT_FUNCTION,   0x2742, 0,  1, fmtDrazLaceSetLaceType },
+        }
+    },
+    
+    {
+        DM_C64IFMT_MC_ILACE, ".mci", "Truepaint (unpacked)",
+        fmtProbeTruePaint, NULL, NULL,
+        6,
+        {
+            { DT_SCREEN_RAM, 0x0000, 0,  0, NULL },
+            { DT_BGCOLOR,    0x03e8, 0,  0, NULL },
+            { DT_BITMAP,     0x0400, 0,  0, NULL },
+            { DT_BITMAP,     0x2400, 1,  0, NULL },
+            { DT_SCREEN_RAM, 0x4400, 1,  0, NULL },
+            { DT_COLOR_RAM,  0x4800, 0,  0, NULL },
+            { DT_FUNCTION,   0x0000, 0,  0, fmtTruePaintSetLaceType },
+        }
+    },
+    
+    {
+        DM_C64IFMT_MC, ".kla", "Koala Paint (unpacked)",
+        fmtProbeKoalaPaint, NULL, NULL,
+        4,
+        {
+            { DT_COLOR_RAM,  0x2328, 0,  0, NULL },
+            { DT_BITMAP,     0x0000, 0,  0, NULL },
+            { DT_SCREEN_RAM, 0x1f40, 0,  0, NULL },
+            { DT_BGCOLOR,    0x2710, 0,  0, NULL },
+        }
+    },
+
+    {
+        DM_C64IFMT_MC, ".ami", "Amica Paint (packed)",
+        fmtProbeAmicaPaintPacked, fmtDecodeAmicaPaintPacked, NULL,
+        4,
+        {
+            { DT_COLOR_RAM,  0x2328, 0,  0, NULL },
+            { DT_BITMAP,     0x0000, 0,  0, NULL },
+            { DT_SCREEN_RAM, 0x1f40, 0,  0, NULL },
+            { DT_BGCOLOR,    0x2710, 0,  0, NULL },
+        }
+    },
+    
+};
+
+const int ndmC64ImageFormats = sizeof(dmC64ImageFormats) / sizeof(dmC64ImageFormats[0]);
+
+
+int dmC64ProbeGeneric(const uint8_t *buf, const size_t len,
+    DMC64ImageFormat **pfmt)
+{
+    int i, scoreMax = 0, scoreIndex = -1;
+
+    for (i = 0; i < ndmC64ImageFormats; i++)
+    {
+        DMC64ImageFormat *fmt = &dmC64ImageFormats[i];
+        int score = fmt->probe(buf, len);
+        if (score > scoreMax)
+        {
+            scoreMax = score;
+            scoreIndex = i;
+        }
+    }
+
+    if (scoreIndex >= 0)
+    {
+        *pfmt = &dmC64ImageFormats[scoreIndex];
+        return scoreMax;
+    }
+    else
+        return 0;
+}
+
+
+int dmC64DecodeGenericBMP(DMC64Image *img, const uint8_t *buf,
+    const size_t len, const DMC64ImageFormat *fmt)
+{
+    int i;
+
+    memset(img, 0, sizeof(*img));
+    img->type = fmt->type;
+
+    for (i = 0; i < fmt->ndecodeOps; i++)
+    {
+        const DMDecodeOp *op = &fmt->decodeOps[i];
+        const uint8_t *src;
+        size_t size;
+
+        if (op->bank < 0 || op->bank >= C64_SCR_MAX_BANK)
+        {
+            dmError("Invalid bank %d definition in generic decode operator %d @ #%d.\n",
+                op->bank, op->type, i);
+            return -1;
+        }
+        
+        if (op->type < 0 || op->type >= DT_LAST)
+        {
+            dmError("Invalid decode operator type %d @ #%d.\n",
+                op->type, i);
+            return -1;
+        }
+        
+        size = (op->size == 0) ? dmC64DefaultSizes[op->type] : op->size;
+        
+        if (op->offs + size > len)
+        {
+            dmError("Decode out of bounds, op #%d type=%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x\n",
+                i, op->type, op->offs, op->offs, op->bank, size, size, len, len);
+            return -2;
+        }
+        
+        src = buf + op->offs;
+        
+        switch (op->type)
+        {
+            case DT_COLOR_RAM:   memcpy(img->color[op->bank], src, size); break;
+            case DT_BITMAP:      memcpy(img->bitmap[op->bank], src, size); break;
+            case DT_SCREEN_RAM:  memcpy(img->screen[op->bank], src, size); break;
+            case DT_BGCOLOR:     img->bgcolor = *src; break;
+            case DT_EXTRADATA:   memcpy(img->extradata, src, size); break;
+            case DT_FUNCTION:
+                if (op->function == NULL)
+                {
+                    dmError("Decode op is a function, but function ptr is NULL: op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x\n",
+                        i, op->offs, op->offs, op->bank, size, size, len, len);
+                    return -6;
+                }
+                if (!op->function(img, op, buf, len))
+                {
+                    dmError("Decode op custom function failed: op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x\n",
+                        i, op->offs, op->offs, op->bank, size, size, len, len);
+                    return -5;
+                }
+                break;
+        }
+    }
+    
+    return 0;
+}
+
+
+static int dmC64ConvertHiResBMP(DMImage *screen, const DMC64Image *img)
+{
+    int yc;
+    uint8_t *dp = screen->data;
+    
+    for (yc = 0; yc < C64_SCR_HEIGHT; yc++)
+    {
+        uint8_t *d = dp;
+        const int y = yc / 8, yb = yc & 7;
+        const int scroffsy = y * C64_SCR_CH_WIDTH;
+        const int bmoffsy = y * C64_SCR_WIDTH;
+        int xc;
+
+        for (xc = 0; xc < C64_SCR_WIDTH; xc++)
+        {
+            const int x = xc / 8;
+            const int scroffs = scroffsy + x;
+            const int b = img->bitmap[0][bmoffsy + (x * 8) + yb];
+            const int v = 7 - (xc & 7);
+            uint8_t c;
+
+            if ((b >> v) & 1)
+                c = img->screen[0][scroffs] & 15;
+            else
+                c = img->screen[0][scroffs] >> 4;
+            
+            *d++ = c;
+        }
+
+        dp += screen->pitch;
+    }
+
+    return 0;
+}
+
+
+static int dmC64ConvertMultiColorBMP(DMImage *screen, const DMC64Image *img)
+{
+    int yc;
+    uint8_t *dp = screen->data;
+    
+    for (yc = 0; yc < C64_SCR_HEIGHT; yc++)
+    {
+        uint8_t *d = dp;
+        const int y = yc / 8, yb = yc & 7;
+        const int scroffsy = y * C64_SCR_CH_WIDTH;
+        const int bmoffsy = y * C64_SCR_WIDTH;
+        int xc;
+
+        for (xc = 0; xc < C64_SCR_WIDTH / 2; xc++)
+        {
+            const int x = xc / 4;
+            const int scroffs = scroffsy + x;
+            const int b = img->bitmap[0][bmoffsy + (x * 8) + yb];
+            const int v = 6 - ((xc * 2) & 6);
+            uint8_t c;
+
+            switch ((b >> v) & 3)
+            {
+                case 0: c = img->bgcolor; break;
+                case 1: c = img->screen[0][scroffs] >> 4; break;
+                case 2: c = img->screen[0][scroffs] & 15; break;
+                case 3: c = img->color[0][scroffs] & 15; break;
+            }
+            
+            *d++ = c;
+            *d++ = c;
+        }
+
+        dp += screen->pitch;
+    }
+
+    return 0;
+}
+
+
+static int dmC64ConvertLaceMultiColorBMP(DMImage *screen, const DMC64Image *img)
+{
+    int yc;
+    uint8_t *dp = screen->data;
+    
+    for (yc = 0; yc < C64_SCR_HEIGHT; yc++)
+    {
+        uint8_t *d = dp;
+        const int y = yc / 8, yb = yc & 7;
+        const int scroffsy = y * C64_SCR_CH_WIDTH;
+        const int bmoffsy = y * C64_SCR_WIDTH;
+        int xc;
+
+        for (xc = 0; xc < C64_SCR_WIDTH / 2; xc++)
+        {
+            const int x = xc / 4;
+            const int scroffs = scroffsy + x;
+            const int bmoffs = bmoffsy + (x * 8) + yb;
+            const int v = 6 - ((xc * 2) & 6);
+            const int b1 = (img->bitmap[0][bmoffs] >> v) & 3;
+            const int b2 = (img->bitmap[1][bmoffs] >> v) & 3;
+            uint8_t c1, c2;
+
+            switch (b1)
+            {
+                case 0: c1 = img->bgcolor; break;
+                case 1: c1 = img->screen[0][scroffs] >> 4; break;
+                case 2: c1 = img->screen[0][scroffs] & 15; break;
+                case 3: c1 = img->color[0][scroffs] & 15; break;
+            }
+
+            switch (b2)
+            {
+                case 0: c2 = img->bgcolor; break;
+                case 1: c2 = img->screen[img->laceBank2][scroffs] >> 4; break;
+                case 2: c2 = img->screen[img->laceBank2][scroffs] & 15; break;
+                case 3: c2 = img->color[img->laceBank2][scroffs] & 15; break;
+            }
+            
+            *d++ = c1;
+            *d++ = c2;
+        }
+
+        dp += screen->pitch;
+    }
+
+    return 0;
+}
+
+
+int dmC64ConvertGenericBMP2Image(DMImage *dst, const DMC64Image *src)
+{
+    switch (src->type)
+    {
+        case DM_C64IFMT_HIRES:
+            return dmC64ConvertHiResBMP(dst, src);
+        
+        case DM_C64IFMT_MC:
+            return dmC64ConvertMultiColorBMP(dst, src);
+
+        case DM_C64IFMT_MC_ILACE:
+            return dmC64ConvertLaceMultiColorBMP(dst, src);
+
+        default:
+            return -1;
+    }
+}
+
+
+#define BUF_SIZE_INITIAL   (16*1024)
+#define BUF_SIZE_GROW      (2*1024)
+
+int dmReadDataFile(const char *filename, uint8_t **pbuf, size_t *pbufSize)
+{
+    FILE *f;
+    uint8_t *dataBuf = NULL, *dataPtr;
+    size_t bufSize, readSize, dataSize;
+    
+    if (filename == NULL)
+        f = stdin;
+    else
+    if ((f = fopen(filename, "rb")) == NULL)
+    {
+        int err = errno;
+        dmError("Could not open input file '%s': %d, %s\n",
+            filename, err, strerror(err));
+        return -1;
+    }
+
+    readSize = bufSize = BUF_SIZE_INITIAL;
+    if ((dataBuf = dmMalloc(bufSize)) == NULL)
+    {
+        fclose(f);
+        dmError("Error allocating memory for data, %d bytes.\n", bufSize);
+        return -4;
+    }
+
+    dataPtr = dataBuf;
+    dataSize = 0;
+
+    while (!feof(f) && !ferror(f))
+    {
+        size_t read = fread(dataPtr, 1, readSize, f);
+
+        dataSize += read;
+        dataPtr += read;
+
+        if (read == readSize && !feof(f))
+        {
+            readSize = BUF_SIZE_GROW;
+            bufSize += BUF_SIZE_GROW;
+            if ((dataBuf = dmRealloc(dataBuf, bufSize)) == NULL)
+            {
+                dmError("Error reallocating memory for data, %d bytes.\n", bufSize);
+                return -4;
+            }
+        }
+        else
+            break;
+    }
+
+    fclose(f);
+
+    *pbuf = dataBuf;
+    *pbufSize = dataSize;
+    
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib64gfx.h	Sat Nov 03 02:19:51 2012 +0200
@@ -0,0 +1,184 @@
+/*
+ * Functions for reading and manipulating some C64/etc graphics formats
+ * Programmed and designed by Matti 'ccr' Hamalainen
+ * (C) Copyright 2012 Tecnic Software productions (TNSP)
+ *
+ * Please read file 'COPYING' for information on license and distribution.
+ */
+#ifndef LIB64GFX_H
+#define LIB64GFX_H 1
+
+#include "dmlib.h"
+
+// Bitmap constants
+#define C64_SCR_WIDTH          320
+#define C64_SCR_HEIGHT         200
+#define C64_SCR_CH_WIDTH       (C64_SCR_WIDTH/8)
+#define C64_SCR_CH_HEIGHT      (C64_SCR_HEIGHT/8)
+#define C64_SCR_COLOR_SIZE     (C64_SCR_CH_WIDTH * C64_SCR_CH_HEIGHT)
+#define C64_SCR_SCREEN_SIZE    (C64_SCR_CH_WIDTH * C64_SCR_CH_HEIGHT)
+#define C64_SCR_BITMAP_SIZE    (C64_SCR_WIDTH * C64_SCR_HEIGHT/8)
+#define C64_SCR_EXTRADATA      1024
+#define C64_SCR_MAX_BANK       8
+
+// C64 video screen pixel aspect ratio on PAL
+#define C64_SCR_PAR_XY         (0.9365f)
+
+// Sprite constants
+#define C64_SPR_WIDTH          3 // bytes
+#define C64_SPR_HEIGHT         21 // lines
+#define C64_SPR_WIDTH_PX       (8 * C64_SPR_WIDTH)
+#define C64_SPR_SIZE           ((C64_SPR_WIDTH * C64_SPR_HEIGHT) + 1)
+
+// Character constants
+#define C64_CHR_WIDTH          1 // bytes
+#define C64_CHR_HEIGHT         8 // lines
+#define C64_CHR_WIDTH_PX       (8 * C64_CHR_WIDTH)
+#define C64_CHR_SIZE           (C64_CHR_WIDTH * C64_CHR_HEIGHT)
+
+// Etc.
+#define C64_RAM_SIZE           (64*1024)
+#define C64_NCOLORS            16
+#define C64_MAX_COLORS         16
+#define C64_VIDBANK_SIZE       (16*1024)
+#define C64_MAX_SPRITES        (C64_VIDBANK_SIZE / C64_SPR_SIZE)
+#define C64_MAX_CHARS          256
+
+// Probe scores
+#define DM_PROBE_SCORE_MAX     1000
+#define DM_PROBE_SCORE_GOOD    750
+#define DM_PROBE_SCORE_AVG     500
+#define DM_PROBE_SCORE_MAYBE   250
+#define DM_PROBE_SCORE_FALSE   0
+
+enum
+{
+    DM_IFMT_PALETTE,
+    DM_IFMT_RGB,
+    DM_IFMT_RGBA,
+    DM_IFMT_RGB_PLANE,
+};
+
+
+// RGBx color struct
+typedef struct
+{
+    uint8_t r, g, b, x;
+} DMColor;
+
+
+// Bitmapped image struct (can be one of types specified by DM_IFMT_*)
+typedef struct
+{
+    int width, height, pitch;
+    BOOL constpal;
+    int ncolors, ctrans;
+    DMColor *pal;
+    uint8_t *data;
+} DMImage;
+
+
+// Different supported C64 bitmap "modes"
+enum
+{
+    DM_C64IFMT_HIRES,
+    DM_C64IFMT_MC,
+    DM_C64IFMT_HIRES_ILACE,
+    DM_C64IFMT_MC_ILACE,
+    DM_C64IFMT_HIRES_FLI,
+    DM_C64IFMT_MC_FLI,
+
+    DM_C64IFMT_LAST_TYPE
+};
+
+
+enum
+{
+    DM_C64ILACE_COLOR,
+    DM_C64ILACE_RES,
+};
+
+typedef struct
+{
+    BOOL multicolor, xexpand, yexpand;
+    int color, xc, yc;
+    uint8_t data[C64_SPR_HEIGHT][C64_SPR_WIDTH];
+} DMC64Sprite;
+
+
+typedef struct
+{
+    int type,     // Image type (DM_C64IFMT_*)
+        laceType, // Interlace type (DM_C64ILACE_*)
+        fliType,  // FLI type (if FLI used)
+        fliLines, // FLI on every # line
+        laceBank2;
+
+    uint8_t color[C64_SCR_MAX_BANK][C64_SCR_COLOR_SIZE],
+            bitmap[C64_SCR_MAX_BANK][C64_SCR_BITMAP_SIZE],
+            screen[C64_SCR_MAX_BANK][C64_SCR_SCREEN_SIZE],
+            extradata[C64_SCR_EXTRADATA],
+            d020, bgcolor, d022, d023, d024;
+
+    uint8_t charset[C64_MAX_CHARS][C64_CHR_HEIGHT * C64_CHR_WIDTH];
+    DMC64Sprite sprites[C64_MAX_SPRITES];
+} DMC64Image;
+
+
+enum
+{
+    DT_COLOR_RAM,
+    DT_BITMAP,
+    DT_SCREEN_RAM,
+    DT_BGCOLOR,
+    DT_EXTRADATA,
+    
+    DT_FUNCTION,
+    
+    DT_LAST,
+};
+
+
+typedef struct _DMDecodeOp
+{
+    int    type;
+    size_t offs;
+    int    bank;
+    size_t size;
+    BOOL   (*function)(DMC64Image *img, const struct _DMDecodeOp *op, const uint8_t *buf, const size_t len);
+} DMDecodeOp;
+
+
+typedef struct _DMC64ImageFormat
+{
+    int  type;
+    char *extension;
+    char *name;
+    int  (*probe)(const uint8_t *buf, const size_t len);
+    int  (*decode)(DMC64Image *img, const uint8_t *buf, const size_t len, const struct _DMC64ImageFormat *fmt);
+    int  (*convert)(DMImage *, DMC64Image *);
+
+    int ndecodeOps;
+    DMDecodeOp decodeOps[16];
+} DMC64ImageFormat;
+
+
+extern const size_t      dmC64DefaultSizes[DT_LAST];
+extern DMColor           dmC64Palette[C64_NCOLORS];
+extern DMC64ImageFormat  dmC64ImageFormats[];
+extern const int         ndmC64ImageFormats;
+extern const char *      dmC64ImageTypeNames[];
+
+
+DMImage * dmImageAlloc(int width, int height);
+void      dmImageFree(DMImage *img);
+int       dmImageGetBytesPerPixel(int format);
+
+
+int       dmC64ConvertCSData(DMImage *img, int xoffs, int yoffs, const uint8_t *inBuf, int width, int height, BOOL multicolor, int *colors);
+int       dmC64ProbeGeneric(const uint8_t *buf, const size_t len, DMC64ImageFormat **fmt);
+int       dmC64DecodeGenericBMP(DMC64Image *img, const uint8_t *buf, const size_t len, const DMC64ImageFormat *fmt);
+int       dmC64ConvertGenericBMP2Image(DMImage *screen, const DMC64Image *img);
+int       dmReadDataFile(const char *filename, uint8_t **buf, size_t *size);
+
+#endif // LIB64GFX_H
--- /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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/view64.c	Sat Nov 03 02:19:51 2012 +0200
@@ -0,0 +1,348 @@
+/*
+ * view64 - Display some C64 etc graphics formats via libSDL
+ * Programmed and designed by Matti 'ccr' Hamalainen
+ * (C) Copyright 2012 Tecnic Software productions (TNSP)
+ *
+ * Please read file 'COPYING' for information on license and distribution.
+ */
+#include "dmlib.h"
+#include "dmargs.h"
+#include "dmfile.h"
+#include "lib64gfx.h"
+#include <SDL.h>
+
+
+char * optFilename = NULL;
+int    optVFlags = SDL_SWSURFACE | SDL_HWPALETTE;
+int    optScrWidth, optScrHeight;
+int    optForcedFormat = -1;
+
+
+static DMOptArg optList[] =
+{
+    { 0, '?', "help",       "Show this help", OPT_NONE },
+    { 1, 'v', "verbose",    "Be more verbose", OPT_NONE },
+    { 2,   0, "fs",         "Fullscreen", OPT_NONE },
+    { 3, 'S', "scale",      "Scale image by factor (1-10)", OPT_ARGREQ },
+    { 4, 'f', "format",     "Force input format (see list below)", OPT_ARGREQ },
+};
+
+const int optListN = sizeof(optList) / sizeof(optList[0]);
+
+
+void dmSetScaleFactor(float factor)
+{
+    optScrWidth = (int) ((float) C64_SCR_WIDTH * factor * C64_SCR_PAR_XY);
+    optScrHeight = (int) ((float) C64_SCR_HEIGHT * factor);
+}
+
+
+void argShowHelp()
+{
+    int i;
+
+    dmPrintBanner(stdout, dmProgName, "[options] <input image file>");
+    dmArgsPrintHelp(stdout, optList, optListN);
+
+    printf("\nAvailable bitmap formats:\n");
+    for (i = 0; i < ndmC64ImageFormats; i++)
+    {
+        DMC64ImageFormat *fmt = &dmC64ImageFormats[i];
+        printf("%3d | %-5s | %-15s | %s\n",
+            i, fmt->extension,
+            dmC64ImageTypeNames[fmt->type],
+            fmt->name);
+    }
+}
+
+
+BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
+{
+    switch (optN)
+    {
+        case 0:
+            argShowHelp();
+            exit(0);
+            break;
+
+        case 1:
+            dmVerbosity++;
+            break;
+        
+        case 2:
+            optVFlags |= SDL_FULLSCREEN;
+            break;
+
+        case 3:
+            {
+                float factor;
+                if (sscanf(optArg, "%f", &factor) == 1)
+                {
+                    if (factor < 1 || factor >= 10)
+                    {
+                        dmError("Invalid scale factor %1.0f, see help for valid values.\n", factor);
+                        return FALSE;
+                    }
+
+                    dmSetScaleFactor(factor);
+                }
+                else
+                {
+                    dmError("Invalid scale factor '%s'.\n", optArg);
+                    return FALSE;
+                }
+            }
+            break;
+
+        case 4:
+            {
+                int i;
+                if (sscanf(optArg, "%d", &i) == 1)
+                {
+                    if (i < 0 || i >= ndmC64ImageFormats)
+                    {
+                        dmError("Invalid image format index %d, see help for valid values.\n", i);
+                        return FALSE;
+                    }
+                    optForcedFormat = i;
+                }
+                else
+                {
+                    dmError("Invalid image format argument '%s'.\n", optArg);
+                    return FALSE;
+                }
+            }
+            break;
+        
+        default:
+            dmError("Unknown option '%s'.\n", currArg);
+            return FALSE;
+    }
+    
+    return TRUE;
+}
+
+
+BOOL argHandleFile(char *filename)
+{
+    if (optFilename == NULL)
+    {
+        optFilename = dm_strdup(filename);
+        return TRUE;
+    }
+    else
+    {
+        dmError("Too many filenames specified ('%s')\n", filename);
+        return FALSE;
+    }
+}
+
+
+BOOL dmInitializeVideo(SDL_Surface **screen)
+{
+    *screen = SDL_SetVideoMode(optScrWidth, optScrHeight, 8, optVFlags | SDL_RESIZABLE);
+    if (*screen == NULL)
+    {
+        dmError("Can't SDL_SetVideoMode(): %s\n", SDL_GetError());
+        return FALSE;
+    }
+    return TRUE;
+}
+
+
+int main(int argc, char *argv[])
+{
+    SDL_Surface *screen = NULL, *surf = NULL;
+    DMImage bmap;
+    BOOL initSDL = FALSE, exitFlag, needRedraw;
+    DMC64ImageFormat *fmt;
+    DMC64Image image;
+    char *windowTitle;
+    Uint8 *dataBuf = NULL;
+    size_t dataSize;
+    int i, ret;
+
+    dmSetScaleFactor(2.0);
+    
+    dmInitProg("view64", "Display some C64 bitmap graphics formats", "0.2", NULL, NULL);
+
+    /* Parse arguments */
+    if (!dmArgsProcess(argc, argv, optList, optListN,
+        argHandleOpt, argHandleFile, FALSE))
+        exit(1);
+
+
+    if (optFilename == NULL)
+    {
+        dmError("No input file specified, perhaps you need some --help\n");
+        goto error_exit;
+    }
+        
+    if (dmReadDataFile(optFilename, &dataBuf, &dataSize) != 0)
+        goto error_exit;
+
+    // Probe for format
+    if (optForcedFormat >= 0)
+    {
+        fmt = &dmC64ImageFormats[optForcedFormat];
+        dmMsg(0,"Forced %s format image, type %d, %s\n", fmt->name, fmt->type, fmt->extension);
+
+        if (fmt->decode != NULL)
+            ret = fmt->decode(&image, dataBuf + 2, dataSize - 2, fmt);
+        else
+            ret = dmC64DecodeGenericBMP(&image, dataBuf + 2, dataSize - 2, fmt);
+
+        if (ret < 0)
+        {
+            dmError("Error decoding image format.\n");
+            return -1;
+        }
+    }
+    else
+    {
+        BOOL found = FALSE;
+        for (i = 0; i < ndmC64ImageFormats; i++)
+        {
+            fmt = &dmC64ImageFormats[i];
+            ret = fmt->probe(dataBuf, dataSize);
+            if (ret > 0)
+            {
+                dmMsg(0,"Probed %s format image, type %d, %s\n", fmt->name, fmt->type, fmt->extension);
+                if (fmt->decode != NULL)
+                    ret = fmt->decode(&image, dataBuf + 2, dataSize - 2, fmt);
+                else
+                    ret = dmC64DecodeGenericBMP(&image, dataBuf + 2, dataSize - 2, fmt);
+
+                if (ret < 0)
+                {
+                    dmError("Error decoding image format.\n");
+                    return -1;
+                }
+                
+                found = TRUE;
+                break;
+            }
+            else
+            if (ret < 0)
+            {
+                dmError("Error in probing.\n");
+            }
+        }
+        if (!found)
+        {
+            dmError("Probing could not find any matching image format. Perhaps try forcing a format via -f\n");
+            return -2;
+        }
+    }
+
+    // Initialize libSDL
+    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0)
+    {
+        dmError("Could not initialize SDL: %s\n", SDL_GetError());
+        goto error_exit;
+    }
+    initSDL = TRUE;
+
+
+    // Open window/set video mode
+    screen = SDL_SetVideoMode(optScrWidth, optScrHeight, 8, optVFlags | SDL_RESIZABLE);
+    if (screen == NULL)
+    {
+        dmError("Can't SDL_SetVideoMode(): %s\n", SDL_GetError());
+        goto error_exit;
+    }
+
+    // Create surface (we are lazy and ugly)
+    surf = SDL_CreateRGBSurface(SDL_SWSURFACE, C64_SCR_WIDTH, C64_SCR_HEIGHT, 8, 0, 0, 0, 0);
+    SDL_SetColors(surf, (SDL_Color *)dmC64Palette, 0, C64_NCOLORS);
+    SDL_SetColors(screen, (SDL_Color *)dmC64Palette, 0, C64_NCOLORS);
+
+    // Convert bitmap (this is a bit ugly and lazy here)
+    bmap.data = surf->pixels;
+    bmap.pitch = surf->pitch;
+    bmap.width = surf->w;
+    bmap.height = surf->h;
+    bmap.constpal = TRUE;
+
+    if (fmt->convert != NULL)
+        ret = fmt->convert(&bmap, &image);
+    else
+        ret = dmC64ConvertGenericBMP2Image(&bmap, &image);
+
+
+    // Set window title and caption
+    windowTitle = dm_strdup_printf("%s - %s", dmProgName, optFilename);
+    SDL_WM_SetCaption(windowTitle, dmProgName);
+    dmFree(windowTitle);
+
+
+    // Start main loop
+    needRedraw = TRUE;
+    exitFlag = FALSE;
+    while (!exitFlag)
+    {
+        SDL_Event event;
+        while (SDL_PollEvent(&event))
+        switch (event.type)
+        {
+            case SDL_KEYDOWN:
+                switch (event.key.keysym.sym)
+                {
+                    case SDLK_ESCAPE: exitFlag = TRUE; break;
+                    
+                    default:
+                        break;
+                }
+
+                needRedraw = TRUE;
+                break;
+            
+            case SDL_VIDEORESIZE:
+                optScrWidth = event.resize.w;
+                optScrHeight = event.resize.h;
+
+                if (!dmInitializeVideo(&screen))
+                    goto error_exit;
+
+                needRedraw = TRUE;
+                break;
+            
+            case SDL_VIDEOEXPOSE:
+                needRedraw = TRUE;
+                break;
+
+            case SDL_QUIT:
+                exit(0);
+        }
+        
+        if (needRedraw)
+        {
+            if (SDL_MUSTLOCK(screen) != 0 && SDL_LockSurface(screen) != 0)
+            {
+                dmError("Can't lock surface.\n");
+                goto error_exit;
+            }
+            
+            dmScaledBlitSurface8to8(surf, 0, 0, screen->w, screen->h, screen);
+            SDL_SetColors(screen, (SDL_Color *)dmC64Palette, 0, C64_NCOLORS);
+
+            if (SDL_MUSTLOCK(screen) != 0)
+                SDL_UnlockSurface(screen);
+
+            SDL_Flip(screen);
+            needRedraw = FALSE;
+        }
+        
+        SDL_Delay(100);
+    }
+
+
+error_exit:
+    if (screen)
+        SDL_FreeSurface(screen);
+
+    if (initSDL)
+        SDL_Quit();
+
+    return 0;
+}