view tools/lib64fmts.c @ 2299:ad019d930401

Add initial (broken) read support for Flinterlazer 1.0 images.
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 04 Jul 2019 11:09:51 +0300
parents 7f6ba3b32f54
children a494e4a4b6bc
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,
};

static const Uint8 fmtMarqPETSCII_ID2[] =
{
    0xa9, 0x00, 0x8d, 0x18, 0xd0, // lda #? : sta $d018
    0xa9, 0x00, 0x8d, 0x20, 0xd0, // lda #? : sta $d020
    0xa9, 0x00, 0x8d, 0x21, 0xd0, // lda #? : sta $d021
};

static const Uint8 fmtMarqPETSCII_ID3[] =
{
    0xa2, 0x00, 0xa0, 0xfa, 0xbd, 0x61,
    0x08, 0x9d, 0x00, 0x04, 0xbd, 0x5b, 0x09, 0x9d, 0xfa, 0x04,
    0xbd, 0x55, 0x0a, 0x9d, 0xf4, 0x05, 0xbd, 0x4f, 0x0b, 0x9d,
    0xee, 0x06, 0xbd, 0x49, 0x0c, 0x9d, 0x00, 0xd8, 0xbd, 0x43,
    0x0d, 0x9d, 0xfa, 0xd8, 0xbd, 0x3d, 0x0e, 0x9d, 0xf4, 0xd9,
    0xbd, 0x37, 0x0f, 0x9d, 0xee, 0xda, 0xe8, 0x88, 0xd0, 0xcc,
    0xa9, 0x1b, 0x8d, 0x11, 0xd0, 0x4c, 0x5e, 0x08,
};


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 + sizeof(fmtMarqPETSCII_ID1) +
            sizeof(fmtMarqPETSCII_ID2), fmtMarqPETSCII_ID3) == 0
        )
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


static int fmtGetMarqPETSCIIData(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_MODE] = D64_FMT_HIRES | D64_FMT_CHAR;
    img->extraInfo[D64_EI_CHAR_CUSTOM] = 0;

    return DMERR_OK;
}


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

    switch (img->extraInfo[D64_EI_CHAR_CASE])
    {
        case 0: val = 20; break;
        case 1: val = 23; break;
        default: return DMERR_INVALID_DATA;
    }

    img->extraData[0].data[0] = val;

    memcpy(buf->data,
        fmtMarqPETSCII_ID1, sizeof(fmtMarqPETSCII_ID1));

    memcpy(buf->data + sizeof(fmtMarqPETSCII_ID1),
        fmtMarqPETSCII_ID2, sizeof(fmtMarqPETSCII_ID2));

    memcpy(buf->data + sizeof(fmtMarqPETSCII_ID1) + sizeof(fmtMarqPETSCII_ID2),
        fmtMarqPETSCII_ID3, sizeof(fmtMarqPETSCII_ID3));

    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_MODE] = D64_FMT_HIRES | D64_FMT_CHAR;
            img->d020    = data[0x001e - 2];
            img->bgcolor = data[0x0023 - 2];
            break;

        case 0xd8:
            img->extraInfo[D64_EI_MODE] = D64_FMT_MC | D64_FMT_CHAR;
            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_MODE] = D64_FMT_ECM | D64_FMT_CHAR;
            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_MODE] = D64_FMT_HIRES | D64_FMT_CHAR;
    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->mode & 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->extraInfo[D64_EI_ILACE_TYPE] = 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->extraInfo[D64_EI_ILACE_TYPE] == 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);
}


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


static int fmtGetPixelFlinterlazer(Uint8 *col,
    const DMC64Image *img, const int rasterX, const int rasterY)
{
    DM_C64_GENERIC_MC_PIXEL_DEFS(img)
    const int vbank = rasterY & 7;
    int res;
    Uint8 color1, color2;
    Uint8 bgcol = img->bgcolor;
    if ((res = dmC64GetGenericMCPixel(&color1, img, bmoffs, scroffs, vshift, vbank    , 0, 0, bgcol)) != DMERR_OK)
        return res;
    *col = color1 * 17;
    return DMERR_OK;
}


enum
{
    I_BRK            = 0x00,
    I_RTS            = 0x60,
    I_LDA_IMD        = 0xa9,
    I_STA_ABS        = 0x8d,
};


static int fmtDecode6502SpeedCode(const DMGrowBuf *buf, DMC64Image *img, const int cbank)
{
    size_t reg_pc = 0;
    Uint8 reg_accu = 0;//, reg_x = 0, reg_y = 0;
    Uint16 reg_ptr;

    while (reg_pc < buf->size)
    {
        Uint8 instr = buf->data[reg_pc++];

        switch (instr)
        {
            case I_LDA_IMD:
                if (reg_pc >= buf->size)
                    goto out;

                reg_accu = buf->data[reg_pc++];
                break;

            case I_STA_ABS:
                if (reg_pc >= buf->size)
                    goto out;
                reg_ptr = buf->data[reg_pc++];

                if (reg_pc >= buf->size)
                    goto out;
                reg_ptr |= buf->data[reg_pc++] << 8;

                if (reg_ptr >= 0xd800 && reg_ptr <= 0xdbff)
                {
                    img->color[cbank].data[reg_ptr - 0xd800] = reg_accu;
                }
                break;

            case I_RTS:
                return DMERR_OK;

            default:
                return DMERR_INVALID_DATA;
        }
    }

out:
    return DMERR_INVALID_DATA;
}


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

    // Flinterlazer stores color RAMs as speedcode, so we need to
    // decode some 6510 instructions to get the data.
    return fmtDecode6502SpeedCode(
        dmGrowBufConstCopyOffsSize(&tmp, buf, 0, 0x17b2), img, 0);
}


