view tools/lib64fmts.c @ 2080:7e4087e2740d

Define macros DM_MEMCMP_SIZE() and DM_MEMCMP_LEN() as helpers.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 10 Dec 2018 19:36:48 +0200
parents c27ed6465022
children 9dc2976e9fa3
line wrap: on
line source

/*
 * Functions for reading and converting various restricted
 * C64/etc and/or indexed/paletted graphics formats.
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2012-2018 Tecnic Software productions (TNSP)
 *
 * Please read file 'COPYING' for information on license and distribution.
 */
#include "lib64gfx.h"

#define 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 fmtMicroIllustratorMagicID_1[] =
{
    0xff, 0x80, 0x69, 0x67, 0x14, 0x00,
};

static const Uint8 fmtMicroIllustratorMagicID_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, fmtMicroIllustratorMagicID_1) == 0
        &&
        DM_MEMCMP_SIZE(buf->data + 9, fmtMicroIllustratorMagicID_2) == 0
        )
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


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

    memcpy(buf->data + 2, fmtMicroIllustratorMagicID_1, sizeof(fmtMicroIllustratorMagicID_1));
    memcpy(buf->data + 9, fmtMicroIllustratorMagicID_2, sizeof(fmtMicroIllustratorMagicID_2));

    return TRUE;
}


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

    if (buf->len == 10003)
        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 != 10006 &&
        buf->len != 10003 &&
        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 fmtDecodeStaticRLEMarkerMode2(DMC64Image *img, const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf mem;
    DMCompParams cfg;

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

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

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

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


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

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

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

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


static int fmtProbeDrazPaint20Packed(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    const Uint8 *ident = buf->data + 2;
    if (buf->len > 22 &&
        dmCompareAddr16(buf, 0, fmt->addr) &&
        DM_MEMCMP_LEN(ident, "DRAZPAINT ") == 0 &&
        ident[11] == '.' && (
        (ident[10] == '1' && ident[12] == '4') ||
        (ident[10] == '2' && ident[12] == '0')
        ))
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


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

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

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

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

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


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

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

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

    dmGenericRLEAnalyze(&tmp, &cfg);

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

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

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


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

    return DM_PROBE_SCORE_FALSE;
}


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

    if (buf != NULL)
        img->laceType = buf->data[op->offs] ? D64_ILACE_RES : D64_ILACE_COLOR;
    else
        img->laceType = D64_ILACE_RES;

    return TRUE;
}


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


static const char *fmtBDP5MagicID = "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, fmtBDP5MagicID) == 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 *) fmtBDP5MagicID, strlen(fmtBDP5MagicID)) ||
        !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 *fmtGunPaintMagicID = "GUNPAINT (JZ) ";
#define fmtGunPaintMagicLen   (14)
#define fmtGunPaintMagicOffs  (0x3e8)

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

    return DM_PROBE_SCORE_FALSE;
}


static BOOL 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 + fmtGunPaintMagicOffs + 2, fmtGunPaintMagicID, fmtGunPaintMagicLen);
    return TRUE;
}


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 Uint8 fmtGetPixelFLIDesigner(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int shift, const int bitmap, const int rasterX, const int rasterY)
{
    (void) rasterX;
    return dmC64GetGenericMCPixel(img, bmoffs, scroffs, shift, rasterY & 7, bitmap, 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 Uint8 fmtGetPixelBlackMailFLI(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int shift, const int bitmap, const int rasterX, const int rasterY)
{
    const int vbank = rasterY & 7;
    (void) rasterX;

    return dmC64GetGenericMCPixel(
        img, bmoffs, scroffs, shift,
        vbank, bitmap, 0,
        img->extraData[0].data[rasterY] & 15);
}


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


static Uint8 fmtGetPixelTruePaint(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int shift, const int bitmap, const int rasterX, const int rasterY)
{
    (void) rasterX;
    (void) rasterY;
    return dmC64GetGenericMCPixel(img, bmoffs, scroffs, shift, 0, bitmap, 0, img->bgcolor);
}


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

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

    return DM_PROBE_SCORE_FALSE;
}


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


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

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

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

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

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

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

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

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

                    // fallthrough

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

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

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

                    count = data;
                    // fallthrough

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

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

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

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

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


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


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

    return DM_PROBE_SCORE_FALSE;
}


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

    // If there is only data for less than XX2_MIN_SIZE bytes,
    // allocate a buffer of that size and copy data there.
    // Otherwise allocate len bytes.
    if (dmGrowBufCopy(&tmp, buf, buf->len < XX2_MIN_SIZE ? XX2_MIN_SIZE - buf->len : 0) == NULL)
        return DMERR_MALLOC;

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


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

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

    return DM_PROBE_SCORE_FALSE;
}


