view tools/lib64gfx.c @ 1463:bde6a66bc2f6

Change dmDecodeGenericRLE() to use DMGrowBuf as output. Also add support for two different common types of RLE encoding. Add stub function of dmEncodeGenericRLE(), not implemented yet. Change functions using dmDecodeGenericRLE to use the new API. Also fix a problem in Amica Paint RLE decoding as the format seems to save one byte less than is necessary.
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 10 May 2018 21:27:24 +0300
parents 9cb6dd1046bf
children 88845f95e791
line wrap: on
line source

/*
 * 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-2018 Tecnic Software productions (TNSP)
 *
 * Please read file 'COPYING' for information on license and distribution.
 */
#include "lib64gfx.h"

#define BUF_SIZE_INITIAL   (16*1024)
#define BUF_SIZE_GROW      (4*1024)


// Based on Pepto's palette, stolen from VICE
DMColor dmDefaultC64Palette[C64_NCOLORS] =
{
    { 0x00, 0x00, 0x00, 0xff },
    { 0xFF, 0xFF, 0xFF, 0xff },
    { 0x68, 0x37, 0x2B, 0xff },
    { 0x70, 0xA4, 0xB2, 0xff },
    { 0x6F, 0x3D, 0x86, 0xff },
    { 0x58, 0x8D, 0x43, 0xff },
    { 0x35, 0x28, 0x79, 0xff },
    { 0xB8, 0xC7, 0x6F, 0xff },
    { 0x6F, 0x4F, 0x25, 0xff },
    { 0x43, 0x39, 0x00, 0xff },
    { 0x9A, 0x67, 0x59, 0xff },
    { 0x44, 0x44, 0x44, 0xff },
    { 0x6C, 0x6C, 0x6C, 0xff },
    { 0x9A, 0xD2, 0x84, 0xff },
    { 0x6C, 0x5E, 0xB5, 0xff },
    { 0x95, 0x95, 0x95, 0xff },
};

#define DM_RLE_MARKER    1
#define DM_RLE_MASK      2


#define DM_GET_ADDR_LO(addr) ((addr) & 0xff)
#define DM_GET_ADDR_HI(addr) (((addr) >> 8) & 0xff)


void dmSetDefaultC64Palette(DMImage *img)
{
    img->constpal = TRUE;
    img->pal      = dmDefaultC64Palette;
    img->ncolors  = C64_NCOLORS;
    img->ctransp  = 255;
}


static BOOL dmCompareAddr16(const Uint8 *buf, const size_t offs, const Uint16 addr)
{
    return buf[offs    ] == DM_GET_ADDR_LO(addr) &&
           buf[offs + 1] == DM_GET_ADDR_HI(addr);
}


int dmC64ImageGetNumBanks(const DMC64ImageFormat *fmt)
{
    int nbanks = 0;
    for (int i = 0; i < D64_MAX_ENCDEC_OPS; i++)
    {
        const DMC64EncDecOp *op = &fmt->encdecOps[i];
        if (op->type == DT_LAST)
            break;

        if (op->bank > nbanks)
            nbanks = op->bank;
    }

    return nbanks + 1;
}


DMC64Image *dmC64ImageAlloc(const DMC64ImageFormat *fmt)
{
    DMC64Image *img = dmMalloc0(sizeof(DMC64Image));

    if (img == NULL)
        return NULL;

    // Initialize image information
    img->width     = fmt->width;
    img->height    = fmt->height;
    img->ch_width  = fmt->ch_width;
    img->ch_height = fmt->ch_height;
    img->nbanks    = dmC64ImageGetNumBanks(fmt);

    // Allocate banks
    if ((img->color = dmCalloc(img->nbanks, sizeof(Uint8 *))) == NULL ||
        (img->bitmap = dmCalloc(img->nbanks, sizeof(Uint8 *))) == NULL ||
        (img->screen = dmCalloc(img->nbanks, sizeof(Uint8 *))) == NULL ||
        (img->charmem = dmCalloc(img->nbanks, sizeof(Uint8 *))) == NULL)
        goto err;

    for (int i = 0; i < img->nbanks; i++)
    {
        if ((img->color[i] = dmMalloc0(img->ch_width * img->ch_height)) == NULL)
            goto err;

        if ((img->bitmap[i] = dmMalloc0(img->ch_width * img->ch_height * 8)) == NULL)
            goto err;

        if ((img->screen[i] = dmMalloc0(img->ch_width * img->ch_height)) == NULL)
            goto err;

        if ((img->charmem[i] = dmMalloc0(C64_MAX_CHARS * C64_CHR_SIZE)) == NULL)
            goto err;
    }

    return img;

err:
    dmC64ImageFree(img);
    return NULL;
}


void dmC64ImageFree(DMC64Image *img)
{
    if (img != NULL)
    {
        // Free the allocated areas
        for (int i = 0; i < img->nbanks; i++)
        {
            dmFree(img->color[i]);
            dmFree(img->bitmap[i]);
            dmFree(img->screen[i]);
            dmFree(img->charmem[i]);
        }

        // Free the pointers to the areas
        dmFree(img->color);
        dmFree(img->bitmap);
        dmFree(img->screen);
        dmFree(img->charmem);

        // Extra data ..
        for (int i = 0; i < C64_MAX_EXTRA_DATA; i++)
            dmFree(img->extraData[i]);

        dmMemset(img, 0, sizeof(DMC64Image));
        dmFree(img);
    }
}


char * dmC64GetImageTypeString(char *buf, const size_t len, const int type)
{
    snprintf(buf, len,
        "%s%s%s%s",
        (type & D64_FMT_FLI) ? "FLI " : "",
        (type & D64_FMT_MC) ? "MCol " : "HiRes ",
        (type & D64_FMT_ILACE) ? "Ilace " : "",
        (type & D64_FMT_CHAR) ? "CharMap" : ""
        );

    return buf;
}


