view tools/lib64fmts.c @ 2208:90ec1ec89c56

Revamp the palette handling in lib64gfx somewhat, add helper functions to lib64util for handling external palette file options and add support for specifying one of the "internal" palettes or external (.act) palette file to gfxconv and 64vw.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 14 Jun 2019 05:01:12 +0300
parents 9f3fb4004c20
children 75b5bb490f38
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-2019 Tecnic Software productions (TNSP)
 *
 * Please read file 'COPYING' for information on license and distribution.
 */
#include "lib64gfx.h"

#define DM_MEMCMP_SIZE(mptr, mcmp) memcmp((mptr), (mcmp), sizeof(mcmp))
#define DM_MEMCMP_LEN(mptr, mcmp) memcmp((mptr), (mcmp), strlen(mcmp))

static int fmtProbeGigapaintHires(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    if (buf->len == fmt->size &&
        dmCompareAddr16(buf, 0, fmt->addr))
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


// XXX TODO: Research what these values actually mean. It would seem probable
// that these may not be static values at all, as there are 8 more that change
// before actual image data, but do not seem to be used in the image itself.
static const Uint8 fmtMicroIllustrator_MagicID_1[] =
{
    0xff, 0x80, 0x69, 0x67, 0x14, 0x00,
};

static const Uint8 fmtMicroIllustrator_MagicID_2[] =
{
    0xe8, 0x03, 0xe8, 0x03, 0x40, 0x1f,
};

static int fmtProbeMicroIllustrator(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    if (buf->len == fmt->size &&
        DM_MEMCMP_SIZE(buf->data + 2, fmtMicroIllustrator_MagicID_1) == 0
        &&
        DM_MEMCMP_SIZE(buf->data + 9, fmtMicroIllustrator_MagicID_2) == 0
        )
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


static int fmtEncodeMicroIllustrator(const DMC64EncDecOp *op, DMGrowBuf *buf,
    const DMC64Image *img, const DMC64ImageCommonFormat *fmt)
{
    (void) op;
    (void) img;
    (void) fmt;

    memcpy(buf->data + 2, fmtMicroIllustrator_MagicID_1, sizeof(fmtMicroIllustrator_MagicID_1));
    memcpy(buf->data + 9, fmtMicroIllustrator_MagicID_2, sizeof(fmtMicroIllustrator_MagicID_2));

    return DMERR_OK;
}


static const Uint8 fmtMarqPETSCII_ID1[] =
{
    0x01, 0x08, 0x0b, 0x08, 0xef, 0x00, 0x9e, 0x32, 0x30, 0x36,
    0x31, 0x00, 0x00, 0x00, 0xa9, 0x0b, 0x8d, 0x11, 0xd0, 0xa9,
};

static const Uint8 fmtMarqPETSCII_ID2[] =
{
    0x8d, 0x21, 0xd0, 0xa2, 0x00, 0xa0, 0xfa, 0xbd, 0x61, 0x08,
    0x9d, 0x00, 0x04, 0xbd, 0x5b, 0x09, 0x9d, 0xfa, 0x04, 0xbd,
    0x55, 0x0a, 0x9d, 0xf4,
};


static int fmtProbeMarqPETSCII(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    (void) fmt;

    if (buf->len == 2098 &&
        DM_MEMCMP_SIZE(buf->data, fmtMarqPETSCII_ID1) == 0 &&
        DM_MEMCMP_SIZE(buf->data + 0x1f, fmtMarqPETSCII_ID2) == 0
        )
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


static int fmtSetMarqPETSCIIData(const DMC64EncDecOp *op, DMC64Image *img,
    const DMGrowBuf *buf, const DMC64ImageCommonFormat *fmt)
{
    (void) op;
    (void) buf;
    (void) fmt;

    switch (img->extraData[0].data[0])
    {
        case 20: img->extraInfo[D64_EI_CHAR_CASE] = 0; break; // upper case
        case 23: img->extraInfo[D64_EI_CHAR_CASE] = 1; break; // lower case
        default:
            return DMERR_INVALID_DATA;
    }

    img->extraInfo[D64_EI_CHAR_MODE] = D64_FMT_HIRES;
    img->extraInfo[D64_EI_CHAR_CUSTOM] = 0;

    return DMERR_OK;
}


static const Uint8 fmtPetsciiKrisszHu_ID1[] =
{
    0x01, 0x08, 0x0b, 0x08, 0x0A, 0x00, 0x9E, 0x32, 0x30, 0x36,
    0x31, 0x00, 0x00, 0x00, 0xA9,
};

static const Uint8 fmtPetsciiKrisszHu_ID2[] =
{
    0x8d, 0x11, 0xd0, 0xa9, 0x80, 0x8d, 0x91, 0x02, 0xa9, 0x18,
};


static int fmtProbePetsciiKrisszHu(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    (void) fmt;

    if (buf->len == 10193 &&
        DM_MEMCMP_SIZE(buf->data, fmtPetsciiKrisszHu_ID1) == 0 &&
        DM_MEMCMP_SIZE(buf->data + 0x10, fmtPetsciiKrisszHu_ID2) == 0
        )
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


static int fmtSetPetsciiKrisszHuData(const DMC64EncDecOp *op, DMC64Image *img,
    const DMGrowBuf *buf, const DMC64ImageCommonFormat *fmt)
{
    (void) op;
    (void) buf;
    (void) fmt;

    const Uint8 *data = img->extraData[0].data;
    switch (data[0x0028 - 2])
    {
        case 0x00:
            img->extraInfo[D64_EI_CHAR_MODE] = D64_FMT_HIRES;
            img->d020    = data[0x001e - 2];
            img->bgcolor = data[0x0023 - 2];
            break;

        case 0xd8:
            img->extraInfo[D64_EI_CHAR_MODE] = D64_FMT_MC;
            img->d020    = data[0x001e - 2];
            img->bgcolor = data[0x0023 - 2];
            img->d022    = data[0x002d - 2];
            img->d023    = data[0x0032 - 2];
            break;

        case 0x01:
            img->extraInfo[D64_EI_CHAR_MODE] = D64_FMT_ECM;
            img->d020    = data[0x001e - 2];
            img->bgcolor = data[0x0023 - 2];
            img->d022    = data[0x0028 - 2];
            img->d023    = data[0x002d - 2];
            img->d024    = data[0x0032 - 2];
            break;

        default:
            return DMERR_INVALID_DATA;
    }

    // XXX TODO this format saves the charset data (for 256 chars)
    // in the PRG and there is no direct indication whether it is
    // a customized one or copy of C64 ROM charset .. we could
    // implement a hash-based detection at some point.
    img->extraInfo[D64_EI_CHAR_CASE] = 0;
    img->extraInfo[D64_EI_CHAR_CUSTOM] = 1;

    return DMERR_OK;
}


static int fmtSetSPETSCIIData(const DMC64EncDecOp *op, DMC64Image *img,
    const DMGrowBuf *buf, const DMC64ImageCommonFormat *fmt)
{
    (void) op;
    (void) buf;
    (void) fmt;

    img->extraInfo[D64_EI_CHAR_MODE] = D64_FMT_HIRES;
    img->extraInfo[D64_EI_CHAR_CUSTOM] = 0;

    return DMERR_OK;
}


static int fmtProbeKoalaPainter(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    int score = DM_PROBE_SCORE_FALSE;

    if (buf->len == 10003 ||
        buf->len == 10004)
        score += DM_PROBE_SCORE_MAYBE;

    if (dmCompareAddr16(buf, 0, fmt->addr))
        score += DM_PROBE_SCORE_MAYBE;

    return score;
}


static int fmtProbeKoalaPainterPacked(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    // Attempt to prevent misprobes of unpacked Koala and Run Paint
    if (buf->len > 30 &&
        buf->len < 10002 &&
        dmCompareAddr16(buf, 0, fmt->addr))
        return DM_PROBE_SCORE_GOOD;

    return DM_PROBE_SCORE_FALSE;
}


static int fmtProbeDoodle(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    if (buf->len > 32 &&
        (dmCompareAddr16(buf, 0, 0x1c00) || dmCompareAddr16(buf, 0, 0x5c00)))
    {
        // Packed variant
        if (fmt->size == 0xfe &&
            buf->len != fmt->size &&
            buf->len != 10242) // Attempt to avoid misprobes of "Rainbow Painter (unpacked)"
            return DM_PROBE_SCORE_MAX;

        // Unpacked variant
        if (fmt->size != 0xfe && buf->len == fmt->size)
            return DM_PROBE_SCORE_MAX;
    }

    return DM_PROBE_SCORE_FALSE;
}


static int fmtProbeArtStudio(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    if ((buf->len == fmt->size || buf->len == 9002) &&
        dmCompareAddr16(buf, 0, 0x2000))
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


static int fmtDecodeStaticRLEMarkerMode2(DMC64Image *img, const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf mem;
    DMCompParams cfg;

    cfg.func         = fmt->name;
    cfg.type         = DM_COMP_RLE_MARKER;
    cfg.flags        = DM_RLE_BYTE_RUNS | DM_RLE_ORDER_2;
    cfg.rleMarkerB   = fmt->size;

    if ((res = dmDecodeGenericRLEAlloc(&mem, buf, &cfg)) != DMERR_OK)
        goto out;

    res = dmC64DecodeGenericBMP(img, &mem, fmt);

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


static int fmtEncodeStaticRLEMarkerMode2(DMGrowBuf *buf, const DMC64Image *img, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf tmp;
    DMCompParams cfg;

    // Encode the data to temp buffer
    if ((res = dmC64EncodeGenericBMP(TRUE, &tmp, img, fmt)) != DMERR_OK)
        goto out;

    // And now RLE compress the data to the existing buffer
    cfg.func         = fmt->name;
    cfg.type         = DM_COMP_RLE_MARKER;
    cfg.flags        = DM_RLE_BYTE_RUNS | DM_RLE_ORDER_2;
    cfg.rleMarkerB   = fmt->size;
    cfg.rleMinCountB = 4;
    cfg.rleMaxCountB = 255;
    res = dmEncodeGenericRLE(buf, &tmp, &cfg);

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


static int fmtProbeDrazPaint20Packed(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    const Uint8 *ident = buf->data + 2;
    if (buf->len > 22 &&
        dmCompareAddr16(buf, 0, fmt->addr) &&
        DM_MEMCMP_LEN(ident, "DRAZPAINT ") == 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 DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf mem, tmp;
    DMCompParams cfg;

    cfg.func         = fmt->name;
    cfg.type         = DM_COMP_RLE_MARKER;
    cfg.flags        = DM_RLE_BYTE_RUNS | DM_RLE_ORDER_1;
    cfg.rleMarkerB   = buf->data[0x0d];

    if ((res = dmDecodeGenericRLEAlloc(&mem,
        dmGrowBufConstCopyOffs(&tmp, buf, 0x0e), &cfg)) != DMERR_OK)
        goto out;

    res = dmC64DecodeGenericBMP(img, &mem, fmt);

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


static int fmtEncodeDrazPaintPacked(DMGrowBuf *buf, const DMC64Image *img, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf tmp;
    DMCompParams cfg;
    const char *magicID = (fmt->format->type & D64_FMT_ILACE) ? "DRAZLACE! 1.0" : "DRAZPAINT 2.0";

    // Encode the data to temp buffer
    if ((res = dmC64EncodeGenericBMP(TRUE, &tmp, img, fmt)) != DMERR_OK)
        goto out;

    // Analyze and setup RLE
    cfg.func         = fmt->name;
    cfg.type         = DM_COMP_RLE_MARKER;
    cfg.flags        = DM_RLE_BYTE_RUNS | DM_RLE_ORDER_1;
    cfg.rleMinCountB = 4;
    cfg.rleMaxCountB = 255;

    dmGenericRLEAnalyze(&tmp, &cfg);

    // Add the header bits
    if (!dmGrowBufPut(buf, (Uint8 *) magicID, strlen(magicID)) ||
        !dmGrowBufPutU8(buf, cfg.rleMarkerB))
    {
        res = DMERR_MALLOC;
        goto out;
    }

    // And now RLE compress the data to the existing buffer
    res = dmEncodeGenericRLE(buf, &tmp, &cfg);

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


static int fmtProbeDrazLace10Packed(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    if (buf->len > 22 &&
        dmCompareAddr16(buf, 0, fmt->addr) &&
        DM_MEMCMP_LEN(buf->data + 2, "DRAZLACE! 1.0") == 0)
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


static int fmtDrazLaceGetLaceType(const DMC64EncDecOp *op, DMC64Image *img,
    const DMGrowBuf *buf, const DMC64ImageCommonFormat *fmt)
{
    (void) fmt;

    img->laceType = buf->data[op->offs] ? D64_ILACE_RES : D64_ILACE_COLOR;
    return DMERR_OK;
}


static int fmtDrazLaceSetLaceType(const DMC64EncDecOp *op, DMGrowBuf *buf,
    const DMC64Image *img, const DMC64ImageCommonFormat *fmt)
{
    (void) fmt;
    buf->data[op->offs] = (img->laceType == D64_ILACE_RES) ? 1 : 0;
    return DMERR_OK;
}


static int fmtGetPixelDrazLace(Uint8 *col,
    const DMC64Image *img, const int rasterX, const int rasterY)
{
    const int
        x = rasterX / 8,
        y = rasterY / 8,
        yoffs = y * img->fmt->chWidth,
        bmoffs = yoffs * 8 + (rasterY & 7) + (x * 8),
        scroffs = yoffs + x,
        vshift = 6 - (rasterX & 6);

    return dmC64GetGenericMCPixel(col, img,
        bmoffs, scroffs,
        vshift, 0,
        rasterX & 1, 0, img->bgcolor);
}


static const char *fmtBDP5_MagicID = "BDP 5.00";

static int fmtProbeBDP5Packed(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    if (buf->len > 20 &&
        dmCompareAddr16(buf, 0, fmt->addr) &&
        DM_MEMCMP_LEN(buf->data + 2, fmtBDP5_MagicID) == 0)
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


static int fmtDecodeBDP5Packed(DMC64Image *img, const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf mem, tmp;
    DMCompParams cfg;

    cfg.func         = fmt->name;
    cfg.type         = DM_COMP_RLE_MARKER;
    cfg.flags        = DM_RLE_BYTE_RUNS | DM_RLE_WORD_RUNS | DM_RLE_ORDER_1;
    cfg.rleMarkerB   = buf->data[8];
    cfg.rleMarkerW   = buf->data[9];

    // Boogie Down Paint apparently is broken and stores one byte less
    // than it should in some cases so we need to do some crappy buffer
    // expansion here ..
    if (dmGrowBufCopyOffs(&tmp, buf, 10, 1) == NULL)
        return DMERR_MALLOC;

    tmp.len = tmp.size;

    if ((res = dmDecodeGenericRLEAlloc(&mem, &tmp, &cfg)) != DMERR_OK)
        goto out;

    res = dmC64DecodeGenericBMP(img, &mem, fmt);

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


static int fmtEncodeBDP5Packed(DMGrowBuf *buf, const DMC64Image *img, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf mem;
    DMCompParams cfg;

    // Encode the data to temp buffer
    if ((res = dmC64EncodeGenericBMP(TRUE, &mem, img, fmt)) != DMERR_OK)
        goto out;

    // Analyze and setup RLE
    cfg.func         = fmt->name;
    cfg.type         = DM_COMP_RLE_MARKER;
    cfg.flags        = DM_RLE_BYTE_RUNS | DM_RLE_WORD_RUNS | DM_RLE_ORDER_1;
    cfg.rleMinCountB = 4;
    cfg.rleMaxCountB = 255;
    cfg.rleMinCountW = 256;
    cfg.rleMaxCountW = 1024;

    dmGenericRLEAnalyze(&mem, &cfg);

    // Add the header bits
    if (!dmGrowBufPut(buf, (Uint8 *) fmtBDP5_MagicID, strlen(fmtBDP5_MagicID)) ||
        !dmGrowBufPutU8(buf, cfg.rleMarkerB) ||
        !dmGrowBufPutU8(buf, cfg.rleMarkerW))
    {
        res = DMERR_MALLOC;
        goto out;
    }

    // And now RLE compress the data to the existing buffer
    res = dmEncodeGenericRLE(buf, &mem, &cfg);

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


static const char *fmtGunPaint_MagicID = "GUNPAINT (JZ)   ";
#define fmtGunPaint_MagicLen   (16)
#define fmtGunPaint_MagicOffs  (0x03e8)

static int fmtProbeGunPaint(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    if (buf->len > fmtGunPaint_MagicOffs + fmtGunPaint_MagicLen &&
        dmCompareAddr16(buf, 0, fmt->addr) &&
        memcmp(buf->data + fmtGunPaint_MagicOffs + 2, fmtGunPaint_MagicID, fmtGunPaint_MagicLen) == 0)
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


static int fmtEncodeGunPaint(const DMC64EncDecOp *op, DMGrowBuf *buf,
    const DMC64Image *img, const DMC64ImageCommonFormat *fmt)
{
    (void) op;
    (void) img;
    (void) fmt;

    // Here we assume that the op triggering this function is
    // at the end of the oplist, so the memory is allocated,
    memcpy(buf->data + fmtGunPaint_MagicOffs + 2, fmtGunPaint_MagicID, fmtGunPaint_MagicLen);

    return DMERR_OK;
}


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

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

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

    for (n = 0, i = 2; i < buf->len; i++)
        if (buf->data[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 DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf mem, tmp;
    DMCompParams cfg;

    // Amica Paint apparently is broken and stores one byte less than it should
    // so we need to do some crappy buffer expansion here ..
    if (dmGrowBufCopy(&tmp, buf, 1) == NULL)
        return DMERR_MALLOC;

    tmp.len = tmp.size;

    // Now do an RLE decode on the enlarged buffer
    cfg.func         = fmt->name;
    cfg.type         = DM_COMP_RLE_MARKER;
    cfg.flags        = DM_RLE_BYTE_RUNS | DM_RLE_ORDER_1;
    cfg.rleMarkerB   = 0xC2;

    if ((res = dmDecodeGenericRLEAlloc(&mem, &tmp, &cfg)) != DMERR_OK)
        goto out;

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

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


static int fmtEncodeAmicaPaintPacked(DMGrowBuf *buf, const DMC64Image *img, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf mem;
    DMCompParams cfg;

    // Encode the data to temp buffer
    if ((res = dmC64EncodeGenericBMP(TRUE, &mem, img, fmt)) != DMERR_OK)
        goto out;

    // And now RLE compress the data to the existing buffer
    cfg.func         = fmt->name;
    cfg.type         = DM_COMP_RLE_MARKER;
    cfg.flags        = DM_RLE_BYTE_RUNS | DM_RLE_ORDER_1;
    cfg.rleMarkerB   = 0xC2;
    cfg.rleMinCountB = 4;
    cfg.rleMaxCountB = 255;

    res = dmEncodeGenericRLE(buf, &mem, &cfg);

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


static int fmtProbeSaracenPaint(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    if ((buf->len == 10219 || buf->len == 10220) &&
        dmCompareAddr16(buf, 0, fmt->addr))
        return DM_PROBE_SCORE_GOOD;

    return DM_PROBE_SCORE_FALSE;
}


static int fmtGetPixelFLIDesigner(Uint8 *col,
    const DMC64Image *img, const int rasterX, const int rasterY)
{
    DM_C64_GENERIC_MC_PIXEL_DEFS(img)

    return dmC64GetGenericMCPixel(col, img,
        bmoffs, scroffs,
        vshift, rasterY & 7,
        0, 0, img->bgcolor);
}


static int fmtProbeBlackMailFLIPacked(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    if (buf->len > 16 &&
        dmCompareAddr16(buf, 0, fmt->addr) &&
        dmCompareAddr16(buf, 2 + 1, fmt->addr + buf->len - 3) &&
        dmCompareAddr16(buf, 2 + 3, 0x7f3f))
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


static int fmtDecodeBlackMailFLIPacked(DMC64Image *img, const DMGrowBuf *psrc, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf dst, src;
    DMCompParams cfg;

    cfg.func         = fmt->name;
    cfg.type         = DM_COMP_RLE_MARKER;
    cfg.flags        = DM_RLE_BYTE_RUNS | DM_RLE_ORDER_1 | DM_RLE_ZERO_COUNT_MAX |
                       DM_RLE_BACKWARDS_INPUT | DM_RLE_BACKWARDS_OUTPUT | DM_OUT_CROP_END;
    cfg.rleMarkerB   = psrc->data[0];
    cfg.cropOutLen   = 0x4442 - 2; // Crop to unpacked size - load address

    // Skip the RLE marker byte, packed data end address and unpacked data end address
    dmGrowBufConstCopyOffs(&src, psrc, 1 + 2 + 2);

    if ((res = dmDecodeGenericRLEAlloc(&dst, &src, &cfg)) != DMERR_OK)
        goto out;

    res = dmC64DecodeGenericBMP(img, &dst, fmt);

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


static int fmtEncodeBlackMailFLIPacked(DMGrowBuf *buf, const DMC64Image *img, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf tmp1, tmp2;
    DMCompParams cfg;

    dmGrowBufInit(&tmp1);
    dmGrowBufInit(&tmp2);

    // Encode the data to temp buffer
    if ((res = dmC64EncodeGenericBMP(TRUE, &tmp1, img, fmt)) != DMERR_OK)
        goto out;

    // And now RLE compress the data to the existing buffer
    cfg.func         = fmt->name;
    cfg.type         = DM_COMP_RLE_MARKER;
    cfg.flags        = DM_RLE_BYTE_RUNS | DM_RLE_ORDER_1 | DM_RLE_ZERO_COUNT_MAX |
                       DM_RLE_BACKWARDS_INPUT | DM_RLE_BACKWARDS_OUTPUT;
    cfg.rleMinCountB = 4;
    cfg.rleMaxCountB = 256; // this format allows 256 byte runs with ZERO_COUNT_MAX

    dmGenericRLEAnalyze(&tmp1, &cfg);

    if ((res = dmEncodeGenericRLEAlloc(&tmp2, &tmp1, &cfg)) != DMERR_OK)
        goto out;

    // Now, finally we must put in the header etc.
    if (!dmGrowBufPutU8(buf, cfg.rleMarkerB) ||
        !dmGrowBufPutU16LE(buf, fmt->addr + tmp2.len + 4) ||
        !dmGrowBufPutU16LE(buf, 0x7f3f) ||
        !dmGrowBufPut(buf, tmp2.data, tmp2.len))
    {
        res = DMERR_MALLOC;
        goto out;
    }

out:
    dmGrowBufFree(&tmp1);
    dmGrowBufFree(&tmp2);
    return res;
}


static int fmtGetPixelBlackMailFLI(Uint8 *col,
    const DMC64Image *img, const int rasterX, const int rasterY)
{
    DM_C64_GENERIC_MC_PIXEL_DEFS(img)

    Uint8 bgcol = (unsigned) rasterY < img->extraData[0].size ?
        img->extraData[0].data[rasterY] : img->bgcolor;

    return dmC64GetGenericMCPixel(col, img,
        bmoffs, scroffs,
        vshift, rasterY & 7,
        0, 0, bgcol & 15);
}


static int fmtTruePaintGetLaceType(const DMC64EncDecOp *op, DMC64Image *img,
    const DMGrowBuf *buf, const DMC64ImageCommonFormat *fmt)
{
    (void) op;
    (void) buf;
    (void) fmt;
    img->laceType = D64_ILACE_RES;
    return DMERR_OK;
}


static int fmtGetPixelTruePaint(Uint8 *col,
    const DMC64Image *img, const int rasterX, const int rasterY)
{
    const int
        x = rasterX / 8,
        y = rasterY / 8,
        yoffs = y * img->fmt->chWidth,
        bmoffs = yoffs * 8 + (rasterY & 7) + (x * 8),
        scroffs = yoffs + x,
        vshift = 6 - (rasterX & 6);

    return dmC64GetGenericMCPixel(col, img,
        bmoffs, scroffs,
        vshift, 0,
        rasterX & 1, 0, img->bgcolor);
}


static int fmtProbeTruePaintPacked(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    // The beginning/un-changing part of the BASIC bootstrap and
    // relocation of decompression code
    static const Uint8 magicID[] = {
        0x0b, 0x08, 0x09, 0x00, 0x9e, 0x32, 0x30, 0x35,
        0x39, 0x00, 0xa2, 0x00, 0x78, 0xbd, 0x1c, 0x08,
        0x9d, 0xf5, 0x00, 0xe8, 0xd0, 0xf7, 0xe6, 0x01,
        0x4c, 0x01, 0x01, 0xa5, 0xfe, 0xd0, 0x02, 0xc6,
        0xff, 0xc6, 0xfe
    };

    if (buf->len >= 320 &&
        dmCompareAddr16(buf, 0, fmt->addr) &&
        DM_MEMCMP_SIZE(buf->data + 2, magicID) == 0)
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


//
// Based on disassembly of the depacker routine. Encoding seems to be
// some kind of "improved RLE" variant with different modes and a
// simplistic "codebook".
//
static int fmtTruePaintGetByte(DMGrowBuf *src, Uint8 *data, const int mode)
{
    if (!dmGrowBufGetU8(src, data))
    {
        return dmError(DMERR_INVALID_DATA,
            "TruePaintRLE: Out of input data (N=%d)\n", mode);
    }
    else
        return DMERR_OK;
}


static int fmtDecodeTruePaintPacked(DMC64Image *img, const DMGrowBuf *psrc, const DMC64ImageFormat *fmt)
{
    int res = DMERR_OK;
    const Uint8 *codeBook1, *codeBook2;
    DMGrowBuf dst, src;
    DMCompParams cfg;
    Uint8 data;

    // 1b7e-67e8 decoded by original depacker
    // 1c00-67e8 is the actual area used tho
    cfg.func         = fmt->name;
    cfg.type         = DM_COMP_RLE_MARKER;
    cfg.flags        = DM_RLE_BACKWARDS_OUTPUT | DM_RLE_BACKWARDS_INPUT | DM_OUT_CROP_END;
    cfg.rleMarkerB   = 0xfe;
    cfg.cropOutLen   = 0x67e8 - 0x1c00;

    // Codebooks: #1 is trampoline table markers, #2 is RLE data table
    codeBook1 = psrc->data + 0x81 - 2;
    codeBook2 = psrc->data + 0x85 - 2;

    // Allocate output buffer
    if ((res = dmGrowBufAlloc(&dst, 64*1024, 4*1024)) != DMERR_OK)
        goto out;

    // As we need to modify the offs, etc. but not the data,
    // we will just make a shallow copy of the DMGrowBuf struct
    dmGrowBufConstCopy(&src, psrc);
    dmSetupRLEBuffers(&dst, &src, &cfg);

    while ((res = fmtTruePaintGetByte(&src, &data, -1)) == DMERR_OK)
    {
        unsigned int count = 1;
        BOOL found = FALSE;

        for (int n = 0; n < 8; n++)
        if (codeBook1[n] == data && !found)
        {
            found = TRUE;
            switch (n)
            {
                case 4: // Y = 4, JTO = $0B
                    if ((res = fmtTruePaintGetByte(&src, &data, n)) != DMERR_OK)
                        goto out;

                    count = data;
                    if (data == 0)
                        goto finish;

                    // fallthrough

                case 1: // Y = 1, JTO = $17
                    count += 2;
                    // fallthrough

                case 0: // Y = 0, JTO = $19
                    if ((res = fmtTruePaintGetByte(&src, &data, n)) != DMERR_OK)
                        goto out;
                    break;

                case 2: // Y = 2, JTO = $07
                    if ((res = fmtTruePaintGetByte(&src, &data, n)) != DMERR_OK)
                        goto out;

                    count = data;
                    // fallthrough

                case 3: // Y = 3, JTO = $0B
                    count += 2;
                    data   = 0;
                    break;

                default: // Y = [5..8], JTO = $00
                    count++;
                    data = codeBook2[n];
                    break;
            }
        }

        if ((res = dmGenericRLEOutputRun(&dst, &cfg, data, count)) != DMERR_OK)
            goto out;
    }

finish:
    dmFinishRLEBuffers(&dst, &src, &cfg);
    res = dmC64DecodeGenericBMP(img, &dst, fmt);

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


#define XX2_MIN_SIZE   4000
#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)


static int fmtProbeFormatXX2(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    if (buf->len >= XX2_MIN_SIZE &&
        buf->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 DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf tmp;

    // 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.
    if (dmGrowBufCopy(&tmp, buf, buf->len < XX2_MIN_SIZE ? XX2_MIN_SIZE - buf->len : 0) == NULL)
        return DMERR_MALLOC;

    tmp.len = tmp.size;
    res = dmC64DecodeGenericBMP(img, &tmp, fmt);
    dmGrowBufFree(&tmp);
    return res;
}


static int fmtProbeCosmosDesignsHiresManager(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    if (buf->len > 32 && dmCompareAddr16(buf, 0, fmt->addr))
    {
        // Packed variant
        if (fmt->size == 0 &&
            dmCompareAddr16(buf, 2, fmt->addr + buf->len - 3) &&
            dmCompareAddr16(buf, 4, 0x7ff2))
            return DM_PROBE_SCORE_MAX;

        // Unpacked variant
        if (fmt->size != 0 && fmt->size == buf->len)
        {
            // In the unpacked format the first 0x40 bytes should be 0xff
            for (size_t offs = 2; offs < 0x42; offs++)
                if (buf->data[offs] != 0xff)
                    return DM_PROBE_SCORE_GOOD;

            return DM_PROBE_SCORE_MAX;
        }
    }

    return DM_PROBE_SCORE_FALSE;
}


static int fmtDecodeCosmosDesignsHiresManagerPacked(DMC64Image *img, const DMGrowBuf *psrc, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf tmp;
    Uint8 data, *dstBuf;
    const size_t baseAddr = 0x4000;
    const size_t dstSize = 0x8000 - baseAddr;
    size_t dstOffs, srcOffs, ncount;

    // Allocate output buffer
    if ((dstBuf = dmMalloc0(dstSize)) == NULL)
    {
        return dmError(DMERR_MALLOC,
            "Could not allocate memory for RLE decoding buffer.\n");
    }

    // Setup input and output offsets
    srcOffs = psrc->len - 1;
    dstOffs = 0x7ff2 - baseAddr - 1;

    while (dstOffs > 0 && srcOffs > 0)
    {
        // Get one byte of data
        data = psrc->data[srcOffs];

        // Current data byte tells us the mode
        if (data == 0)
        {
            // RLE run
            if (srcOffs < 3)
            {
                res = dmError(DMERR_INVALID_DATA,
                    "RLE: Invalid data/out of data for run sequence.\n");
                goto out;
            }

            ncount = psrc->data[--srcOffs];
            data = psrc->data[--srcOffs];

            if (dstOffs < ncount)
                goto finish;

            dstOffs -= ncount;

            for (size_t n = 0; n < ncount + 1; n++)
                dstBuf[dstOffs + n] = data;

            srcOffs--;
        }
        else
        {
            // Literal run of data bytes
            ncount = data;
            if (srcOffs < ncount)
                ncount = srcOffs;

            if (dstOffs < ncount)
                ncount = dstOffs;

            srcOffs -= ncount;
            dstOffs -= ncount - 1;

            for (size_t n = 0; n < ncount; n++)
                dstBuf[dstOffs + n] = psrc->data[srcOffs + n];
        }
    }

finish:

    // Fixups that the original decoder does, not necessary really
    dstBuf[0x7ff0 - baseAddr] = 0x03;
    dstBuf[0x7ffe - baseAddr] = dstBuf[4];

    for (size_t n = 0; n < 0x40; n++)
        dstBuf[n] = 0xff;

    for (size_t n = 0; n < 0x100; n++)
        dstBuf[0x40 + n] = 0x00;

    res = dmC64DecodeGenericBMP(img, dmGrowBufConstCreateFrom(&tmp, dstBuf, dstSize), fmt);

out:
    dmFree(dstBuf);
    return res;
}


static const char *fmtFunPaint2_MagicID = "FUNPAINT (MT) ";
#define fmtFunPaint2_Header_Size (0x10)


static int fmtProbeFunPaint2(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    if (buf->len > 30 &&
        DM_MEMCMP_LEN(buf->data + 2, fmtFunPaint2_MagicID) == 0)
    {
        // Unpacked variant
        if (fmt->size != 0 && buf->data[14 + 2] == 0)
            return DM_PROBE_SCORE_MAX;

        // Packed variant
        if (fmt->size == 0 && buf->data[14 + 2] != 0)
            return DM_PROBE_SCORE_MAX;
    }

    return DM_PROBE_SCORE_FALSE;
}


static int fmtDecodeFunPaint2(DMC64Image *img, const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf tmp;

    // Check if the data is compressed
    if (buf->data[14])
    {
        DMGrowBuf mem;
        DMCompParams cfg;

        cfg.func         = fmt->name;
        cfg.type         = DM_COMP_RLE_MARKER;
        cfg.flags        = DM_RLE_BYTE_RUNS | DM_RLE_ORDER_1;
        cfg.rleMarkerB   = buf->data[15];

        dmGrowBufCopyOffs(&tmp, buf, fmtFunPaint2_Header_Size, 1);
        tmp.len = tmp.size;

        if ((res = dmDecodeGenericRLEAlloc(
            &mem, &tmp, &cfg)) == DMERR_OK)
            res = dmC64DecodeGenericBMP(img, &mem, fmt);

        dmGrowBufFree(&mem);
        dmGrowBufFree(&tmp);
    }
    else
    {
        res = dmC64DecodeGenericBMP(img, dmGrowBufConstCopyOffs(&tmp, buf, fmtFunPaint2_Header_Size), fmt);
    }

    return res;
}


static int fmtEncodeFunPaint2Unpacked(DMGrowBuf *buf, const DMC64Image *img, const DMC64ImageFormat *fmt)
{
    // Add the header bits
    if (!dmGrowBufPut(buf, (Uint8 *) fmtFunPaint2_MagicID, strlen(fmtFunPaint2_MagicID)) ||
        !dmGrowBufPutU8(buf, 0) || // 0 == unpacked variant
        !dmGrowBufPutU8(buf, 0))   // RLE marker byte (not used in unpacked)
        return DMERR_MALLOC;

    return dmC64EncodeGenericBMP(FALSE, buf, img, fmt);
}


static int fmtEncodeFunPaint2Packed(DMGrowBuf *buf, const DMC64Image *img, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf mem;
    DMCompParams cfg;

    // Encode the data to temp buffer
    if ((res = dmC64EncodeGenericBMP(TRUE, &mem, img, fmt)) != DMERR_OK)
        goto out;

    // Analyze and setup RLE
    cfg.func         = fmt->name;
    cfg.type         = DM_COMP_RLE_MARKER;
    cfg.flags        = DM_RLE_BYTE_RUNS | DM_RLE_ORDER_1;
    cfg.rleMinCountB = 4;
    cfg.rleMaxCountB = 255;

    dmGenericRLEAnalyze(&mem, &cfg);

    // Add the header bits
    if (!dmGrowBufPut(buf, (Uint8 *) fmtFunPaint2_MagicID, strlen(fmtFunPaint2_MagicID)) ||
        !dmGrowBufPutU8(buf, 1) || // non-zero == packed variant
        !dmGrowBufPutU8(buf, cfg.rleMarkerB))
    {
        res = DMERR_MALLOC;
        goto out;
    }

    // And now RLE compress the data to the existing buffer
    res = dmEncodeGenericRLE(buf, &mem, &cfg);

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


static int fmtGetPixelFunPaint2(Uint8 *col,
    const DMC64Image *img, const int rasterX, const int rasterY)
{
    const int
        x = rasterX / 8,
        y = rasterY / 8,
        yb = rasterY & 7,
        yoffs = y * img->fmt->chWidth,
        bmoffs = yoffs * 8 + yb + (x * 8),
        scroffs = yoffs + x,
        bitmap = rasterX & 1,
        vbank = yb + (bitmap * 8),
        vshift = 6 - (rasterX & 6);

    Uint8 bgcol = (unsigned) rasterY < img->extraData[0].size ?
        img->extraData[0].data[rasterY] : img->bgcolor;

    return dmC64GetGenericMCPixel(col, img,
        bmoffs, scroffs,
        vshift, vbank,
        bitmap, 0, bgcol & 15);
}


static int fmtGetPixelBFLI(Uint8 *col,
    const DMC64Image *img, const int rasterX, const int rasterY)
{
    DM_C64_GENERIC_MC_PIXEL_DEFS(img)
    const int vbb = rasterY < 200 ? 0 : 1;
    const int vbank = (rasterY & 7) + (vbb * 8);

    return dmC64GetGenericMCPixel(col, img,
        bmoffs & 0x1fff, scroffs & 0x3ff,
        vshift, vbank,
        vbb, 0, img->bgcolor);
}


static int fmtGetPixelPentelPaint(Uint8 *col,
    const DMC64Image *img, const int rasterX, const int rasterY)
{
    DM_C64_GENERIC_SC_PIXEL_DEFS(img)
    const int spr_y = rasterY / D64_SPR_HEIGHT_PX;
    const int spr_yd = rasterY % D64_SPR_HEIGHT_PX;
    const int spr_x = rasterX / D64_SPR_WIDTH_PX;
    const int spr_xd = (rasterX % D64_SPR_WIDTH_PX) / 8;
    const int offs = (spr_y * 8 + spr_x) * D64_SPR_SIZE + (spr_yd * D64_SPR_WIDTH_UT) + spr_xd;
    const int mask = 1 << (7 - (rasterX & 7));
    int res;

    Uint8 color1,
        color2 = img->extraData[0].data[offs] & mask ? 0x0f : 0,
        color3 = img->extraData[0].data[offs + D64_SPR_SIZE * 155] & mask ? img->d022 : 0;

    if ((res = dmC64GetGenericSCPixel(&color1, img, bmoffs, scroffs, vshift, 0, 0)) != DMERR_OK)
        return res;

    *col = color3 ? color3 : ( color2 ? color2 : color1 );
    return DMERR_OK;
}


// Horizontal character X-offset and scanline Y-offset
#define DM_CREST_SHFLI_IMG_XOFFS  14
#define DM_CREST_SHFLI_IMG_YOFFS  1

// True image width and height in character blocks
#define DM_CREST_SHFLI_IMG_WIDTH  (4 * D64_SPR_WIDTH_UT)
#define DM_CREST_SHFLI_IMG_HEIGHT 21

#define DM_CREST_SHFLI_BANKS      8
#define DM_CREST_SHFLI_PTRS       8

static const Uint8 fmtCrestSHFLI_Sprite_pointers[DM_CREST_SHFLI_BANKS][DM_CREST_SHFLI_PTRS] =
{
    { 0x80, 0x84, 0x85, 0x89, 0x8A, 0x8E, 0x8F, 0x93 },
    { 0x94, 0x98, 0x99, 0x9D, 0x9E, 0xA2, 0xA3, 0xA7 },
    { 0xA8, 0xAC, 0xAD, 0xB1, 0xB2, 0xB6, 0xB7, 0xBB },
    { 0xBC, 0xC0, 0xC1, 0xC5, 0xC6, 0xCA, 0xCB, 0xCF },
    { 0xD0, 0xD4, 0xD5, 0xD9, 0xDA, 0xDE, 0xDF, 0xE3 },
    { 0xE4, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE },
    { 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6 },
    { 0xF7, 0x1E, 0x2E, 0x3E, 0x4E, 0x5E, 0x6E, 0x7E },
};


static const Uint8 fmtCrestSHFLI_MagicID_Packed[] =
{
    0x83, 0x92, 0x85, 0x93, 0x94,
};


static int fmtProbeCrestSHFLI(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    // Unpacked variant
    if (buf->len == fmt->size &&
        dmCompareAddr16(buf, 0, fmt->addr))
    {
        int score = DM_PROBE_SCORE_MAYBE;
        for (int nbank = 0; nbank < DM_CREST_SHFLI_BANKS; nbank++)
        {
            if (memcmp(buf->data + 2 + (nbank * 0x0400) + 0x03f8,
                fmtCrestSHFLI_Sprite_pointers[nbank], DM_CREST_SHFLI_PTRS) == 0)
                score += DM_PROBE_SCORE_GOOD;
        }
        return score;
    }

    // Packed variant
    if (dmCompareAddr16(buf, 0, fmt->addr) &&
        buf->len > sizeof(fmtCrestSHFLI_MagicID_Packed) &&
        DM_MEMCMP_SIZE(buf->data + buf->len - sizeof(fmtCrestSHFLI_MagicID_Packed), fmtCrestSHFLI_MagicID_Packed) == 0)
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


static int fmtDecodeCrestSHFLIPacked(DMC64Image *img, const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf tmp, mem;
    DMCompParams cfg;

    // Compression is typical RLE, with first byte being the RLE marker byte etc.
    // However, as a difference to the uncompressed files, only the data of the
    // 96 pixels wide area (4 sprite widths) x 168 tall is saved.
    cfg.func         = fmt->name;
    cfg.type         = DM_COMP_RLE_MARKER;
    cfg.flags        = DM_RLE_BYTE_RUNS | DM_RLE_ORDER_1 | DM_RLE_ZERO_COUNT_MAX;
    cfg.rleMarkerB   = buf->data[0];

    if ((res = dmDecodeGenericRLEAlloc(
        &mem, dmGrowBufConstCopyOffs(&tmp, buf, 1), &cfg)) == DMERR_OK)
    {
        const size_t fmtUncompSize = 0x1ff0;
        const size_t dstSize = 16 * 1024;
        Uint8 *dstBuf, *sptr, *dptr;

        // Check uncompressed size?
        if (mem.len != fmtUncompSize)
        {
            res = dmError(DMERR_INVALID_DATA,
                "%s: Unexpected uncompressed data size %d bytes (should be %d).\n",
                cfg.func, mem.len, fmtUncompSize);
            goto out;
        }

        // Allocate output buffer
        if ((dstBuf = dmMalloc0(dstSize)) == NULL)
        {
            res = dmError(DMERR_MALLOC,
                "%s: Could not allocate temporary memory buffer of %d bytes.\n",
                cfg.func, dstSize);
            goto out;
        }

        // Now that we have the uncompressed data (0x1ff0 bytes), we need to
        // re-arrange it. The data is as follows ..
        //
        // 0x0000 - sprite data for 64 sprites
        // 0x1000 - bitmap (12 * 21 bytes)
        // 0x1800 - screen RAMs (12 * 21 bytes) x 8 banks
        // 0x1fe8 - sprite color #1
        // 0x1fe9 - sprite color #2
        //

        // Copy sprite colors
        sptr = mem.data + 0x1fe8;
        dptr = dstBuf   + 0x03e8;
        dptr[0] = sptr[0];
        dptr[1] = sptr[1];

        // First, clear and set some defaults that are not saved in the file
        for (int nbank = 0; nbank < DM_CREST_SHFLI_BANKS; nbank++)
        {
            dptr = dstBuf + nbank * 0x0400;

            // Set preset screen RAM for other area
            memset(dptr, 0xff, fmt->format->chWidth * fmt->format->chHeight);

            // Copy sprite data points
            memcpy(dptr + 0x03f8, &fmtCrestSHFLI_Sprite_pointers[nbank], DM_CREST_SHFLI_PTRS);
        }

        // Now we copy and transform the bitmap and screen RAM data.
        for (int yc = 0; yc < DM_CREST_SHFLI_IMG_HEIGHT * 8; yc++)
        {
            const int syy = yc / 8;
            const int syd = yc & 7;

            // In the image the first visible scanline is unused, but in
            // the compressed version data starts right away, so we offset
            // the destination Y coordinate by one.
            const int dyc = yc + DM_CREST_SHFLI_IMG_YOFFS;
            const int dyy = dyc / 8;
            const int dyd = dyc & 7;

            // Format of the bitmap data is one horizontal pixel row (12 bytes)
            // times 21*8 rows, e.g. the data is "linear" rows of bytes and not
            // arranged in usual c64 bitmap "char" order. Thus we reorder it.
            sptr = mem.data + 0x1000 + DM_CREST_SHFLI_IMG_WIDTH * (syd + 8 * syy);
            dptr = dstBuf   + 0x2000 + DM_CREST_SHFLI_IMG_XOFFS * 8 + (fmt->format->chWidth * dyy * 8) + dyd;

            for (int xc = 0; xc < DM_CREST_SHFLI_IMG_WIDTH; xc++)
            {
                dptr[xc * 8] = sptr[xc];
                sptr[xc] = 0xaa;
            }

            // A bit similar arrangement is used for the screen RAM data.
            // Each row of 12 bytes of data is for a bank. Next row is for
            // next bank, etc.
            sptr = mem.data + 0x1800 + DM_CREST_SHFLI_IMG_WIDTH * syd + DM_CREST_SHFLI_IMG_WIDTH * 8 * syy;
            dptr = dstBuf + DM_CREST_SHFLI_IMG_XOFFS + 0x0400 * dyd + fmt->format->chWidth * dyy;

            for (int xc = 0; xc < DM_CREST_SHFLI_IMG_WIDTH; xc++)
            {
                dptr[xc] = sptr[xc];
                sptr[xc] = 0xaa;
            }
        }

        // The sprite data is also transformed similarly, data is
        // in same scanline format as the bitmap. Thus we need to
        // place it where it belongs based on the sprite pointers.
        for (int yc = 0; yc < DM_CREST_SHFLI_IMG_HEIGHT * 8; yc++)
        {
            const int yd = yc % D64_SPR_HEIGHT_PX;
            const Uint8 *sprPtrs = fmtCrestSHFLI_Sprite_pointers[yc % 8];
            Uint8 *sp1, *sp2, *dp;

            dptr = dstBuf + D64_SPR_WIDTH_UT * yd;
            sp1 = mem.data + DM_CREST_SHFLI_IMG_WIDTH * yc;
            sp2 = sp1 + 0x0800;

            for (int xc = 0; xc < DM_CREST_SHFLI_IMG_WIDTH / D64_SPR_WIDTH_UT; xc++)
            {
                dp = dptr + D64_SPR_SIZE * sprPtrs[xc];
                for (int xd = 0; xd < D64_SPR_WIDTH_UT; xd++)
                    dp[xd] = *sp1++;

                dp = dptr + D64_SPR_SIZE * sprPtrs[xc + 4];
                for (int xd = 0; xd < D64_SPR_WIDTH_UT; xd++)
                    dp[xd] = *sp2++;
            }
        }

        res = dmC64DecodeGenericBMP(img, dmGrowBufConstCreateFrom(&tmp, dstBuf, dstSize), fmt);
        dmFree(dstBuf);
    }

out:
    dmGrowBufFree(&mem);

    return res;
}


static int fmtGetSpritePixelCrestSHFLI(Uint8 *col,
    const DMC64Image *img, const int sindex, const int cindex,
    const int spr_xd, const int spr_yd, const int mask)
{
    const size_t offs = sindex * D64_SPR_SIZE + (D64_SPR_WIDTH_UT * spr_yd) + spr_xd;

    if (offs >= img->extraData[14].size)
        return DMERR_BOUNDS;

    if (img->extraData[14].data[offs] & mask)
    {
        *col = img->extraData[15].data[cindex];
        return DMERR_OK;
    }

    return -1;
}


static int fmtGetPixelCrestSHFLI(Uint8 *col,
    const DMC64Image *img, const int rasterX, const int rasterY)
{
    DM_C64_GENERIC_SC_PIXEL_DEFS(img)
    const int sprOffsetX = DM_CREST_SHFLI_IMG_XOFFS * 8,
              sprOffsetY = DM_CREST_SHFLI_IMG_YOFFS;
    const int nbank = rasterY & 7;
    int res;

    if (rasterY / 8 >= DM_CREST_SHFLI_IMG_HEIGHT)
    {
        *col = 0x0f;
        return DMERR_OK;
    }

    if (rasterY >= sprOffsetY &&
        rasterX >= sprOffsetX &&
        rasterX < sprOffsetX + 4 * D64_SPR_WIDTH_PX)
    {
        const int localX = rasterX - sprOffsetX,
                  localY = rasterY - sprOffsetY;
        const int sbank = localY & 7;
        const int spr_yd = localY % D64_SPR_HEIGHT_PX;
        const int spr_x = localX / D64_SPR_WIDTH_PX;
        const int spr_xd = (localX % D64_SPR_WIDTH_PX) / 8;
        const int mask = 1 << (7 - (localX & 7));

        const int spr_offs = spr_x & 3;
        const int spr_index1 = img->extraData[sbank].data[spr_offs];
        const int spr_index2 = img->extraData[sbank].data[spr_offs + 4];

        if ((res = fmtGetSpritePixelCrestSHFLI(col, img, spr_index1, 0, spr_xd, spr_yd, mask)) == DMERR_OK ||
            res != -1)
            return res;

        if ((res = fmtGetSpritePixelCrestSHFLI(col, img, spr_index2, 1, spr_xd, spr_yd, mask)) == DMERR_OK ||
            res != -1)
            return res;
    }

    if ((res = dmC64GetGenericSCPixel(col, img, bmoffs, scroffs, vshift, nbank, 0)) != DMERR_OK)
        return res;

    return DMERR_OK;
}


static int fmtGetPixelHCB(Uint8 *col,
    const DMC64Image *img, const int rasterX, const int rasterY)
{
    DM_C64_GENERIC_MC_PIXEL_DEFS(img)
    const int vbank = (rasterY / 4) & 1;
    const int ry = rasterY / 5;

    Uint8 bgcol = (unsigned) ry < img->extraData[0].size ?
        img->extraData[0].data[ry] : img->bgcolor;

    return dmC64GetGenericMCPixel(col, img,
        bmoffs, scroffs,
        vshift, vbank,
        0, vbank, bgcol & 15);
}


static int fmtGetPixelCrestHIFLIorCDHM(Uint8 *col,
    const DMC64Image *img, const int rasterX, const int rasterY)
{
    DM_C64_GENERIC_SC_PIXEL_DEFS(img)

    return dmC64GetGenericSCPixel(col, img,
        bmoffs, scroffs,
        vshift, rasterY & 7, 0);
}


static int fmtECIGetLaceType(const DMC64EncDecOp *op, DMC64Image *img,
    const DMGrowBuf *buf, const DMC64ImageCommonFormat *fmt)
{
    (void) op;
    (void) buf;
    (void) fmt;
    img->laceType = D64_ILACE_COLOR;
    return DMERR_OK;
}


static int fmtGetPixelECI(Uint8 *col,
    const DMC64Image *img, const int rasterX, const int rasterY)
{
    DM_C64_GENERIC_SC_PIXEL_DEFS(img)
    const int vbank = rasterY & 7;
    Uint8 color1, color2;
    int res;

    if ((res = dmC64GetGenericSCPixel(&color1, img, bmoffs, scroffs, vshift, vbank    , 0)) != DMERR_OK ||
        (res = dmC64GetGenericSCPixel(&color2, img, bmoffs, scroffs, vshift, vbank + 8, 1)) != DMERR_OK)
        return res;

    *col = (color1 * D64_NCOLORS) + color2;
    return DMERR_OK;
}


static int fmtConvertECIBMP2Image(DMImage *dst, const DMC64Image *src,
    const DMC64ImageFormat *fmt, const DMC64ImageConvSpec *spec)
{
    int res;

    if ((res = dmC64SetImagePalette(dst, spec, TRUE)) != DMERR_OK)
        return res;

    return dmC64ConvertGenericBMP2Image(dst, src, fmt, spec);
}


static int fmtProbeECIPacked(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    int score = DM_PROBE_SCORE_FALSE;

    if (buf->len > 128 &&
        dmCompareAddr16(buf, 0, fmt->addr))
    {
        size_t i, n;

        // Count statistics about used byte values and compare to
        // value in buf[2] which is the RLE marker
        for (n = 0, i = 3; i < buf->len; i++)
            if (buf->data[i] == buf->data[2]) n++;

        if (n > 50)
            score = DM_PROBE_SCORE_GOOD;
        else
        if (n > 25)
            score = DM_PROBE_SCORE_AVG;
        else
        if (n > 10)
            score = DM_PROBE_SCORE_MAYBE;
    }

    if (// Try to avoid misprobe of Crest Hires FLI Designer and Cosmos Design format
        buf->len == 16386 ||
        // Face Painter
        buf->len == 10004)
        score /= 3;

    return score;
}


static int fmtDecodeECIPacked(DMC64Image *img, const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf mem, tmp;
    DMCompParams cfg;

    cfg.func         = fmt->name;
    cfg.type         = DM_COMP_RLE_MARKER;
    cfg.flags        = DM_RLE_BYTE_RUNS | DM_RLE_ORDER_1;
    cfg.rleMarkerB   = buf->data[0];

    if ((res = dmDecodeGenericRLEAlloc(
        &mem, dmGrowBufConstCopyOffs(&tmp, buf, 1), &cfg)) == DMERR_OK)
        res = dmC64DecodeGenericBMP(img, &mem, fmt);

    dmGrowBufFree(&mem);
    return res;
}


static int fmtEncodeECIPacked(DMGrowBuf *buf, const DMC64Image *img, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf tmp;
    DMCompParams cfg;

    // Encode the data to temp buffer
    if ((res = dmC64EncodeGenericBMP(TRUE, &tmp, img, fmt)) != DMERR_OK)
        goto out;

    // Analyze and setup RLE
    cfg.func         = fmt->name;
    cfg.type         = DM_COMP_RLE_MARKER;
    cfg.flags        = DM_RLE_BYTE_RUNS | DM_RLE_ORDER_1;
    cfg.rleMinCountB = 4;
    cfg.rleMaxCountB = 255;

    dmGenericRLEAnalyze(&tmp, &cfg);

    // Add the header bits
    if (!dmGrowBufPutU8(buf, cfg.rleMarkerB))
    {
        res = DMERR_MALLOC;
        goto out;
    }

    // And now RLE compress the data to the existing buffer
    res = dmEncodeGenericRLE(buf, &tmp, &cfg);

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


//
// Helper macros for defining screen memory layouts
// common for several FLI type image formats
//
#define DEF_REPEAT_BLOCK(dtype, start, oindex, bindex, osize, bsize, oflags) \
    { DO_COPY, (dtype), (start) + ((osize) * (oindex)), (bindex), (bsize), 0, NULL, NULL, (oflags) }

#define DEF_REPEAT_BLOCK_8(dtype, start, sindex, osize, bsize, oflags) \
    DEF_REPEAT_BLOCK((dtype), (start), 0, ((sindex) + 0), (osize), (bsize), (oflags)), \
    DEF_REPEAT_BLOCK((dtype), (start), 1, ((sindex) + 1), (osize), (bsize), (oflags)), \
    DEF_REPEAT_BLOCK((dtype), (start), 2, ((sindex) + 2), (osize), (bsize), (oflags)), \
    DEF_REPEAT_BLOCK((dtype), (start), 3, ((sindex) + 3), (osize), (bsize), (oflags)), \
    DEF_REPEAT_BLOCK((dtype), (start), 4, ((sindex) + 4), (osize), (bsize), (oflags)), \
    DEF_REPEAT_BLOCK((dtype), (start), 5, ((sindex) + 5), (osize), (bsize), (oflags)), \
    DEF_REPEAT_BLOCK((dtype), (start), 6, ((sindex) + 6), (osize), (bsize), (oflags)), \
    DEF_REPEAT_BLOCK((dtype), (start), 7, ((sindex) + 7), (osize), (bsize), (oflags))


//
// Many formats actually share memory layout and other specs, and there are
// packed and unpacked versions of several formats. We'll reuse these here
// through this common formats data array, referred from dmC64ImageFormats[]
//
DMC64ImageCommonFormat dmC64CommonFormats[] =
{
    { // #0: Koala Painter type memory layout
        D64_FMT_MC,
        D64_SCR_WIDTH / 2, D64_SCR_HEIGHT,
        D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
        2, 1,
        NULL, NULL,
        NULL,
        {
            { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_COPY       , DS_SCREEN_RAM  , 0x1f40, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_COPY       , DS_COLOR_RAM   , 0x2328, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_SET_MEM_LO , DS_BGCOL       , 0x2710, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
        }
    },

    { // #1: Black Mail FLI Graph
        D64_FMT_MC | D64_FMT_FLI,
        D64_SCR_WIDTH / 2, D64_SCR_HEIGHT,
        D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
        2, 1,
        NULL, NULL,
        fmtGetPixelBlackMailFLI,
        {
            { DO_COPY       , DS_EXTRA_DATA  , 0x0000, 0,  200,   0, NULL, NULL, DF_NORMAL },
            { DO_COPY       , DS_COLOR_RAM   , 0x0100, 0,  0,   0, NULL, NULL, DF_NORMAL },
            DEF_REPEAT_BLOCK_8(DS_SCREEN_RAM, 0x0500, 0, 0x400, 0, DF_NORMAL),
            { DO_COPY       , DS_BITMAP_RAM  , 0x2500, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
        }
    },

    { // #2: Art Studio etc. Hires
        D64_FMT_HIRES,
        D64_SCR_WIDTH   , D64_SCR_HEIGHT,
        D64_SCR_CH_WIDTH, D64_SCR_CH_HEIGHT,
        1, 1,
        NULL, NULL,
        NULL,
        {
            { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_COPY       , DS_SCREEN_RAM  , 0x1f40, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
        }
    },

    { // #3: FunPaint II
        D64_FMT_MC | D64_FMT_FLI | D64_FMT_ILACE,
        D64_SCR_WIDTH, D64_SCR_HEIGHT,
        D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
        1, 1,
        NULL, NULL,
        fmtGetPixelFunPaint2,
        {
            DEF_REPEAT_BLOCK_8(DS_SCREEN_RAM, 0x0000, 0, 0x400, 0, DF_NORMAL),
            { DO_COPY       , DS_BITMAP_RAM  , 0x2000, 0,  0  , 0, NULL, NULL, DF_NORMAL },
            { DO_COPY       , DS_EXTRA_DATA  , 0x3f48, 0,  100, 0, NULL, NULL, DF_NORMAL },
            { DO_COPY       , DS_COLOR_RAM   , 0x4000, 0,  0  , 0, NULL, NULL, DF_NORMAL },
            DEF_REPEAT_BLOCK_8(DS_SCREEN_RAM, 0x43e8, 8, 0x400, 0, DF_NORMAL),
            { DO_COPY       , DS_BITMAP_RAM  , 0x63e8, 1,  0  , 0, NULL, NULL, DF_NORMAL },
            { DO_COPY       , DS_EXTRA_DATA  , 0x8328, 0,  100, 100, NULL, NULL, DF_NORMAL },
            { DO_FUNC       , 0              , 0     , 0,  1  , 0, fmtTruePaintGetLaceType, NULL, DF_NORMAL },
            { DO_LAST       , 0              , 0     , 0,  0  , 0, NULL, NULL, DF_NORMAL },
        }
    },

    { // #4: DrazPaint 1.x & 2
        D64_FMT_MC,
        D64_SCR_WIDTH / 2, D64_SCR_HEIGHT,
        D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
        2, 1,
        NULL, NULL,
        NULL,
        {
            { DO_COPY       , DS_COLOR_RAM   , 0x0000, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_COPY       , DS_BITMAP_RAM  , 0x0800, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_COPY       , DS_SCREEN_RAM  , 0x0400, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_SET_MEM_LO , DS_BGCOL       , 0x2740, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
        }
    },

    { // #5: DrazLace 1.0
        D64_FMT_MC | D64_FMT_ILACE,
        D64_SCR_WIDTH   , D64_SCR_HEIGHT,
        D64_SCR_CH_WIDTH, D64_SCR_CH_HEIGHT,
        1, 1,
        NULL, NULL,
        fmtGetPixelDrazLace,
        {
            { DO_COPY       , DS_COLOR_RAM   , 0x0000, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_COPY       , DS_BITMAP_RAM  , 0x0800, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_COPY       , DS_SCREEN_RAM  , 0x0400, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_SET_MEM_LO , DS_BGCOL       , 0x2740, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_FUNC       , 0              , 0x2742, 0,  1,   0, fmtDrazLaceGetLaceType, fmtDrazLaceSetLaceType, DF_NORMAL },
            { DO_COPY       , DS_BITMAP_RAM  , 0x2800, 1,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
        }
    },

    { // #6: TruePaint
        D64_FMT_MC | D64_FMT_ILACE,
        D64_SCR_WIDTH   , D64_SCR_HEIGHT,
        D64_SCR_CH_WIDTH, D64_SCR_CH_HEIGHT,
        1, 1,
        NULL, NULL,
        fmtGetPixelTruePaint,
        {
            { DO_COPY       , DS_SCREEN_RAM  , 0x0000, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_SET_MEM_LO , DS_BGCOL       , 0x03e8, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_COPY       , DS_BITMAP_RAM  , 0x0400, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_COPY       , DS_BITMAP_RAM  , 0x2400, 1,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_COPY       , DS_SCREEN_RAM  , 0x4400, 1,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_COPY       , DS_COLOR_RAM   , 0x4800, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_FUNC       , 0              , 0x0000, 0,  0,   0, fmtTruePaintGetLaceType, NULL, DF_NORMAL },
            { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
        }
    },

    { // #7: ECI Graphic Editor Hires FLI
        D64_FMT_HIRES | D64_FMT_FLI,
        D64_SCR_WIDTH,    D64_SCR_HEIGHT,
        D64_SCR_CH_WIDTH, D64_SCR_CH_HEIGHT,
        1, 1,
        fmtConvertECIBMP2Image, NULL,
        fmtGetPixelECI,
        {
            { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0,  0,   0, NULL, NULL, DF_NORMAL },
            DEF_REPEAT_BLOCK_8(DS_SCREEN_RAM , 0x2000, 0, 0x400, 0, DF_NORMAL),
            { DO_COPY       , DS_BITMAP_RAM  , 0x4000, 1,  0,   0, NULL, NULL, DF_NORMAL },
            DEF_REPEAT_BLOCK_8(DS_SCREEN_RAM , 0x6000, 8, 0x400, 0, DF_NORMAL),
            { DO_FUNC       , 0              , 0     , 0,  0,   0, fmtECIGetLaceType, NULL, DF_NORMAL },
            { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
        }
    },

    { // #8: Cosmos Designs Hires Manager
        D64_FMT_HIRES | D64_FMT_FLI,
        D64_SCR_WIDTH, 24*8, // Actually 296 x 192 (=24*8)
        D64_SCR_CH_WIDTH, 24,
        1, 1,
        NULL, NULL,
        fmtGetPixelCrestHIFLIorCDHM,
        {
            { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0,  0,   0, NULL, NULL, DF_NORMAL },
            DEF_REPEAT_BLOCK_8(DS_SCREEN_RAM , 0x2000, 0,  0x400, 0, DF_NORMAL),
            { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
        }
    },

    { // #9: FBI Crew FLI Designer 1.x & 2.0
        D64_FMT_MC | D64_FMT_FLI,
        D64_SCR_WIDTH / 2, D64_SCR_HEIGHT,
        D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
        2, 1,
        NULL, NULL,
        fmtGetPixelFLIDesigner,
        {
            { DO_COPY      , DS_COLOR_RAM   , 0x0000, 0,  0,   0, NULL, NULL, DF_NORMAL },
            DEF_REPEAT_BLOCK_8(DS_SCREEN_RAM, 0x0400, 0, 0x400, 0, DF_NORMAL),
            { DO_COPY      , DS_BITMAP_RAM  , 0x2400, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_LAST      , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
        }
    },

    { // #10: Doodle
        D64_FMT_HIRES,
        D64_SCR_WIDTH   , D64_SCR_HEIGHT,
        D64_SCR_CH_WIDTH, D64_SCR_CH_HEIGHT,
        1, 1,
        NULL, NULL,
        NULL,
        {
            { DO_COPY       , DS_SCREEN_RAM  , 0x0000, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_COPY       , DS_BITMAP_RAM  , 0x0400, 0,  0,   0, NULL, NULL, DF_NORMAL },
            { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
        }
    },

    { // #11: Crest Super Hires FLI Editor 1.0 (SHF)
        D64_FMT_HIRES | D64_FMT_FLI,
        D64_SCR_WIDTH, D64_SCR_HEIGHT,
        D64_SCR_CH_WIDTH, 24,
        1, 1,
        NULL, NULL,
        fmtGetPixelCrestSHFLI,
        {
            DEF_REPEAT_BLOCK_8(DS_SCREEN_RAM,  0x0000, 0  , 0x0400,   0x0400, DF_NORMAL),
            DEF_REPEAT_BLOCK_8(DS_EXTRA_DATA,  0x03f8, 0  , 0x0400,   8, DF_DECODE), // Sprite pointers for each bank
            { DO_COPY       , DS_EXTRA_DATA  , 0x03e8, 15 , 2     ,   0, NULL, NULL, DF_DECODE },  // 2 sprite colors
            { DO_COPY       , DS_EXTRA_DATA  , 0x0000, 14 , 0x3e00,   0, NULL, NULL, DF_DECODE },  // Lazily copy whole data for sprite data
            { DO_COPY       , DS_BITMAP_RAM  , 0x2000, 0  , 0     ,   0, NULL, NULL, DF_NORMAL },
            { DO_LAST       , 0              , 0     , 0  , 0     ,   0, NULL, NULL, DF_NORMAL },
        }
    },
};


//
// Array with data for supported formats
//
DMC64ImageFormat dmC64ImageFormats[] =
{
    {
        "d2p", "DrazPaint 1.4/2.0 (packed)", 0x5800, 0, DM_FMT_RDWR,
        fmtProbeDrazPaint20Packed,
        fmtDecodeDrazPaintPacked, fmtEncodeDrazPaintPacked,
        { }, &dmC64CommonFormats[4]
    },

    {
        "drp", "DrazPaint (unpacked)", 0x5800, 10051, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        { }, &dmC64CommonFormats[4]
    },

    {
        "dlp", "DrazLace 1.0 (packed)", 0x5800, 0, DM_FMT_RDWR,
        fmtProbeDrazLace10Packed,
        fmtDecodeDrazPaintPacked, fmtEncodeDrazPaintPacked,
        { }, &dmC64CommonFormats[5]
    },

    {
        "drl", "DrazLace 1.0 (unpacked)", 0x5800, 18242, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        { }, &dmC64CommonFormats[5]
    },

    {
        "bdp5", "Boogie Down Paint 5 (packed)", 0x5000, 0, DM_FMT_RDWR,
        fmtProbeBDP5Packed,
        fmtDecodeBDP5Packed, fmtEncodeBDP5Packed,
        { }, &dmC64CommonFormats[0] // Memory format is same as Koala
    },

    {
        "vid", "Vidcom 64 (unpacked)", 0x5800, 10050, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        {
            D64_FMT_MC,
            D64_SCR_WIDTH / 2, D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_COLOR_RAM   , 0x0000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x07e8, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_BITMAP_RAM  , 0x0800, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_SCREEN_RAM  , 0x0400, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
            },
        },
        NULL
    },

    {
        "p64", "Picasso 64 (unpacked)", 0x1800, 10050, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        {
            D64_FMT_MC,
            D64_SCR_WIDTH / 2, D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_COLOR_RAM   , 0x0000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x07fe, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_BITMAP_RAM  , 0x0800, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_SCREEN_RAM  , 0x0400, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
            },
        },
        NULL
    },

    {
        "mci", "Truepaint (unpacked)", 0x9c00, 19434, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        { }, &dmC64CommonFormats[6]
    },

    {
        "mcip", "Truepaint (packed)", 0x0801, 0, DM_FMT_RD,
        fmtProbeTruePaintPacked,
        fmtDecodeTruePaintPacked, NULL,
        { }, &dmC64CommonFormats[6]
    },

    {
        "kla", "Koala Painter (unpacked)", 0x6000, 10003, DM_FMT_RDWR,
        fmtProbeKoalaPainter,
        NULL, NULL,
        { }, &dmC64CommonFormats[0]
    },

    {
        "klp", "Koala Painter (packed)", 0x6000, 0xfe, DM_FMT_RDWR, // size is abused for RLE marker byte
        fmtProbeKoalaPainterPacked,
        fmtDecodeStaticRLEMarkerMode2, fmtEncodeStaticRLEMarkerMode2,
        { }, &dmC64CommonFormats[0]
    },

    {
        "aas", "Advanced Art Studio (unpacked)", 0x2000, 10018, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        {
            D64_FMT_MC,
            D64_SCR_WIDTH / 2, D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_SCREEN_RAM  , 0x1f40, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_D020        , 0x2328, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x2329, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_COLOR_RAM   , 0x2338, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
            },
        },
        NULL
    },

    {
        "ims", "Image System MC (unpacked)", 0x3c00, 10218, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        {
            D64_FMT_MC,
            D64_SCR_WIDTH / 2, D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_COLOR_RAM   , 0x0000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_BITMAP_RAM  , 0x0400, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x23ff, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_SCREEN_RAM  , 0x2400, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "mil", "Micro Illustrator (unpacked)", 0x18dc, 10022, DM_FMT_RDWR | DM_FMT_BROKEN,
        fmtProbeMicroIllustrator,
        NULL, NULL,
        {
            D64_FMT_MC,
            D64_SCR_WIDTH / 2, D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_SCREEN_RAM  , 20 + 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_COLOR_RAM   , 20 + 1000  , 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_BITMAP_RAM  , 20 + 2000  , 0,  0,   0, NULL, NULL, DF_NORMAL },
                // XXX TODO: Unknown where the background color is set, so default to 0x00
                { DO_SET_OP     , DS_BGCOL       , 0x00       , 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_BGCOL       , 20 + 0x3e8 , 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_BGCOL       , 20 + 0x3e9 , 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_BGCOL       , 20 + 0x3ea , 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_BGCOL       , 20 + 0x3eb , 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_FUNC       , 0              , 0          , 0,  0,   0, NULL, fmtEncodeMicroIllustrator, DF_NORMAL },
                { DO_LAST       , 0              , 0          , 0,  0,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "cdu", "CDU-Paint (unpacked)", 0x7eef, 10277, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        {
            D64_FMT_MC,
            D64_SCR_WIDTH / 2, D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_BITMAP_RAM  , 0x0000 + 0x111, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_SCREEN_RAM  , 0x1f40 + 0x111, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_COLOR_RAM   , 0x2328 + 0x111, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x2710 + 0x111, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "rbp", "Rainbow Painter (unpacked)", 0x5c00, 10242, DM_FMT_RDWR | DM_FMT_BROKEN,
        NULL,
        NULL, NULL,
        {
            D64_FMT_MC,
            D64_SCR_WIDTH / 2, D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_SCREEN_RAM  , 0x0000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_BITMAP_RAM  , 0x0400, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_COLOR_RAM   , 0x2400, 0,  0,   0, NULL, NULL, DF_NORMAL },
                // XXX TODO: Not sure if the background color is hardcoded ..
                { DO_SET_OP     , DS_BGCOL       , 0x00  , 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "sar", "Saracen Paint (unpacked)", 0x7800, 10219, DM_FMT_RDWR,
        fmtProbeSaracenPaint,
        NULL, NULL,
        {
            D64_FMT_MC,
            D64_SCR_WIDTH / 2, D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_SCREEN_RAM  , 0x7800 - 0x7800, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x7bf0 - 0x7800, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_BITMAP_RAM  , 0x7c00 - 0x7800, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_COLOR_RAM   , 0x9c00 - 0x7800, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "blp", "Blazing Paddles (unpacked)", 0xA000, 10242, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        {
            D64_FMT_MC,
            D64_SCR_WIDTH / 2, D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                // Both d020 and bgcolor confirmed by tests
                { DO_SET_MEM_LO , DS_D020        , 0x1f7f, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x1f80, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_SCREEN_RAM  , 0x2000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_COLOR_RAM   , 0x2400, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "pmg", "Paint Magic crippled MC (unpacked)", 0x3f8e, 9332, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        {
            D64_FMT_MC,
            D64_SCR_WIDTH / 2, D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_BITMAP_RAM  , 0x4000 + 0x72 - 0x4000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_SCREEN_RAM  , 0x6000 + 0x72 - 0x4000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_D020        , 0x5f40 + 0x72 - 0x4000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_COLOR_RAM   , 0x5f43 + 0x72 - 0x4000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x5f44 + 0x72 - 0x4000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "a64", "Wigmore Artist 64 (unpacked)", 0x4000, 10242, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        {
            D64_FMT_MC,
            D64_SCR_WIDTH / 2, D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_SCREEN_RAM  , 0x2000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_COLOR_RAM   , 0x2400, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_D020        , 0x27fe, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x27ff, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "ami", "Amica Paint (packed)", 0x4000, 0, DM_FMT_RDWR,
        fmtProbeAmicaPaintPacked,
        fmtDecodeAmicaPaintPacked, fmtEncodeAmicaPaintPacked,
        { }, &dmC64CommonFormats[0]
    },

    {
        "rpm", "Run Paint (unpacked)", 0x6000, 10006, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        { }, &dmC64CommonFormats[0]
    },

    {
        "ipc", "Interpaint MC (unpacked)", 0x4000, 10003, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        { }, &dmC64CommonFormats[0]
    },

    {
        "art", "Art Studio (unpacked)", 0x2000, 9009, DM_FMT_RDWR,
        fmtProbeArtStudio,
        NULL, NULL,
        { }, &dmC64CommonFormats[2]
    },

    {
        "iph", "Interpaint (unpacked)", 0x4000, 9002, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        { }, &dmC64CommonFormats[2]
    },

    {
        "dd", "Doodle (unpacked)", 0x1c00, 9218, DM_FMT_RDWR,
        fmtProbeDoodle,
        NULL, NULL,
        { }, &dmC64CommonFormats[10]
    },

    {
        "jj", "Doodle (packed)", 0x5c00, 0xfe, DM_FMT_RDWR, // size is abused for RLE marker byte
        fmtProbeDoodle,
        fmtDecodeStaticRLEMarkerMode2, fmtEncodeStaticRLEMarkerMode2,
        { }, &dmC64CommonFormats[10]
    },

    {
        "mon", "Monomagic (unpacked)", 0x2000, 8194, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        {
            D64_FMT_HIRES,
            D64_SCR_WIDTH   , D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH, D64_SCR_CH_HEIGHT,
            1, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0, 0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_OP     , DS_SCREEN_RAM  , 0xCF  , 0, 0,   0, NULL, NULL, DF_NORMAL },
                // Default colors used by MM are --^^
                { DO_LAST       , 0              , 0     , 0, 0,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "hir", "Plain hires (unpacked)", 0x2000, 8002, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        {
            D64_FMT_HIRES,
            D64_SCR_WIDTH   , D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH, D64_SCR_CH_HEIGHT,
            1, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0, 0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_OP     , DS_SCREEN_RAM  , 0xF0  , 0, 0,   0, NULL, NULL, DF_NORMAL },
                { DO_LAST       , 0              , 0     , 0, 0,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "gih", "Gigapaint hires (unpacked)", 0x6000, 8002, DM_FMT_RDWR,
        fmtProbeGigapaintHires,
        NULL, NULL,
        {
            D64_FMT_HIRES,
            D64_SCR_WIDTH   , D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH, D64_SCR_CH_HEIGHT,
            1, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0, 0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_OP     , DS_SCREEN_RAM  , 0x0F  , 0, 0,   0, NULL, NULL, DF_NORMAL },
                { DO_LAST       , 0              , 0     , 0, 0,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "bfli", "Pu-239 Big FLI/BFLI (unpacked)", 0x3bff, 33795, DM_FMT_RD,
        NULL,
        NULL, NULL,
        {
            D64_FMT_MC | D64_FMT_FLI,
            D64_SCR_WIDTH / 2, D64_SCR_HEIGHT * 2,
            D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            fmtGetPixelBFLI,
            {
                { DO_COPY       , DS_COLOR_RAM   , 0x0001, 0,  0x400 ,   0, NULL, NULL, DF_NORMAL },
                DEF_REPEAT_BLOCK_8(DS_SCREEN_RAM , 0x0401, 0, 0x400, 0, DF_NORMAL),
                { DO_COPY       , DS_BITMAP_RAM  , 0x2401, 0,  0x2000,   0, NULL, NULL, DF_NORMAL },
                DEF_REPEAT_BLOCK_8(DS_SCREEN_RAM , 0x4401, 8, 0x400, 0x400, DF_NORMAL),
                { DO_COPY       , DS_BITMAP_RAM  , 0x6401, 1,  0x2000,   0, NULL, NULL, DF_NORMAL },
                { DO_LAST       , 0              , 0     , 0,  0     ,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "bml", "Black Mail FLI (unpacked)", 0x3b00, 17474, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        { }, &dmC64CommonFormats[1]
    },

    {
        "bmlp", "Black Mail FLI (packed)", 0x38f0, 0, DM_FMT_RDWR,
        fmtProbeBlackMailFLIPacked,
        fmtDecodeBlackMailFLIPacked, fmtEncodeBlackMailFLIPacked,
        { }, &dmC64CommonFormats[1]
    },

    {
        "fd1", "FBI Crew FLI Designer 1.1 (unpacked)", 0x3c00, 17409, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        { }, &dmC64CommonFormats[9]
    },

    {
        "fd2", "FLI Designer 2 (unpacked)", 0x3ff0, 17409, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        { }, &dmC64CommonFormats[9]
    },

    {
        "eci", "ECI Graphic Editor 1.0 (unpacked)", 0x4000, 32770, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        { }, &dmC64CommonFormats[7]
    },

    {
        "ecp", "ECI Graphic Editor 1.0 (packed)", 0x4000, 0, DM_FMT_RDWR,
        fmtProbeECIPacked,
        fmtDecodeECIPacked, fmtEncodeECIPacked,
        { }, &dmC64CommonFormats[7]
    },

    {
        "fpt", "Face Painter (unpacked)", 0x4000, 10004, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        { // Almost same layout as Koala Painter, but FPT has D020
            D64_FMT_MC,
            D64_SCR_WIDTH / 2, D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_SCREEN_RAM  , 0x1f40, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_COLOR_RAM   , 0x2328, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x2710, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_D020        , 0x2711, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "xx1", "Unknown $2000 format (unpacked)", 0x2000, 10242, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        {
            D64_FMT_MC,
            D64_SCR_WIDTH / 2, D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY      , DS_BITMAP_RAM  , 0x0000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY      , DS_SCREEN_RAM  , 0x2000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY      , DS_COLOR_RAM   , 0x2400, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_OP    , DS_BGCOL       , 0x00  , 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_LAST      , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "xx2", "Unknown $2000 format (unpacked)", 0x2000, 0, DM_FMT_RDWR,
        fmtProbeFormatXX2,
        fmtDecodeFormatXX2, NULL,
        {
            D64_FMT_MC,
            XX2_WIDTH_CH * 4, XX2_HEIGHT_CH * 8,
            XX2_WIDTH_CH    , XX2_HEIGHT_CH,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY      , DS_BITMAP_RAM  , 0x0000, 0,  XX2_BSIZE,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY      , DS_SCREEN_RAM  , XX2_BSIZE, 0,  XX2_SIZE,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY      , DS_COLOR_RAM   , XX2_BSIZE + XX2_SIZE, 0,  XX2_SIZE,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_OP    , DS_BGCOL       , 11    , 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_LAST      , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "fp2", "FunPaint II (unpacked)", 0x3ff0, 33694, DM_FMT_RDWR,
        fmtProbeFunPaint2,
        fmtDecodeFunPaint2, fmtEncodeFunPaint2Unpacked,
        { }, &dmC64CommonFormats[3]
    },

    {
        "fp2p", "FunPaint II (packed)", 0x3ff0, 0, DM_FMT_RDWR,
        fmtProbeFunPaint2,
        fmtDecodeFunPaint2, fmtEncodeFunPaint2Packed,
        { }, &dmC64CommonFormats[3]
    },

    {
        "gun", "GunPaint 1.1 (unpacked)", 0x4000, 33603, DM_FMT_RDWR,
        fmtProbeGunPaint,
        NULL, NULL,
        {
            D64_FMT_MC | D64_FMT_FLI | D64_FMT_ILACE,
            D64_SCR_WIDTH, D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
            1, 1,
            NULL, NULL,
            fmtGetPixelFunPaint2, // The format is essentially same as FP2
            {
                DEF_REPEAT_BLOCK_8(DS_SCREEN_RAM , 0x0000, 0, 0x400, 0, DF_NORMAL),
                { DO_COPY       , DS_BITMAP_RAM  , 0x2000, 0,  0  , 0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_EXTRA_DATA  , 0x3f4f, 0,  177, 0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_COLOR_RAM   , 0x4000, 0,  0  , 0, NULL, NULL, DF_NORMAL },
                DEF_REPEAT_BLOCK_8(DS_SCREEN_RAM , 0x4400, 8, 0x400, 0, DF_NORMAL),
                { DO_COPY       , DS_BITMAP_RAM  , 0x6400, 1,  0  , 0, NULL, NULL, DF_NORMAL },
                // GunPaint does not store the last 3 d021 values .. so set them to black
                // XXX TODO: According to some, the last 4 should be same ..
                { DO_SET_MEM    , DS_EXTRA_DATA  , 0     , 0,  3  , 20+177, NULL, NULL, DF_NORMAL },
                { DO_FUNC       , 0              , 0x2742, 0,  1  , 0, fmtTruePaintGetLaceType, NULL, DF_NORMAL },
                { DO_FUNC       , 0              , 0     , 0,  0  , 0, NULL, fmtEncodeGunPaint, DF_NORMAL },
                { DO_LAST       , 0              , 0     , 0,  0  , 0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "hcb", "Half Char Bitmap (unpacked)", 0x5000, 12148, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        {
            D64_FMT_MC | D64_FMT_FLI,
            D64_SCR_WIDTH / 2, D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH, D64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            fmtGetPixelHCB,
            {
                { DO_COPY       , DS_COLOR_RAM   , 0x0000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_COLOR_RAM   , 0x0400, 1,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_SCREEN_RAM  , 0x0800, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_SCREEN_RAM  , 0x0c00, 1,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_BITMAP_RAM  , 0x1000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_EXTRA_DATA  , 0x2f40, 0,  D64_SCR_HEIGHT / 4,   0, NULL, NULL, DF_NORMAL },
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "pen", "Pentel Paint (unpacked)", 0x4800, 19845, DM_FMT_RD | DM_FMT_BROKEN,
        NULL,
        NULL, NULL,
        {
            D64_FMT_HIRES | D64_FMT_FLI,
            192, D64_SCR_HEIGHT,
            24, D64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            fmtGetPixelPentelPaint,
            {
                { DO_COPY       , DS_BITMAP_RAM  , 0x0000         , 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_OP     , DS_SCREEN_RAM  , 0x10           , 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x9580 - 0x4800, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_D022        , 0x9581 - 0x4800, 0,  0,   0, NULL, NULL, DF_NORMAL }, // Sprite color
                { DO_SET_MEM_LO , DS_COLOR_RAM   , 0x9582 - 0x4800, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_EXTRA_DATA  , 0x5ac0 - 0x4800, 0,  D64_SPR_SIZE * 235,   0, NULL, NULL, DF_NORMAL }, // Sprite data
                { DO_LAST       , 0              , 0              , 0,  0,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "chfd", "Crest Hires FLI Designer (unpacked)", 0x4000, 16386, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        {
            D64_FMT_HIRES | D64_FMT_FLI,
            D64_SCR_WIDTH, D64_SCR_HEIGHT,    // Actually 296 x 112 (=14*8)
            D64_SCR_CH_WIDTH, D64_SCR_CH_HEIGHT,
            1, 1,
            NULL, NULL,
            fmtGetPixelCrestHIFLIorCDHM,
            {
                { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0,  0    ,  0, NULL, NULL, DF_NORMAL },
                DEF_REPEAT_BLOCK_8(DS_SCREEN_RAM , 0x2000, 0,  0x400,  0, DF_NORMAL),
                { DO_LAST       , 0              , 0     , 0,  0    ,  0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "cshf", "Crest Super Hires FLI Editor v1.0 (unpacked)", 0x4000, 15874, DM_FMT_RDWR,
        fmtProbeCrestSHFLI,
        NULL, NULL,
        { }, &dmC64CommonFormats[11]
    },

    {
        "cshfp", "Crest Super Hires FLI Editor v1.0 (packed)", 0xa000, 0, DM_FMT_RD,
        fmtProbeCrestSHFLI,
        fmtDecodeCrestSHFLIPacked, NULL,
        { }, &dmC64CommonFormats[11]
    },

    {
        "cdhm", "Cosmos Designs Hires Manager (unpacked)", 0x4000, 16385, DM_FMT_RDWR,
        fmtProbeCosmosDesignsHiresManager,
        NULL, NULL,
        { }, &dmC64CommonFormats[8]
    },

    {
        "cdhp", "Cosmos Designs Hires Manager (packed)", 0x4000, 0, DM_FMT_RD,
        fmtProbeCosmosDesignsHiresManager,
        fmtDecodeCosmosDesignsHiresManagerPacked, NULL,
        { }, &dmC64CommonFormats[8]
    },

    {
        "mrqp", "Marq's PETSCII editor (unpacked)", 0x0801, 0, DM_FMT_RD,
        fmtProbeMarqPETSCII,
        NULL, NULL,
        {
            D64_FMT_HIRES | D64_FMT_CHAR,
            D64_SCR_WIDTH   , D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH, D64_SCR_CH_HEIGHT,
            1, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_SCREEN_RAM  , 0x60  , 0, 0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_COLOR_RAM   , 0x60 + 1000, 0, 0,   0, NULL, NULL, DF_NORMAL },

                // For offset values see petscii/m_c64.pde :: save_prg()
                { DO_SET_MEM_LO , DS_D020        , 25 - 2, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_BGCOL       , 30 - 2, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_EXTRA_DATA  , 20 - 2, 0,  1,   0, NULL, NULL, DF_NORMAL },
                { DO_FUNC       , 0              , 0     , 0,  0,   0, fmtSetMarqPETSCIIData, NULL, DF_NORMAL },

                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "upet", "Unknown PETSCII (unpacked)", 0x0801, 2499, DM_FMT_RD,
        NULL,
        NULL, NULL,
        {
            D64_FMT_HIRES | D64_FMT_CHAR,
            D64_SCR_WIDTH   , D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH, D64_SCR_CH_HEIGHT,
            1, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_SCREEN_RAM  , 0x01ab, 0, 0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_COLOR_RAM   , 0x01ab + 1000, 0, 0,   0, NULL, NULL, DF_NORMAL },

                // For offset values see petscii/m_c64.pde :: save_prg()
                { DO_SET_MEM_LO , DS_D020        , 0x01a9, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x01aa, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_FUNC       , 0              , 0     , 0,  0,   0, fmtSetSPETSCIIData, NULL, DF_NORMAL },

                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "pkhu", "petscii.krissz.hu editor (unpacked)", 0x0801, 0, DM_FMT_RD,
        fmtProbePetsciiKrisszHu,
        NULL, NULL,
        {
            D64_FMT_CHAR,
            D64_SCR_WIDTH   , D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH, D64_SCR_CH_HEIGHT,
            1, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_SCREEN_RAM  , 0x2001 - 2, 0, 0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_COLOR_RAM   , 0x23e9 - 2, 0, 0,   0, NULL, NULL, DF_NORMAL },

                { DO_COPY       , DS_EXTRA_DATA  , 0x0000    , 0, 0x0100, 0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_CHAR_DATA   , 0x1801 - 2, 0, 0x0800, 0, NULL, NULL, DF_NORMAL },
                { DO_FUNC       , 0              , 0         , 0, 0     , 0, fmtSetPetsciiKrisszHuData, NULL, DF_NORMAL },

                { DO_LAST       , 0              , 0         , 0, 0,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },
};

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