#define FUNPAINT2_HEADER_SIZE (0x10)
static const char *fmtFunPaint2MagicID = "FUNPAINT (MT) ";


static int fmtProbeFunPaint2(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    if (buf->len > 30 &&
        DM_MEMCMP_LEN(buf->data + 2, fmtFunPaint2MagicID) == 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, FUNPAINT2_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, FUNPAINT2_HEADER_SIZE), fmt);
    }

    return res;
}


static int fmtEncodeFunPaint2Unpacked(DMGrowBuf *buf, const DMC64Image *img, const DMC64ImageFormat *fmt)
{
    // Add the header bits
    if (!dmGrowBufPut(buf, (Uint8 *) fmtFunPaint2MagicID, strlen(fmtFunPaint2MagicID)) ||
        !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 *) fmtFunPaint2MagicID, strlen(fmtFunPaint2MagicID)) ||
        !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 Uint8 fmtGetPixelFunPaint2(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int shift, const int bitmap, const int rasterX, const int rasterY)
{
    const int vbank = (rasterY & 7) + (bitmap * 8);
    (void) rasterX;

    return dmC64GetGenericMCPixel(
        img, bmoffs, scroffs, shift,
        vbank, bitmap, 0,
        img->extraData[0].data[rasterY] & 15);
}


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

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


static Uint8 fmtGetPixelPentelPaint(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int shift, const int bitmap, const int rasterX, const int rasterY)
{
    const int ry = rasterY / C64_SPR_HEIGHT_PX;
    const int yd = rasterY % C64_SPR_HEIGHT_PX;
    const int rx = rasterX / C64_SPR_WIDTH_PX;
    const int xd = rasterX % C64_SPR_WIDTH_PX;
    const int offs = (ry * 8 + rx) * C64_SPR_SIZE + (yd * C64_SPR_WIDTH_UT) + (xd / 8);
    const int mask = 1 << (7 - (rasterX & 7));

    Uint8 color1 = dmC64GetGenericSCPixel(img, bmoffs, scroffs, shift, 0, bitmap, 0);
    Uint8 color2 = img->extraData[0].data[offs] & mask ? 0x0f : 0;
    Uint8 color3 = img->extraData[0].data[offs + C64_SPR_SIZE * 155] & mask ? img->d022 : 0;

    return color3 ? color3 : ( color2 ? color2 : color1 );
}


static Uint8 fmtGetPixelHCB(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int shift, const int bitmap, const int rasterX, const int rasterY)
{
    const int vbank = (rasterY / 4) & 1;
    (void) bitmap;
    (void) rasterX;

    return dmC64GetGenericMCPixel(
        img, bmoffs, scroffs,
        shift, vbank, 0, vbank,
        img->extraData[0].data[rasterY / 5] & 15);
}


static Uint8 fmtGetPixelCrestHIFLIorCDHM(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int shift, const int bitmap, const int rasterX, const int rasterY)
{
    (void) rasterX;
    return dmC64GetGenericSCPixel(img, bmoffs, scroffs, shift, rasterY & 7, bitmap, 0);
}


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


static Uint8 fmtGetPixelECI(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int shift, const int bitmap, const int rasterX, const int rasterY)
{
    const int vbank = rasterY & 7;
    Uint8
        c1 = dmC64GetGenericSCPixel(img, bmoffs, scroffs, shift, vbank    , 0, 0),
        c2 = dmC64GetGenericSCPixel(img, bmoffs, scroffs, shift, vbank + 8, 1, 0);

    (void) bitmap;
    (void) rasterX;
    return (c1 * C64_NCOLORS) + c2;
}