int dmC64ConvertCSDataToImage(DMImage *img,
    int xoffs, int yoffs, const Uint8 *buf,
    int width, int height, BOOL multicolor,
    int *colors)
{
    int yc, widthpx = width * 8;
    Uint8 *dp;

    if (img == NULL)
        return DMERR_NULLPTR;

    if (xoffs < 0 || yoffs < 0 ||
        xoffs > img->width - widthpx ||
        yoffs > img->height - height)
        return DMERR_INVALID_ARGS;

    dp = img->data + (yoffs * img->pitch) + xoffs;

    if (multicolor)
    {
        for (yc = 0; yc < height; yc++)
        {
            const int offs = yc * width;
            int xc;
            Uint8 *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 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 *d = dp;

            for (xc = 0; xc < widthpx; xc++)
            {
                const int b = buf[offs + (xc / 8)];
                const int v = 7 - (xc & 7);
                const Uint8 c = colors[(b >> v) & 1];

                *d++ = c;
            }

            dp += img->pitch;
        }
    }

    return DMERR_OK;
}


static int dmDecodeGenericRLE(DMGrowBuf *dst, const Uint8 *src, const Uint8 *srcEnd, const Uint8 rleMarker, const Uint8 rleMask, const int rleType)
{
    int res;

    if ((res = dmGrowBufAlloc(dst, BUF_SIZE_INITIAL, BUF_SIZE_GROW)) != DMERR_OK)
        goto err;

    // Perform RLE decode
    while (src < srcEnd)
    {
        Uint8 c = *src++;
        int cnt = 1;

        switch (rleType)
        {
            case DM_RLE_MARKER:
                if (c == rleMarker)
                {
                    if (srcEnd - src < 2)
                    {
                        res = dmError(DMERR_INVALID_DATA,
                            "Foobar: %d\n", srcEnd - src);
                        goto err;
                    }
                    cnt = *src++;
                    c = *src++;
                }
                break;

            case DM_RLE_MASK:
                if ((c & rleMask) == rleMarker)
                {
                    if (srcEnd - src < 1)
                    {
                        res = dmError(DMERR_INVALID_DATA,
                            "foobar2\n");
                        goto err;
                    }
                    // XXX TODO actually we probably want another mask here
                    cnt = c & (0xff ^ rleMask);
                    c = *src++;
                }
                break;
        }

        while (cnt--)
        {
            if (!dmGrowBufPutU8(dst, c))
            {
                res = dmError(DMERR_MALLOC,
                    "bazzooo\n");
                goto err;
            }
        }
    }

    // Reallocate the memory
    if ((res = dmGrowBufResize(dst)) != DMERR_OK)
        goto err;

    res = DMERR_OK;

err:
    return res;
}


static int dmEncodeGenericRLE(DMGrowBuf *dst, const Uint8 *src, const Uint8 *srcEnd, const Uint8 rleMarker)
{
    (void) dst;
    (void) src;
    (void) srcEnd;
    (void) rleMarker;
    return DMERR_OK;
}


static inline Uint8 dmC64GetGenericSCPixel(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int vshift, const int vbank, const int vbitmap, const int cbank)
{
    (void) cbank;
    if ((img->bitmap[vbitmap][bmoffs] >> vshift) & 1)
        return img->screen[vbank][scroffs] >> 4;
    else
        return img->screen[vbank][scroffs] & 15;
}


static inline Uint8 dmC64GetGenericMCPixel(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int vshift, const int vbank, const int vbitmap, const int cbank)
{
    switch ((img->bitmap[vbitmap][bmoffs] >> vshift) & 3)
    {
        case  0: return img->bgcolor; break;
        case  1: return img->screen[vbank][scroffs] >> 4; break;
        case  2: return img->screen[vbank][scroffs] & 15; break;
        default: return img->color[cbank][scroffs] & 15; break;
    }
}


static inline Uint8 fmtGetGenericSCPixel(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int vshift, const int vbitmap, const int raster)
{
    (void) raster;
    return dmC64GetGenericSCPixel(img, bmoffs, scroffs, vshift, 0, vbitmap, 0);
}


static inline Uint8 fmtGetGenericMCPixel(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int vshift, const int vbitmap, const int raster)
{
    (void) raster;
    return dmC64GetGenericMCPixel(img, bmoffs, scroffs, vshift, 0, vbitmap, 0);
}