#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);
}


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


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 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 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_SET_OP     , DS_EXTRA_INFO  , D64_FLI_8,  0    , 0, D64_EI_FLI_TYPE, NULL, NULL, DF_DECODE },
            { 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_SET_OP     , DS_EXTRA_INFO  , D64_ILACE_RES, 0 , 0  , D64_EI_ILACE_TYPE, NULL, NULL, DF_DECODE },
            { DO_SET_OP     , DS_EXTRA_INFO  , D64_FLI_8,  0,   0, D64_EI_FLI_TYPE, NULL, NULL, DF_DECODE },
            { 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_SET_OP     , DS_EXTRA_INFO  , D64_ILACE_RES, 0 , 0  , D64_EI_ILACE_TYPE, NULL, NULL, DF_DECODE },
            { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
        }
    },

    { // #7: ECI Graphic Editor Hires FLI
        D64_FMT_HIRES | D64_FMT_FLI | D64_FMT_ILACE,
        D64_SCR_WIDTH,    D64_SCR_HEIGHT,
        D64_SCR_CH_WIDTH, D64_SCR_CH_HEIGHT,
        1, 1,
        NULL, 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_SET_OP     , DS_EXTRA_INFO  , D64_ILACE_COLOR, 0 , 0  , D64_EI_ILACE_TYPE, NULL, NULL, DF_DECODE },
            { DO_SET_OP     , DS_EXTRA_INFO  , D64_FLI_8,  0,   0, D64_EI_FLI_TYPE, NULL, NULL, DF_DECODE },
            { 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_SET_OP     , DS_EXTRA_INFO  , D64_FLI_8,  0,   0, D64_EI_FLI_TYPE, NULL, NULL, DF_DECODE },
            { 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_SET_OP     , DS_EXTRA_INFO  , D64_FLI_8,  0,   0, D64_EI_FLI_TYPE, NULL, NULL, DF_DECODE },
            { 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_SET_OP     , DS_EXTRA_INFO  , D64_FLI_8,  0,   0, D64_EI_FLI_TYPE, NULL, NULL, DF_DECODE },
            { 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_SET_OP     , DS_EXTRA_INFO  , D64_FLI_8,  0,   0, D64_EI_FLI_TYPE, NULL, NULL, DF_DECODE },
                { 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]
    },

    {
        "flnt", "Flinterlazer 1.0 (unpacked)", 0x284e, 38812, DM_FMT_RD | DM_FMT_BROKEN,
        NULL,
        NULL, NULL,
        {
            D64_FMT_MC | D64_FMT_FLI | D64_FMT_ILACE,
            D64_SCR_WIDTH / 2, D64_SCR_HEIGHT,
            D64_SCR_CH_WIDTH , D64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            fmtGetPixelFlinterlazer,
            {
                { DO_COPY       , DS_COLOR_RAM   , 0x0000         , 0,  0    ,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_COLOR_RAM   , 0x0000         , 1,  0    ,   0, NULL, NULL, DF_NORMAL },

                { DO_COPY       , DS_BITMAP_RAM  , 0x4000 - 0x284e, 0,  0    ,   0, NULL, NULL, DF_NORMAL },
                DEF_REPEAT_BLOCK_8(DS_SCREEN_RAM , 0x6000 - 0x284e, 0,  0x400,   0, DF_NORMAL),

                { DO_COPY       , DS_BITMAP_RAM  , 0x8000 - 0x284e, 1,  0    ,   0, NULL, NULL, DF_NORMAL },
                DEF_REPEAT_BLOCK_8(DS_SCREEN_RAM , 0xA000 - 0x284e, 1,  0x400,   0, DF_NORMAL),

                { DO_SET_OP     , DS_EXTRA_INFO  , D64_ILACE_COLOR, 0 , 0    ,   D64_EI_ILACE_TYPE, NULL, NULL, DF_DECODE },
                { DO_SET_OP     , DS_EXTRA_INFO  , D64_FLI_8      , 0,  0    ,   D64_EI_FLI_TYPE, NULL, NULL, DF_DECODE },
                { DO_FUNC       , 0              , 0     , 0,  0,   0, fmtDecodeFlinterlazer, NULL, DF_NORMAL },
                { DO_LAST       , 0              , 0              , 0,  0    ,   0, NULL, NULL, DF_NORMAL },
            }
        },
        NULL
    },

    {
        "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_SET_OP     , DS_EXTRA_INFO  , D64_ILACE_RES  , 0,  0  , D64_EI_ILACE_TYPE, NULL, NULL, DF_DECODE },
                { DO_SET_OP     , DS_EXTRA_INFO  , D64_FLI_8      , 0,  0  , D64_EI_FLI_TYPE, NULL, NULL, DF_DECODE },
                { 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_SET_OP     , DS_EXTRA_INFO  , D64_FLI_8      , 0,  0  , D64_EI_FLI_TYPE, NULL, NULL, DF_DECODE },
                { 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_SET_OP     , DS_EXTRA_INFO  , D64_FLI_8      , 0,  0  , D64_EI_FLI_TYPE, NULL, NULL, DF_DECODE },
                { 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_SET_OP     , DS_EXTRA_INFO  , D64_FLI_8      , 0,  0  , D64_EI_FLI_TYPE, NULL, NULL, DF_DECODE },
                { 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_RDWR,
        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,
            {
                // For offset values see petscii/m_c64.pde :: save_prg()
                { DO_FUNC       , 0              , 0     , 0,  0,   0, NULL, fmtSetMarqPETSCIIData, DF_NORMAL },
                { 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 },
                { 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, fmtGetMarqPETSCIIData, 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]);