static int fmtConvertECIBMP2Image(DMImage *dst, const DMC64Image *src,
    const DMC64ImageFormat *fmt, const DMC64ImageConvSpec *spec)
{
    if (!dmSetMixedColorC64Palette(dst))
        return DMERR_MALLOC;

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


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

    // XXX TODO: Perhaps count statistics about used byte values
    // and compare to value in buf[2] which is the RLE marker
    if (buf->len < 128 ||
        !dmCompareAddr16(buf, 0, fmt->addr) ||
        // Try to avoid misprobe of Crest Hires FLI Designer and Cosmos Design format
        buf->len == 16386 || buf->len == 16385 ||
        // Face Painter
        buf->len == 10004)
        return DM_PROBE_SCORE_FALSE;

    for (n = 0, i = 3; i < buf->len; i++)
        if (buf->data[i] == buf->data[2]) 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 fmtDecodeECIPacked(DMC64Image *img, const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    int res;
    DMGrowBuf tmp;
    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[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_SCREEN_RAM(start, oindex, bindex, osize, bsize) \
    { DO_COPY, DS_SCREEN_RAM, (start) + ((osize) * (oindex)), (bindex), (bsize), 0, NULL, NULL }

#define DEF_SCREEN_RAMS_8(start, sindex, osize, bsize) \
    DEF_SCREEN_RAM((start), 0, (sindex + 0), (osize), (bsize)), \
    DEF_SCREEN_RAM((start), 1, (sindex + 1), (osize), (bsize)), \
    DEF_SCREEN_RAM((start), 2, (sindex + 2), (osize), (bsize)), \
    DEF_SCREEN_RAM((start), 3, (sindex + 3), (osize), (bsize)), \
    DEF_SCREEN_RAM((start), 4, (sindex + 4), (osize), (bsize)), \
    DEF_SCREEN_RAM((start), 5, (sindex + 5), (osize), (bsize)), \
    DEF_SCREEN_RAM((start), 6, (sindex + 6), (osize), (bsize)), \
    DEF_SCREEN_RAM((start), 7, (sindex + 7), (osize), (bsize))



//
// 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,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        2, 1,
        NULL, NULL,
        NULL,
        {
            { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0,  0,   0, NULL, NULL },
            { DO_COPY       , DS_SCREEN_RAM  , 0x1f40, 0,  0,   0, NULL, NULL },
            { DO_COPY       , DS_COLOR_RAM   , 0x2328, 0,  0,   0, NULL, NULL },
            { DO_SET_MEM_LO , DS_BGCOL       , 0x2710, 0,  0,   0, NULL, NULL },
            { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL },
        }
    },

    { // #1: Black Mail FLI Graph
        D64_FMT_MC | D64_FMT_FLI,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        2, 1,
        NULL, NULL,
        fmtGetPixelBlackMailFLI,
        {
            { DO_COPY       , DS_EXTRA_DATA  , 0x0000, 0,  200,   0, NULL, NULL },
            { DO_COPY       , DS_COLOR_RAM   , 0x0100, 0,  0,   0, NULL, NULL },
            DEF_SCREEN_RAMS_8(0x0500, 0, 0x400, 0),
            { DO_COPY       , DS_BITMAP_RAM  , 0x2500, 0,  0,   0, NULL, NULL },
            { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL },
        }
    },

    { // #2: Art Studio etc. Hires
        D64_FMT_HIRES,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        1, 1,
        NULL, NULL,
        NULL,
        {
            { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0,  0,   0, NULL, NULL },
            { DO_COPY       , DS_SCREEN_RAM  , 0x1f40, 0,  0,   0, NULL, NULL },
            { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL },
        }
    },

    { // #3: FunPaint II
        D64_FMT_MC | D64_FMT_FLI | D64_FMT_ILACE,
        C64_SCR_WIDTH, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        1, 1,
        NULL, NULL,
        fmtGetPixelFunPaint2,
        {
            DEF_SCREEN_RAMS_8(0x0000, 0, 0x400, 0),
            { DO_COPY       , DS_BITMAP_RAM  , 0x2000, 0,  0  , 0, NULL, NULL },
            { DO_COPY       , DS_EXTRA_DATA  , 0x3f48, 0,  100, 0, NULL, NULL },
            { DO_COPY       , DS_COLOR_RAM   , 0x4000, 0,  0  , 0, NULL, NULL },
            DEF_SCREEN_RAMS_8(0x43e8, 8, 0x400, 0),
            { DO_COPY       , DS_BITMAP_RAM  , 0x63e8, 1,  0  , 0, NULL, NULL },
            { DO_COPY       , DS_EXTRA_DATA  , 0x8328, 0,  100, 100, NULL, NULL },
            { DO_FUNC       , 0              , 0     , 0,  1  , 0, fmtTruePaintGetLaceType, NULL },
            { DO_LAST       , 0              , 0     , 0,  0  , 0, NULL, NULL },
        }
    },

    { // #4: DrazPaint 1.x & 2
        D64_FMT_MC,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        2, 1,
        NULL, NULL,
        NULL,
        {
            { DO_COPY       , DS_COLOR_RAM   , 0x0000, 0,  0,   0, NULL, NULL },
            { DO_COPY       , DS_BITMAP_RAM  , 0x0800, 0,  0,   0, NULL, NULL },
            { DO_COPY       , DS_SCREEN_RAM  , 0x0400, 0,  0,   0, NULL, NULL },
            { DO_SET_MEM_LO , DS_BGCOL       , 0x2740, 0,  0,   0, NULL, NULL },
            { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL },
        }
    },

    { // #5: DrazLace 1.0
        D64_FMT_MC | D64_FMT_ILACE,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        1, 1,
        NULL, NULL,
        NULL,
        {
            { DO_COPY       , DS_COLOR_RAM   , 0x0000, 0,  0,   0, NULL, NULL },
            { DO_COPY       , DS_BITMAP_RAM  , 0x0800, 0,  0,   0, NULL, NULL },
            { DO_COPY       , DS_SCREEN_RAM  , 0x0400, 0,  0,   0, NULL, NULL },
            { DO_SET_MEM_LO , DS_BGCOL       , 0x2740, 0,  0,   0, NULL, NULL },
            { DO_FUNC       , 0              , 0x2742, 0,  1,   0, fmtDrazLaceGetLaceType, fmtDrazLaceSetLaceType },
            { DO_COPY       , DS_BITMAP_RAM  , 0x2800, 1,  0,   0, NULL, NULL },
            { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL },
        }
    },

    { // #6: TruePaint
        D64_FMT_MC | D64_FMT_ILACE,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        1, 1,
        NULL, NULL,
        fmtGetPixelTruePaint,
        {
            { DO_COPY       , DS_SCREEN_RAM  , 0x0000, 0,  0,   0, NULL, NULL },
            { DO_SET_MEM_LO , DS_BGCOL       , 0x03e8, 0,  0,   0, NULL, NULL },
            { DO_COPY       , DS_BITMAP_RAM  , 0x0400, 0,  0,   0, NULL, NULL },
            { DO_COPY       , DS_BITMAP_RAM  , 0x2400, 1,  0,   0, NULL, NULL },
            { DO_COPY       , DS_SCREEN_RAM  , 0x4400, 1,  0,   0, NULL, NULL },
            { DO_COPY       , DS_COLOR_RAM   , 0x4800, 0,  0,   0, NULL, NULL },
            { DO_FUNC       , 0              , 0x0000, 0,  0,   0, fmtTruePaintGetLaceType, NULL },
            { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL },
        }
    },

    { // #7: ECI Graphic Editor Hires FLI
        D64_FMT_HIRES | D64_FMT_FLI,
        C64_SCR_WIDTH,    C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        1, 1,
        fmtConvertECIBMP2Image, NULL,
        fmtGetPixelECI,
        {
            { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0,  0,   0, NULL, NULL },
            DEF_SCREEN_RAMS_8(0x2000, 0, 0x400, 0),
            { DO_COPY       , DS_BITMAP_RAM  , 0x4000, 1,  0,   0, NULL, NULL },
            DEF_SCREEN_RAMS_8(0x6000, 8, 0x400, 0),
            { DO_FUNC       , 0              , 0     , 0,  0,   0, fmtECIGetLaceType, NULL },
            { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL },
        }
    },

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

    { // #9: FBI Crew FLI Designer 1.x & 2.0
        D64_FMT_MC | D64_FMT_FLI,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        2, 1,
        NULL, NULL,
        fmtGetPixelFLIDesigner,
        {
            { DO_COPY      , DS_COLOR_RAM   , 0x0000, 0,  0,   0, NULL, NULL },
            DEF_SCREEN_RAMS_8(0x0400, 0, 0x400, 0),
            { DO_COPY      , DS_BITMAP_RAM  , 0x2400, 0,  0,   0, NULL, NULL },
            { DO_LAST      , 0              , 0     , 0,  0,   0, NULL, NULL },
        }
    },

    { // #10: Doodle
        D64_FMT_HIRES,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        1, 1,
        NULL, NULL,
        NULL,
        {
            { DO_COPY       , DS_SCREEN_RAM  , 0x0000, 0,  0,   0, NULL, NULL },
            { DO_COPY       , DS_BITMAP_RAM  , 0x0400, 0,  0,   0, NULL, NULL },
            { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL },
        }
    },
};


//
// 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,
            C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
            C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_COLOR_RAM   , 0x0000, 0,  0,   0, NULL, NULL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x07e8, 0,  0,   0, NULL, NULL },
                { DO_COPY       , DS_BITMAP_RAM  , 0x0800, 0,  0,   0, NULL, NULL },
                { DO_COPY       , DS_SCREEN_RAM  , 0x0400, 0,  0,   0, NULL, NULL },
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL },
            },
        },
        NULL
    },

    {
        "p64", "Picasso 64 (unpacked)", 0x1800, 10050, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        {
            D64_FMT_MC,
            C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
            C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_COLOR_RAM   , 0x0000, 0,  0,   0, NULL, NULL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x07fe, 0,  0,   0, NULL, NULL },
                { DO_COPY       , DS_BITMAP_RAM  , 0x0800, 0,  0,   0, NULL, NULL },
                { DO_COPY       , DS_SCREEN_RAM  , 0x0400, 0,  0,   0, NULL, NULL },
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL },
            },
        },
        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,
            C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
            C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0,  0,   0, NULL, NULL },
                { DO_COPY       , DS_SCREEN_RAM  , 0x1f40, 0,  0,   0, NULL, NULL },
                { DO_SET_MEM_LO , DS_D020        , 0x2328, 0,  0,   0, NULL, NULL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x2329, 0,  0,   0, NULL, NULL },
                { DO_COPY       , DS_COLOR_RAM   , 0x2338, 0,  0,   0, NULL, NULL },
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL },
            },
        },
        NULL
    },

    {
        "ims", "Image System MC (unpacked)", 0x3c00, 10218, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        {
            D64_FMT_MC,
            C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
            C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_COLOR_RAM   , 0x0000, 0,  0,   0, NULL, NULL },
                { DO_COPY       , DS_BITMAP_RAM  , 0x0400, 0,  0,   0, NULL, NULL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x23ff, 0,  0,   0, NULL, NULL },
                { DO_COPY       , DS_SCREEN_RAM  , 0x2400, 0,  0,   0, NULL, NULL },
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL },
            }
        },
        NULL
    },

    {
        "mil", "Micro Illustrator (unpacked)", 0x18dc, 10022, DM_FMT_RDWR | DM_FMT_BROKEN,
        fmtProbeMicroIllustrator,
        NULL, NULL,
        {
            D64_FMT_MC,
            C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
            C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_SCREEN_RAM  , 20 + 0     , 0,  0,   0, NULL, NULL },
                { DO_COPY       , DS_COLOR_RAM   , 20 + 1000  , 0,  0,   0, NULL, NULL },
                { DO_COPY       , DS_BITMAP_RAM  , 20 + 2000  , 0,  0,   0, NULL, NULL },
                // XXX TODO: Unknown where the background color is set, so default to 0x00
                { DO_SET_OP     , DS_BGCOL       , 0x00       , 0,  0,   0, NULL, NULL },
                { DO_FUNC       , 0              , 0          , 0,  0,   0, NULL, fmtEncodeMicroIllustrator },
                { DO_LAST       , 0              , 0          , 0,  0,   0, NULL, NULL },
            }
        },
        NULL
    },

    {
        "cdu", "CDU-Paint (unpacked)", 0x7eef, 10277, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        {
            D64_FMT_MC,
            C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
            C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_BITMAP_RAM  , 0x0000 + 0x111, 0,  0,   0, NULL, NULL },
                { DO_COPY       , DS_SCREEN_RAM  , 0x1f40 + 0x111, 0,  0,   0, NULL, NULL },
                { DO_COPY       , DS_COLOR_RAM   , 0x2328 + 0x111, 0,  0,   0, NULL, NULL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x2710 + 0x111, 0,  0,   0, NULL, NULL },
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL },
            }
        },
        NULL
    },

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

    {
        "sar", "Saracen Paint (unpacked)", 0x7800, 10219, DM_FMT_RDWR,
        fmtProbeSaracenPaint,
        NULL, NULL,
        {
            D64_FMT_MC,
            C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
            C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_SCREEN_RAM  , 0x7800 - 0x7800, 0,  0,   0, NULL, NULL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x7bf0 - 0x7800, 0,  0,   0, NULL, NULL },
                { DO_COPY       , DS_BITMAP_RAM  , 0x7c00 - 0x7800, 0,  0,   0, NULL, NULL },
                { DO_COPY       , DS_COLOR_RAM   , 0x9c00 - 0x7800, 0,  0,   0, NULL, NULL },
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL },
            }
        },
        NULL
    },

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

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

    {
        "a64", "Wigmore Artist 64 (unpacked)", 0x4000, 10242, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        {
            D64_FMT_MC,
            C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
            C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0,  0,   0, NULL, NULL },
                { DO_COPY       , DS_SCREEN_RAM  , 0x2000, 0,  0,   0, NULL, NULL },
                { DO_COPY       , DS_COLOR_RAM   , 0x2400, 0,  0,   0, NULL, NULL },
                { DO_SET_MEM_LO , DS_D020        , 0x27fe, 0,  0,   0, NULL, NULL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x27ff, 0,  0,   0, NULL, NULL },
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL },
            }
        },
        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,
        NULL,
        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,
            C64_SCR_WIDTH   , C64_SCR_HEIGHT,
            C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
            1, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0, 0,   0, NULL, NULL },
                { DO_SET_OP     , DS_SCREEN_RAM  , 0xCF  , 0, 0,   0, NULL, NULL },
                // Default colors used by MM are --^^
                { DO_LAST       , 0              , 0     , 0, 0,   0, NULL, NULL },
            }
        },
        NULL
    },

    {
        "hir", "Plain hires (unpacked)", 0x2000, 8002, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        {
            D64_FMT_HIRES,
            C64_SCR_WIDTH   , C64_SCR_HEIGHT,
            C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
            1, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0, 0,   0, NULL, NULL },
                { DO_SET_OP     , DS_SCREEN_RAM  , 0xF0  , 0, 0,   0, NULL, NULL },
                { DO_LAST       , 0              , 0     , 0, 0,   0, NULL, NULL },
            }
        },
        NULL
    },

    {
        "gih", "Gigapaint hires (unpacked)", 0x6000, 8002, DM_FMT_RDWR,
        fmtProbeGigapaintHires,
        NULL, NULL,
        {
            D64_FMT_HIRES,
            C64_SCR_WIDTH   , C64_SCR_HEIGHT,
            C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
            1, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0, 0,   0, NULL, NULL },
                { DO_SET_OP     , DS_SCREEN_RAM  , 0x0F  , 0, 0,   0, NULL, NULL },
                { DO_LAST       , 0              , 0     , 0, 0,   0, NULL, NULL },
            }
        },
        NULL
    },

    {
        "bfli", "Pu-239 Big FLI/BFLI (unpacked)", 0x3bff, 33795, DM_FMT_RD,
        NULL,
        NULL, NULL,
        {
            D64_FMT_MC | D64_FMT_FLI,
            C64_SCR_WIDTH / 2, C64_SCR_HEIGHT * 2,
            C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            fmtGetPixelBFLI,
            {
                { DO_COPY       , DS_COLOR_RAM   , 0x0001, 0,  0x400 ,   0, NULL, NULL },
                DEF_SCREEN_RAMS_8(0x0401, 0, 0x400, 0),
                { DO_COPY       , DS_BITMAP_RAM  , 0x2401, 0,  0x2000,   0, NULL, NULL },
                DEF_SCREEN_RAMS_8(0x4401, 8, 0x400, 0x400),
                { DO_COPY       , DS_BITMAP_RAM  , 0x6401, 1,  0x2000,   0, NULL, NULL },
                { DO_LAST       , 0              , 0     , 0,  0     ,   0, NULL, NULL },
            }
        },
        NULL
    },

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

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

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

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

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

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

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

    {
        "xx1", "Unknown $2000 format (unpacked)", 0x2000, 10242, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        {
            D64_FMT_MC,
            C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
            C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
            2, 1,
            NULL, NULL,
            NULL,
            {
                { DO_COPY      , DS_BITMAP_RAM  , 0x0000, 0,  0,   0, NULL, NULL },
                { DO_COPY      , DS_SCREEN_RAM  , 0x2000, 0,  0,   0, NULL, NULL },
                { DO_COPY      , DS_COLOR_RAM   , 0x2400, 0,  0,   0, NULL, NULL },
                { DO_SET_OP    , DS_BGCOL       , 0x00  , 0,  0,   0, NULL, NULL },
                { DO_LAST      , 0              , 0     , 0,  0,   0, NULL, NULL },
            }
        },
        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 },
                { DO_COPY      , DS_SCREEN_RAM  , XX2_BSIZE, 0,  XX2_SIZE,   0, NULL, NULL },
                { DO_COPY      , DS_COLOR_RAM   , XX2_BSIZE + XX2_SIZE, 0,  XX2_SIZE,   0, NULL, NULL },
                { DO_SET_OP    , DS_BGCOL       , 11    , 0,  0,   0, NULL, NULL },
                { DO_LAST      , 0              , 0     , 0,  0,   0, NULL, NULL },
            }
        },
        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 (unpacked)", 0x4000, 0, DM_FMT_RDWR,
        fmtProbeGunPaint,
        NULL, NULL,
        {
            D64_FMT_MC | D64_FMT_FLI | D64_FMT_ILACE,
            C64_SCR_WIDTH, C64_SCR_HEIGHT,
            C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
            1, 1,
            NULL, NULL,
            fmtGetPixelFunPaint2, // The format is essentially same as FP2
            {
                DEF_SCREEN_RAMS_8(0x0000, 0, 0x400, 0),
                { DO_COPY       , DS_BITMAP_RAM  , 0x2000, 0,  0  , 0, NULL, NULL },
                { DO_COPY       , DS_EXTRA_DATA  , 0x3f4f, 0,  177, 0, NULL, NULL },
                { DO_COPY       , DS_COLOR_RAM   , 0x4000, 0,  0  , 0, NULL, NULL },
                DEF_SCREEN_RAMS_8(0x4400, 8, 0x400, 0),
                { DO_COPY       , DS_BITMAP_RAM  , 0x6400, 1,  0  , 0, NULL, NULL },
                // 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 },
                { DO_FUNC       , 0              , 0x2742, 0,  1  , 0, fmtTruePaintGetLaceType, NULL },
                { DO_FUNC       , 0              , fmtGunPaintMagicOffs, 0, fmtGunPaintMagicLen, 0, NULL, fmtEncodeGunPaint },
                { DO_LAST       , 0              , 0     , 0,  0  , 0, NULL, NULL },
            }
        },
        NULL
    },

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

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

    {
        "chid", "Crest Hires FLI Designer (unpacked)", 0x4000, 16386, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        {
            D64_FMT_HIRES | D64_FMT_FLI,
            C64_SCR_WIDTH, C64_SCR_HEIGHT,    // Actually 296 x 112 (=14*8)
            C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
            1, 1,
            NULL, NULL,
            fmtGetPixelCrestHIFLIorCDHM,
            {
                { DO_COPY       , DS_BITMAP_RAM  , 0x0000, 0,  0,   0, NULL, NULL },
                DEF_SCREEN_RAMS_8(0x2000, 0, 0x400, 0),
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL },
            }
        },
        NULL
    },

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

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