static int fmtProbeDrazPaint20Packed(const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    const char *ident = (const char *) buf + 2;

    if (len > 22 &&
        dmCompareAddr16(buf, 0, fmt->addr) &&
        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 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf mem;

    if ((res = dmDecodeGenericRLE(&mem, buf + 0x0e, buf + len, *(buf + 0x0d), 0, DM_RLE_MARKER)) != DMERR_OK)
        goto out;

    res = dmC64DecodeGenericBMP(img, mem.data, mem.len, fmt);

out:
    dmGrowBufFree(&mem);
    return res;
}


static int fmtProbeDrazLace10Packed(const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    if (len > 22 &&
        dmCompareAddr16(buf, 0, fmt->addr) &&
        strncmp((const char *) (buf + 2), "DRAZLACE! 1.0", 13) == 0)
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


static BOOL fmtDrazLaceSetLaceType(DMC64Image *img, const struct _DMC64EncDecOp *op, const Uint8 *buf, const size_t len)
{
    (void) len;
    img->laceType = buf[op->offs] ? D64_ILACE_RES : D64_ILACE_COLOR;
    return TRUE;
}


static int fmtProbeGunPaint(const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    if (len > 0x400 &&
        dmCompareAddr16(buf, 0, fmt->addr) &&
        strncmp((const char *) (buf + 0x3ea), "GUNPAINT (JZ) ", 14) == 0)
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


static int fmtProbeAmicaPaintPacked(const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    size_t i, n;

    if (len < 2048 || !dmCompareAddr16(buf, 0, fmt->addr))
        return DM_PROBE_SCORE_FALSE;

    // Interpaint Hi-Res gives a false positive
    // as do some GunPaint images ..
    if (len == 9002 || fmtProbeGunPaint(buf, len, fmt) > DM_PROBE_SCORE_GOOD)
        return DM_PROBE_SCORE_FALSE;

    for (n = 0, i = 2; i < len; i++)
        if (buf[i] == 0xC2) n++;

    if (n > 50)
        return DM_PROBE_SCORE_GOOD;
    if (n > 25)
        return DM_PROBE_SCORE_AVG;
    if (n > 10)
        return DM_PROBE_SCORE_MAYBE;

    return DM_PROBE_SCORE_FALSE;
}


static int fmtDecodeAmicaPaintPacked(DMC64Image *img, const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf mem, tmp;

    // Amica Paint apparently is broken and stores one byte less than it should
    // so we need to do some crappy buffer expansion here ..
    if ((res = dmGrowBufAlloc(&tmp, len + 4, 4)) != DMERR_OK)
        return res;

    tmp.len = len;
    memcpy(tmp.data, buf, len);
    tmp.data[tmp.len++] = 0;

    // Now do an RLE decode on the enlarged buffer
    if ((res = dmDecodeGenericRLE(&mem, tmp.data, tmp.data + tmp.len, 0xC2, 0, DM_RLE_MARKER)) != DMERR_OK)
        goto out;

    // And finally decode to bitmap struct
    res = dmC64DecodeGenericBMP(img, mem.data, mem.len, fmt);

out:
    dmGrowBufFree(&tmp);
    dmGrowBufFree(&mem);
    return res;
}


static int fmtProbeFLIDesigner(const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    if (len == fmt->size &&
        (dmCompareAddr16(buf, 0, 0x3c00) || dmCompareAddr16(buf, 0, 0x3ff0)))
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


static BOOL fmtTruePaintSetLaceType(DMC64Image *img, const struct _DMC64EncDecOp *op, const Uint8 *buf, const size_t len)
{
    (void) op;
    (void) buf;
    (void) len;
    img->laceType = D64_ILACE_RES;
    return TRUE;
}


static Uint8 fmtGetPixelTruePaint(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int vshift, const int vbitmap, const int raster)
{
    (void) raster;
    return dmC64GetGenericMCPixel(img, bmoffs, scroffs, vshift, 0, vbitmap, 0);
}


#define XX2_MIN_SIZE 4000

static int fmtProbeFormatXX2(const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    if (len >= XX2_MIN_SIZE && len <= XX2_MIN_SIZE + 8 &&
        dmCompareAddr16(buf, 0, fmt->addr))
        return DM_PROBE_SCORE_MAYBE;

    return DM_PROBE_SCORE_FALSE;
}


static int fmtDecodeFormatXX2(DMC64Image *img, const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    int res;

    // If there is only data for less than XX2_MIN_SIZE bytes,
    // allocate a buffer of that size and copy data there.
    // Otherwise allocate len bytes.
    size_t nlen = len < XX2_MIN_SIZE ? XX2_MIN_SIZE : len;
    Uint8 *mem = dmMalloc0(nlen);
    if (mem == NULL)
        return DMERR_MALLOC;

    memcpy(mem, buf, len);
    res = dmC64DecodeGenericBMP(img, mem, nlen, fmt);

    dmFree(mem);
    return res;
}


#define FUNPAINT2_HEADER_SIZE (0x10)

static BOOL fmtProbeFunPaint2Header(const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    return
        len > 30 &&
        dmCompareAddr16(buf, 0, fmt->addr) &&
        strncmp((const char *) (buf + 2), "FUNPAINT (MT) ", 14) == 0;
}


static int fmtProbeFunPaint2Unpacked(const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    if (fmtProbeFunPaint2Header(buf, len, fmt) &&
        buf[2 + 14] == 0)
        return DM_PROBE_SCORE_MAX;
    else
        return DM_PROBE_SCORE_FALSE;
}


static int fmtProbeFunPaint2Packed(const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    if (fmtProbeFunPaint2Header(buf, len, fmt) &&
        buf[2 + 14] != 0)
        return DM_PROBE_SCORE_MAX;
    else
        return DM_PROBE_SCORE_FALSE;
}


static int fmtDecodeFunPaint2Unpacked(DMC64Image *img, const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    return dmC64DecodeGenericBMP(img, buf + FUNPAINT2_HEADER_SIZE, len - FUNPAINT2_HEADER_SIZE, fmt);
}


static int fmtDecodeFunPaint2Packed(DMC64Image *img, const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf mem;
    dmGrowBufInit(&mem);

    if ((res = dmDecodeGenericRLE(&mem, buf + FUNPAINT2_HEADER_SIZE, buf + len, *(buf + 15), 0, DM_RLE_MARKER)) != DMERR_OK)
        goto out;

    res = dmC64DecodeGenericBMP(img, mem.data, mem.len, fmt);

out:
    dmGrowBufFree(&mem);
    return res;
}


static Uint8 fmtGetPixelFunPaint2(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int vshift, const int vbitmap, const int raster)
{
    const int vbank = (raster & 7) + (vbitmap * 8);
    int vr, vb;
    if (raster < 100)
    {
        vb = 0;
        vr = raster;
    }
    else
    {
        vb = 0;
        vr = raster - 100;
    }

    switch ((img->bitmap[vbitmap][bmoffs] >> vshift) & 3)
    {
        case  0: return img->extraData[vb][vr] & 15; break;
        case  1: return img->screen[vbank][scroffs] >> 4; break;
        case  2: return img->screen[vbank][scroffs] & 15; break;
        default: return img->color[0][scroffs] & 15; break;
    }
}


static Uint8 fmtGetPixelGunPaint(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int vshift, const int vbitmap, const int raster)
{
    const int vbank = (raster & 7);// + (vbitmap * 8);
    int vr, vb;
    if (raster < 177)
    {
        vb = 0;
        vr = raster;
    }
    else
    {
        vb = 0;
        vr = raster - 177;
    }

    switch ((img->bitmap[vbitmap][bmoffs] >> vshift) & 3)
    {
        case  0: return img->extraData[vb][vr] & 15; break;
        case  1: return img->screen[vbank][scroffs] >> 4; break;
        case  2: return img->screen[vbank][scroffs] & 15; break;
        default: return img->color[0][scroffs] & 15; break;
    }
}


static Uint8 fmtGetPixelBMFLI(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int vshift, const int vbitmap, const int raster)
{
    const int vbank = raster & 7;
    switch ((img->bitmap[vbitmap][bmoffs] >> vshift) & 3)
    {
        case  0: return img->extraData[0][raster]; break;
        case  1: return img->screen[vbank][scroffs] >> 4; break;
        case  2: return img->screen[vbank][scroffs] & 15; break;
        default: return img->color[0][scroffs] & 15; break;
    }
}


static Uint8 fmtGetPixelFLIDesigner(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int vshift, const int vbitmap, const int raster)
{
    return dmC64GetGenericMCPixel(img, bmoffs, scroffs, vshift, raster & 7, vbitmap, 0);
}


static Uint8 fmtGetPixelCHFLI(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int vshift, const int vbitmap, const int raster)
{
    const int vbank = raster & 7;

    if ((img->bitmap[vbitmap][bmoffs] >> vshift) & 1)
        return img->screen[vbank][scroffs] >> 4;
    else
        return img->screen[vbank][scroffs] & 15;
}


//
// Array with data for supported formats
//
#define DEF_SCREEN_RAM(start, oindex, bindex, osize) { DT_SCREEN_RAM, (start) + ((osize) * (oindex)), (bindex), 0, NULL, NULL }
#define DEF_SCREEN_RAMS_8(start, sindex, osize) \
    DEF_SCREEN_RAM((start), 0, (sindex + 0), (osize)), \
    DEF_SCREEN_RAM((start), 1, (sindex + 1), (osize)), \
    DEF_SCREEN_RAM((start), 2, (sindex + 2), (osize)), \
    DEF_SCREEN_RAM((start), 3, (sindex + 3), (osize)), \
    DEF_SCREEN_RAM((start), 4, (sindex + 4), (osize)), \
    DEF_SCREEN_RAM((start), 5, (sindex + 5), (osize)), \
    DEF_SCREEN_RAM((start), 6, (sindex + 6), (osize)), \
    DEF_SCREEN_RAM((start), 7, (sindex + 7), (osize)),


const DMC64ImageFormat dmC64ImageFormats[] =
{
    {
        D64_FMT_MC, "d2p", "DrazPaint 2.0 (packed)", 0x5800, 0,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        fmtProbeDrazPaint20Packed,
        fmtDecodeDrazPaintPacked, NULL,
        NULL, NULL,
        NULL,
        {
            { DT_COLOR_RAM,    0x0000, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0800, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x0400, 0,  0, NULL, NULL },
            { DT_COLOR_REG,    0x2740, 0,  DC_BGCOL, NULL, NULL },
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_MC | D64_FMT_ILACE, "dlp", "DrazLace 1.0 (packed)", 0x5800, 0,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        fmtProbeDrazLace10Packed,
        fmtDecodeDrazPaintPacked, NULL,
        NULL, NULL,
        NULL,
        {
            { DT_COLOR_RAM,    0x0000, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0800, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x0400, 0,  0, NULL, NULL },
            { DT_COLOR_REG,    0x2740, 0,  DC_BGCOL, NULL, NULL },
            { DT_BITMAP,       0x2800, 1,  0, NULL, NULL },
            { DT_DEC_FUNCTION, 0x2742, 0,  1, fmtDrazLaceSetLaceType, NULL },
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_MC, "drp", "DrazPaint (unpacked)", 0x5800, 10051,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        {
            { DT_COLOR_RAM,    0x0000, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0800, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x0400, 0,  0, NULL, NULL },
            { DT_COLOR_REG,    0x2740, 0,  DC_BGCOL, NULL, NULL },
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_MC | D64_FMT_ILACE, "drl", "DrazLace 1.0 (unpacked)", 0x5800, 18242,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        {
            { DT_COLOR_RAM,    0x0000, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0800, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x0400, 0,  0, NULL, NULL },
            { DT_COLOR_REG,    0x2740, 0,  DC_BGCOL, NULL, NULL },
            { DT_BITMAP,       0x2800, 1,  0, NULL, NULL },
            { DT_DEC_FUNCTION, 0x2742, 0,  1, fmtDrazLaceSetLaceType, NULL },
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_MC | D64_FMT_ILACE, "mci", "Truepaint (unpacked)", 0x9c00, 19434,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        fmtGetPixelTruePaint,
        {
            { DT_SCREEN_RAM,   0x0000, 0,  0, NULL, NULL },
            { DT_COLOR_REG,    0x03e8, 0,  DC_BGCOL, NULL, NULL },
            { DT_BITMAP,       0x0400, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x2400, 1,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x4400, 1,  0, NULL, NULL },
            { DT_COLOR_RAM,    0x4800, 0,  0, NULL, NULL },
            { DT_DEC_FUNCTION, 0x0000, 0,  0, fmtTruePaintSetLaceType, NULL },
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_MC, "kla", "Koala Paint (unpacked)", 0x6000, 10003,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        {
            { DT_BITMAP,       0x0000, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1f40, 0,  0, NULL, NULL },
            { DT_COLOR_RAM,    0x2328, 0,  0, NULL, NULL },
            { DT_COLOR_REG,    0x2710, 0,  DC_BGCOL, NULL, NULL },
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_MC, "ocp", "Advanced Art Studio (unpacked)", 0x2000, 10018,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        {
            { DT_BITMAP,       0x0000, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1f40, 0,  0, NULL, NULL },
            { DT_COLOR_RAM,    0x2338, 0,  0, NULL, NULL },
            { DT_COLOR_REG,    0x2329, 0,  DC_BGCOL, NULL, NULL },
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_MC, "ami", "Amica Paint (packed)", 0x4000, 0,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        fmtProbeAmicaPaintPacked,
        fmtDecodeAmicaPaintPacked, NULL,
        NULL, NULL,
        NULL,
        {
            { DT_COLOR_RAM,    0x2328, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0000, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1f40, 0,  0, NULL, NULL },
            { DT_COLOR_REG,    0x2710, 0,  DC_BGCOL, NULL, NULL },
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_MC, "rpm", "Run Paint (unpacked)", 0x6000, 10006,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        {
            { DT_COLOR_RAM,    0x2328, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0000, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1f40, 0,  0, NULL, NULL },
            { DT_COLOR_REG,    0x2710, 0,  DC_BGCOL, NULL, NULL },
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_HIRES, "art", "Art Studio (unpacked)", 0x2000, 9009,
        C64_SCR_WIDTH    , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        {
            { DT_BITMAP,       0x0000, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1f40, 0,  0, NULL, NULL },
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_HIRES, "iph", "Interpaint (unpacked)", 0x4000, 9002,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        {
            { DT_BITMAP,       0x0000, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1f40, 0,  0, NULL, NULL },
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_MC, "ipc", "Interpaint MC (unpacked)", 0x4000, 10003,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        {
            { DT_BITMAP,       0x0000, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1f40, 0,  0, NULL, NULL },
            { DT_COLOR_RAM,    0x2328, 0,  0, NULL, NULL },
            { DT_COLOR_REG,    0x2710, 0,  DC_BGCOL, NULL, NULL },
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_HIRES, "dd", "Doodle (unpacked)", 0x1c00, 9218,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        {
            { DT_SCREEN_RAM,   0x0000, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0400, 0,  0, NULL, NULL },
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_MC | D64_FMT_FLI, "bml", "Blackmail FLI (unpacked)", 0x3b00, 17474,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        fmtGetPixelBMFLI,
        {
            { DT_EXTRA_DATA,   0x0000, 0,  200, NULL, NULL },
            { DT_COLOR_RAM,    0x0100, 0,  0, NULL, NULL },
            DEF_SCREEN_RAMS_8( 0x0500, 0,  0x400)
            { DT_BITMAP,       0x2500, 0,  0, NULL, NULL },
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_MC | D64_FMT_FLI, "fli", "FLI Designer (unpacked)", 0, 17409,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        fmtProbeFLIDesigner,
        NULL, NULL,
        NULL, NULL,
        fmtGetPixelFLIDesigner,
        {
            { DT_COLOR_RAM,    0x0000, 0,  0, NULL, NULL },
            DEF_SCREEN_RAMS_8( 0x0400, 0,  0x400)
            { DT_BITMAP,       0x2400, 0,  0, NULL, NULL },
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_MC, "xx1", "Unknown $2000 format (unpacked)", 0x2000, 10242,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        {
            { DT_BITMAP,       0x0000, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x2000, 0,  0, NULL, NULL },
            { DT_COLOR_RAM,    0x2400, 0,  0, NULL, NULL },
            { DT_COLOR_SET,    0x00  , 0,  DC_BGCOL, NULL, NULL },
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },

#define XX2_WIDTH_CH   40
#define XX2_HEIGHT_CH  10
#define XX2_SIZE       (XX2_WIDTH_CH * XX2_HEIGHT_CH)
#define XX2_BSIZE      (XX2_SIZE * 8)

    {
        D64_FMT_MC, "xx2", "Unknown $2000 format (unpacked)", 0x2000, 0,
        XX2_WIDTH_CH * 4, XX2_HEIGHT_CH * 8,
        XX2_WIDTH_CH    , XX2_HEIGHT_CH,
        fmtProbeFormatXX2,
        fmtDecodeFormatXX2, NULL,
        NULL, NULL,
        NULL,
        {
            { DT_BITMAP,       0x0000, 0,  XX2_BSIZE, NULL, NULL },
            { DT_COLOR_RAM,    XX2_BSIZE + XX2_SIZE, 0,  XX2_SIZE, NULL, NULL },
            { DT_SCREEN_RAM,   XX2_BSIZE, 0,  XX2_SIZE, NULL, NULL },
            { DT_COLOR_SET,    11,     0,  DC_BGCOL, NULL, NULL },
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_MC | D64_FMT_FLI | D64_FMT_ILACE, "fp2", "FunPaint II (unpacked)", 0x3ff0, 33694,
        C64_SCR_WIDTH, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        fmtProbeFunPaint2Unpacked,
        fmtDecodeFunPaint2Unpacked, NULL,
        NULL, NULL,
        fmtGetPixelFunPaint2,
        {
            DEF_SCREEN_RAMS_8( 0x0000, 0,  0x400)
            { DT_BITMAP,       0x2000, 0,  0, NULL, NULL },
            { DT_EXTRA_DATA,   0x3f40, 0,  100, NULL, NULL },
            { DT_COLOR_RAM,    0x4000, 0,  0, NULL, NULL },
            DEF_SCREEN_RAMS_8( 0x43e8, 8,  0x400)
            { DT_BITMAP,       0x63e8, 1,  0, NULL, NULL },
            { DT_EXTRA_DATA,   0x8328, 1,  100, NULL, NULL },
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_MC | D64_FMT_FLI | D64_FMT_ILACE, "fp2p", "FunPaint II (packed)", 0x3ff0, 0,
        C64_SCR_WIDTH, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        fmtProbeFunPaint2Packed,
        fmtDecodeFunPaint2Packed, NULL,
        NULL, NULL,
        fmtGetPixelFunPaint2,
        {
            DEF_SCREEN_RAMS_8( 0x0000, 0,  0x400)
            { DT_BITMAP,       0x2000, 0,  0, NULL, NULL },
            { DT_EXTRA_DATA,   0x3f40, 0,  100, NULL, NULL },
            { DT_COLOR_RAM,    0x4000, 0,  0, NULL, NULL },
            DEF_SCREEN_RAMS_8( 0x43e8, 8,  0x400)
            { DT_BITMAP,       0x63e8, 1,  0, NULL, NULL },
            { DT_EXTRA_DATA,   0x8328, 1,  100, NULL, NULL },
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_MC | D64_FMT_FLI | D64_FMT_ILACE, "gun", "GunPaint (unpacked)", 0x4000, 0,
        C64_SCR_WIDTH, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        fmtProbeGunPaint,
        NULL, NULL,
        NULL, NULL,
        fmtGetPixelGunPaint,
        {
            DEF_SCREEN_RAMS_8( 0x0000, 0,  0x400)
            { DT_BITMAP,       0x2000, 0,  0, NULL, NULL },
            { DT_EXTRA_DATA,   0x3f4f, 0,  177, NULL, NULL },
            { DT_COLOR_RAM,    0x4000, 0,  0, NULL, NULL },
            DEF_SCREEN_RAMS_8( 0x4400, 8,  0x400)
            { DT_BITMAP,       0x6400, 1,  0, NULL, NULL },
            { DT_EXTRA_DATA,   0x47e8, 1,  20, NULL, NULL },
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_HIRES | D64_FMT_FLI, "chi", "Crest Hires FLI Designer (unpacked)", 0x4000, 16386,
        C64_SCR_WIDTH, 14 * 8,
        C64_SCR_CH_WIDTH , 14,
        NULL,
        NULL, NULL,
        NULL, NULL,
        fmtGetPixelCHFLI,
        {
            { DT_BITMAP,       0x0000, 0,  0, NULL, NULL },
            DEF_SCREEN_RAMS_8( 0x2000, 0,  0x400)
            { DT_LAST,         0,      0,  0, NULL, NULL },
        }
    },
};

const int ndmC64ImageFormats = sizeof(dmC64ImageFormats) / sizeof(dmC64ImageFormats[0]);


// Perform probing of the given data buffer, trying to determine
// if it contains a supported "C64" image format. Returns the
// "probe score", see libgfx.h for list of values. If a match
// is found, pointer to format description is set to *pfmt.
int dmC64ProbeBMP(const Uint8 *buf, const size_t len, const DMC64ImageFormat **pfmt)
{
    int scoreMax = DM_PROBE_SCORE_FALSE, scoreIndex = -1;

    for (int i = 0; i < ndmC64ImageFormats; i++)
    {
        const DMC64ImageFormat *fmt = &dmC64ImageFormats[i];
        int score = DM_PROBE_SCORE_FALSE;
        if (fmt->probe == NULL && fmt->size > 0 && fmt->addr > 0)
        {
            // Generic probe just checks matching size and load address
            if (len == fmt->size && dmCompareAddr16(buf, 0, fmt->addr))
                score = DM_PROBE_SCORE_GOOD;
        }
        else
        if (fmt->probe != NULL)
            score = fmt->probe(buf, len, fmt);

        if (score > scoreMax)
        {
            scoreMax = score;
            scoreIndex = i;
        }
    }

    if (scoreIndex >= 0)
    {
        *pfmt = &dmC64ImageFormats[scoreIndex];
        return scoreMax;
    }
    else
        return DM_PROBE_SCORE_FALSE;
}


static int dmC64SanityCheckEncDecOp(const int i, const DMC64EncDecOp *op, const DMC64Image *img)
{
    switch (op->type)
    {
        case DT_COLOR_RAM:
        case DT_BITMAP:
        case DT_SCREEN_RAM:
        case DT_CHAR_DATA:
            if (op->bank < 0 || op->bank > img->nbanks)
            {
                return dmError(DMERR_INTERNAL,
                    "Invalid bank %d / %d definition in generic encode/decode operator %d @ #%d.\n",
                    op->bank, img->nbanks, op->type, i);
            }
            break;

        case DT_EXTRA_DATA:
            if (op->bank < 0 || op->bank >= C64_MAX_EXTRA_DATA)
            {
                return dmError(DMERR_INTERNAL,
                    "Invalid bank %d definition in generic encode/decode operator %d @ #%d.\n",
                    op->bank, op->type, i);
            }
            break;
    }

    if (op->type < 0 || op->type >= DT_LAST)
    {
        return dmError(DMERR_INTERNAL,
            "Invalid encode/decode operator type %d @ #%d.\n",
            op->type, i);
    }

    return DMERR_OK;
}


static BOOL dmC64GetOpSize(const DMC64EncDecOp *op, const DMC64ImageFormat *fmt, size_t *size)
{
    BOOL check;
    switch (op->type)
    {
        case DT_SCREEN_RAM:
        case DT_COLOR_RAM:
            *size = fmt->ch_height * fmt->ch_width;
            check = TRUE;
            break;

        case DT_BITMAP:
            *size = fmt->ch_height * fmt->ch_width * 8;
            check = TRUE;
            break;

        case DT_EXTRA_DATA:
            *size = op->size;
            check = TRUE;
            break;

        case DT_CHAR_DATA:
            *size = C64_MAX_CHARS * C64_CHR_SIZE;
            check = TRUE;
            break;

        case DT_COLOR_REG:
            *size = 1;
            check = FALSE;
            break;

        default:
            *size = 0;
            check = FALSE;
    }

    if (op->size != 0)
    {
        if (check && op->size > *size)
            return FALSE;

        *size = op->size;
    }

    return TRUE;
}


int dmC64DecodeGenericBMP(DMC64Image *img, const Uint8 *buf,
    const size_t len, const DMC64ImageFormat *fmt)
{
    int res = DMERR_OK;

    if (buf == NULL || img == NULL || fmt == NULL)
        return DMERR_NULLPTR;

    // Clear the image structure, set basics
    img->type      = fmt->type;
    img->width     = fmt->width;
    img->height    = fmt->height;
    img->ch_width  = fmt->ch_width;
    img->ch_height = fmt->ch_height;
    img->nbanks    = dmC64ImageGetNumBanks(fmt);

    // Perform decoding
    for (int i = 0; i < D64_MAX_ENCDEC_OPS; i++)
    {
        const DMC64EncDecOp *op = &fmt->encdecOps[i];
        const Uint8 *src;
        size_t size;

        // Check for last operator
        if (op->type == DT_LAST)
            break;

        // Check operation validity
        if ((res = dmC64SanityCheckEncDecOp(i, op, img)) != DMERR_OK)
            return res;

        // Check size
        if (!dmC64GetOpSize(op, fmt, &size))
        {
            return dmError(DMERR_INVALID_DATA,
                "Decode op SIZE out of bounds, op #%d type=%d, offs=%d ($%04x), "
                "bank=%d, size=%d ($%04x) vs. allocated %d ($%04x)\n",
                i, op->type, op->offs, op->offs, op->bank, size, size, op->size, op->size);
        }

        // Do we need to reallocate some more space?
        if (op->offs + size > len)
        {
            return dmError(DMERR_INVALID_DATA,
                "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);
        }

        src = buf + op->offs;

        // Perform operation
        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_CHAR_DATA:   memcpy(img->charmem[op->bank], src, size); break;
            case DT_EXTRA_DATA:
                if (img->extraData[op->bank] != NULL)
                {
                    return dmError(DMERR_INTERNAL,
                        "Extra data block already allocated and used! "
                        "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                        i, op->offs, op->offs, op->bank, size, size, len, len);
                }
                if ((img->extraData[op->bank] = dmMalloc0(size)) == NULL)
                {
                    return dmError(DMERR_MALLOC,
                        "Could not allocate extradata block! "
                        "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                        i, op->offs, op->offs, op->bank, size, size, len, len);
                }
                img->extraDataSizes[op->bank] = size;
                memcpy(img->extraData[op->bank], src, size);
                break;

            case DT_COLOR_REG:
                switch (op->size)
                {
                    case DC_D020: img->d020 = *src; break;
                    case DC_BGCOL:
                    case DC_D021: img->bgcolor = *src; break;
                    case DC_D022: img->d022 = *src; break;
                    case DC_D023: img->d023 = *src; break;
                    case DC_D024: img->d024 = *src; break;
                    default:
                        return dmError(DMERR_INTERNAL,
                            "Unhandled DT_COLOR_REG mode %d in "
                            "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                            op->size, i, op->offs, op->offs, op->bank, size, size, len, len);
                }
                break;

            case DT_COLOR_SET:
                switch (op->size)
                {
                    case DC_D020: img->d020 = op->offs; break;
                    case DC_BGCOL:
                    case DC_D021: img->bgcolor = op->offs; break;
                    case DC_D022: img->d022 = op->offs; break;
                    case DC_D023: img->d023 = op->offs; break;
                    case DC_D024: img->d024 = op->offs; break;
                    default:
                        return dmError(DMERR_INTERNAL,
                            "Unhandled DT_COLOR_SET mode %d in "
                            "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                            op->size, i, op->offs, op->offs, op->bank, size, size, len, len);
                }
                break;

            case DT_CHAR_CONFIG:
                switch (op->offs)
                {
                    case D64_CHCFG_SCREEN:
                        break;

                    case D64_CHCFG_LINEAR:
                        {
                            for (int bank = 0; bank < img->nbanks; bank++)
                            for (int offs = 0; offs < fmt->ch_height * fmt->ch_width; offs++)
                                img->screen[bank][offs] = offs & 0xff;
                        }
                        break;

                    default:
                        return dmError(DMERR_INTERNAL,
                            "Unhandled DT_CHAR_CONFIG mode %d in ",
                            "op #%d, bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                            op->offs, i, op->bank, size, size, len, len);
                }
                break;

            case DT_DEC_FUNCTION:
                if (op->decfunction == NULL)
                {
                    return dmError(DMERR_INTERNAL,
                        "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);
                }
                if (!op->decfunction(img, op, buf, len))
                {
                    return dmError(DMERR_INTERNAL,
                        "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);
                }
                break;
        }
    }

    return DMERR_OK;
}


int dmC64EncodeGenericBMP(Uint8 **pbuf, size_t *plen, const DMC64Image *img, const DMC64ImageFormat *fmt)
{
    int res = DMERR_OK;
    Uint8 *buf;
    size_t allocated;

    if (pbuf == NULL || plen == NULL || img == NULL || fmt == NULL)
        return DMERR_NULLPTR;

    // Allocate the output buffer
    *plen = 0;
    if (fmt->size > 0)
        *plen = allocated = fmt->size;
    else
        allocated = 16 * 1024;

    if ((buf = dmMalloc(allocated)) == NULL)
    {
        return dmError(DMERR_MALLOC,
            "Could not allocate %d bytes of memory for C64 image encoding buffer.\n",
            allocated);
        goto err;
    }

    // Perform encoding
    for (int i = 0; i < D64_MAX_ENCDEC_OPS; i++)
    {
        const DMC64EncDecOp *op = &fmt->encdecOps[i];
        Uint8 *dst = buf + op->offs;
        size_t size, chksize;

        // Check for last operator
        if (op->type == DT_LAST)
            break;

        // Check operation validity
        if ((res = dmC64SanityCheckEncDecOp(i, op, img)) != DMERR_OK)
            goto err;

        // Check size
        if (!dmC64GetOpSize(op, fmt, &size))
        {
            res = dmError(DMERR_INVALID_DATA,
                "Decode op SIZE out of bounds, op #%d type=%d, offs=%d ($%04x), "
                "bank=%d, size=%d ($%04x) vs. allocated %d ($%04x)\n",
                i, op->type, op->offs, op->offs, op->bank, size, size, op->size, op->size);
            goto err;
        }

        // Do we need to reallocate some more space?
        chksize = op->offs + size;
        if (chksize > allocated)
        {
            size_t diff = allocated - chksize,
                   grow = (diff / (BUF_SIZE_GROW - 1)) * BUF_SIZE_GROW;

            allocated += grow;

            if ((buf = dmRealloc(buf, allocated)) == NULL)
            {
                res = dmError(DMERR_MALLOC,
                    "Could not re-allocate %d bytes of memory for C64 image encoding buffer.\n",
                    allocated);
                goto err;
            }
        }

        if (fmt->size == 0 && chksize > *plen)
            *plen = chksize;

        // Perform operation
        switch (op->type)
        {
            case DT_COLOR_RAM:   memcpy(dst, img->color[op->bank], size); break;
            case DT_BITMAP:      memcpy(dst, img->bitmap[op->bank], size); break;
            case DT_SCREEN_RAM:  memcpy(dst, img->screen[op->bank], size); break;
            case DT_CHAR_DATA:   memcpy(dst, img->charmem[op->bank], size); break;
            case DT_EXTRA_DATA:
                if (img->extraData[op->bank] == NULL)
                {
                    res = dmError(DMERR_NULLPTR,
                        "DT_EXTRA_DATA block is NULL in ",
                        "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                        i, op->offs, op->offs, op->bank, size, size, *plen, *plen);
                    goto err;
                }
                if (size > img->extraDataSizes[op->bank])
                {
                    res = dmError(DMERR_INTERNAL,
                        "DT_EXTRA_DATA size mismatch %d <> %d in ",
                        "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                        op->size, img->extraDataSizes[op->bank], i, op->offs, op->offs, op->bank, size, size, *plen, *plen);
                    goto err;
                }
                memcpy(dst, img->extraData[op->bank], size);
                break;

            case DT_COLOR_REG:
                switch (op->size)
                {
                    case DC_D020: *dst = img->d020; break;
                    case DC_BGCOL:
                    case DC_D021: *dst = img->bgcolor; break;
                    case DC_D022: *dst = img->d022; break;
                    case DC_D023: *dst = img->d023; break;
                    case DC_D024: *dst = img->d024; break;
                    default:
                        res = dmError(DMERR_INTERNAL,
                            "Unhandled DT_COLOR_REG mode %d in ",
                            "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                            op->size, i, op->offs, op->offs, op->bank, size, size, *plen, *plen);
                        goto err;
                }
                break;

            case DT_ENC_FUNCTION:
                if (op->encfunction == NULL)
                {
                    res = dmError(DMERR_INTERNAL,
                        "Encode 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, *plen, *plen);
                    goto err;
                }
                /*
                if (!op->encfunction(op, buf, len))
                {
                    res = dmError(DMERR_INTERNAL,
                        "Encode 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);
                    goto out;
                }
                */
                break;
        }
    }

    res = DMERR_OK;

err:
    *pbuf = buf;
    return res;
}


// Convert a generic "C64" format bitmap in DMC64Image struct to
// a indexed/paletted bitmap image.
int dmC64ConvertGenericBMP2Image(DMImage *dst, const DMC64Image *src, const DMC64ImageFormat *fmt)
{
    DMC64GetPixelFunc getPixel;

    // Sanity check arguments
    if (dst == NULL || src == NULL)
        return DMERR_NULLPTR;

    if (dst->width < src->width || dst->height < src->height)
    {
        return dmError(DMERR_INVALID_DATA,
            "Invalid src vs. dst width/height %d x %d <-> %d x %d\n",
            src->width, src->height, dst->width, dst->height);
    }

    dmMemset(dst->data, 0, dst->size);

    // Check pixel getter function
    if (fmt->getPixel != NULL)
        getPixel = fmt->getPixel;
    else
        getPixel = (fmt->type & D64_FMT_MC) ? fmtGetGenericMCPixel : fmtGetGenericSCPixel;

    // Resolution interlaced pics need to halve the source width
    int rwidth = src->width;
    if ((src->type & D64_FMT_ILACE) && src->laceType == D64_ILACE_RES)
        rwidth /= 2;

    // Perform conversion
    Uint8 *dp = dst->data;
    for (int yc = 0; yc < src->height; yc++)
    {
        Uint8 *d = dp;
        const int y = yc / 8, yb = yc & 7;
        const int scroffsy = y * src->ch_width;
        int xc;

        if (src->type & D64_FMT_CHAR)
        {
            // Charmode conversion
            if ((src->type & D64_FMT_MC) == D64_FMT_HIRES)
            // Hi-res charmap
            for (xc = 0; xc < rwidth; xc++)
            {
                const int x = xc / 8;
                const int scroffs = scroffsy + x;
                const int chr = src->screen[0][scroffs];
                const int v = 7 - (xc & 7);

                if ((src->charmem[0][chr * C64_CHR_SIZE + yb] >> v) & 1)
                    *d++ = src->color[0][scroffs];
                else
                    *d++ = src->bgcolor;
            }
            else
            // Multicolor variants
            for (xc = 0; xc < rwidth; xc++)
            {
                const int x = xc / 4;
                const int scroffs = scroffsy + x;
                const int chr = src->screen[0][scroffs];
                const int col = src->color[0][scroffs] & 15;

                if (col & 8)
                {
                    const int v = 6 - ((xc * 2) & 6);
                    switch ((src->charmem[0][chr * C64_CHR_SIZE + yb] >> v) & 3)
                    {
                        case 0: *d++ = src->bgcolor; break;
                        case 1: *d++ = src->d022; break;
                        case 2: *d++ = src->d023; break;
                        case 3: *d++ = col;
                    }
                }
                else
                {
                    const int v = 7 - (xc & 7);
                    if ((src->charmem[0][chr * C64_CHR_SIZE + yb] >> v) & 1)
                        *d++ = src->color[0][scroffs];
                    else
                        *d++ = src->bgcolor;
                }
            }
        }
        else
        {
            // Perform generic BITMAP conversion
            const int bmoffsy = y * src->ch_width * 8 + yb;

            if ((src->type & D64_FMT_MC) == D64_FMT_HIRES)
            // Hi-res bitmap
            for (xc = 0; xc < rwidth; xc++)
            {
                const int x = xc / 8;
                const int scroffs = scroffsy + x;
                const int bmoffs = bmoffsy + (x * 8);
                const int vshift = 7 - (xc & 7);

                *d++ = getPixel(src, bmoffs, scroffs, vshift, 0, yc);
            }
            else
            // Multicolor bitmap and variants
            for (xc = 0; xc < rwidth; xc++)
            {
                const int x = xc / 4;
                const int scroffs = scroffsy + x;
                const int bmoffs = bmoffsy + (x * 8);
                const int vshift = 6 - ((xc * 2) & 6);

                if (src->type & D64_FMT_ILACE)
                {
                    switch (src->laceType)
                    {
                        case D64_ILACE_RES:
                            *d++ = getPixel(src, bmoffs, scroffs, vshift, 0, yc);
                            *d++ = getPixel(src, bmoffs, scroffs, vshift, 1, yc);
                            break;

                        default:
                            return DMERR_NOT_SUPPORTED;
                    }
                }
                else
                {
                    *d++ = getPixel(src, bmoffs, scroffs, vshift, 0, yc);
                }
            }
        }
        dp += dst->pitch;
    }

    return DMERR_OK;
}


int dmC64ConvertBMP2Image(DMImage **pdst, const DMC64Image *src, const DMC64ImageFormat *fmt)
{
    int res;
    DMImage *dst;

    if (pdst == NULL || src == NULL)
        return DMERR_NULLPTR;

    // Allocate image structure
    if ((*pdst = dst = dmImageAlloc(src->width, src->height, DM_IFMT_PALETTE, -1)) == NULL)
        return DMERR_MALLOC;

    // Set partial palette information
    dst->ncolors  = C64_NCOLORS;
    dst->constpal = TRUE;

    // Convert
    if (fmt->convertFrom != NULL)
        res = fmt->convertFrom(dst, src, fmt);
    else
        res = dmC64ConvertGenericBMP2Image(dst, src, fmt);

    return res;
}


int dmC64DecodeBMP(DMC64Image **img, const Uint8 *buf, const size_t len,
    const size_t probeOffs, const size_t loadOffs,
    const DMC64ImageFormat **fmt, const DMC64ImageFormat *forced)
{
    if (img == NULL)
        return DMERR_NULLPTR;

    // Check for forced format
    if (forced != NULL)
        *fmt = forced;
    else
    {
        // Nope, perform a generic probe
        if (probeOffs >= len)
            return DMERR_OUT_OF_DATA;

        if (dmC64ProbeBMP(buf + probeOffs, len - probeOffs, fmt) == DM_PROBE_SCORE_FALSE)
            return DMERR_NOT_SUPPORTED;
    }

    if (loadOffs >= len)
        return DMERR_INVALID_ARGS;

    if (*fmt == NULL)
        return DMERR_INVALID_DATA;

    // Allocate memory
    if ((*img = dmC64ImageAlloc(*fmt)) == NULL)
        return DMERR_MALLOC;

    // Decode the bitmap to memory layout
    if ((*fmt)->decode != NULL)
        return (*fmt)->decode(*img, buf + loadOffs, len - loadOffs, *fmt);
    else
        return dmC64DecodeGenericBMP(*img, buf + loadOffs, len - loadOffs, *fmt);
}