view tools/lib64fmts.c @ 2576:812b16ee49db

I had been living under apparent false impression that "realfft.c" on which the FFT implementation in DMLIB was basically copied from was released in public domain at some point, but it could very well be that it never was. Correct license is (or seems to be) GNU GPL. Thus I removing the code from DMLIB, and profusely apologize to the author, Philip Van Baren. It was never my intention to distribute code based on his original work under a more liberal license than originally intended.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 11 Mar 2022 16:32:50 +0200
parents 21d296803fac
children 317d2f4b322c
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-2021 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))


// Basic probe, but return MAX score for this format
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 fmtSupeRes_MagicID_1[] =
{
    0x40, 0x5c, 0x2a,
};


static int fmtProbeSupeRes(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    if (buf->len > 12 &&
        DM_MEMCMP_SIZE(buf->data, fmtSupeRes_MagicID_1) == 0)
    {
        if (buf->data[3] == fmt->extra)
            return DM_PROBE_SCORE_MAX;
    }

    return DM_PROBE_SCORE_FALSE;
}


typedef struct
{
    DMGrowBuf src;
    Uint8 *dstBuf;
    size_t dstSize;

    Uint8 dbyte, repcount;

    size_t offs, end_offs;
} DMSupeResDecCtx;


static int fmtSupeResGetByte(DMSupeResDecCtx *ctx, Uint8 *data, const int mode)
{
    if (!dmGrowBufGetU8(&ctx->src, data))
    {
        return dmError(DMERR_INVALID_DATA,
            "SupeRes: Out of input data (N=%d).\n",
            mode);
    }
    else
        return DMERR_OK;
}


static int fmtDecodeSupeRes24_25(DMSupeResDecCtx *ctx, Uint8 tmp)
{
    if (tmp == 0x24)
    {
        ctx->dbyte = 0xff;
    }
    else
    {
        if (tmp == 0x25)
        {
            int res;
            if ((res = fmtSupeResGetByte(ctx, &tmp, 9)) != DMERR_OK)
                return res;

            tmp = (0x26 + tmp) & 0xff;
        }

        ctx->dbyte = tmp - 0x26;
    }

    return DMERR_OK;
}


static int fmtDecodeSupeResByte(DMSupeResDecCtx *ctx)
{
    Uint8 tmp;
    int res;

    do
    {
        if ((res = fmtSupeResGetByte(ctx, &tmp, 1)) != DMERR_OK)
            break;

        if (tmp == 0x21)
        {
            // ????
            ctx->offs = 0xffff;
            break;
        }
        else
        if (tmp == 0x20)
        {
            if ((res = fmtSupeResGetByte(ctx, &tmp, 2)) != DMERR_OK)
                break;

            ctx->repcount = tmp - 0x26;

            if ((res = fmtSupeResGetByte(ctx, &tmp, 3)) != DMERR_OK ||
                (res = fmtDecodeSupeRes24_25(ctx, tmp)) != DMERR_OK)
                break;

            for (int cnt = 0; cnt < ctx->repcount; cnt++)
            {
                ctx->dstBuf[ctx->offs++] = ctx->dbyte;
                if (ctx->offs >= ctx->end_offs)
                    break;
            }

            if (ctx->offs >= ctx->end_offs)
            {
                ctx->offs--;
                break;
            }
        }
        else
        {
            res = fmtDecodeSupeRes24_25(ctx, tmp);
            break;
        }
    } while (1);

    return res;
}


static int fmtDecodeSupeResSection(DMSupeResDecCtx *ctx, const size_t offs, const size_t size)
{
    int res = DMERR_OK;

    ctx->offs = offs;
    ctx->end_offs = offs + size;

    do
    {
        if ((res = fmtDecodeSupeResByte(ctx)) != DMERR_OK)
            goto out;

        if (ctx->offs < ctx->end_offs)
            ctx->dstBuf[ctx->offs++] = ctx->dbyte;

    } while (ctx->offs < ctx->end_offs);

out:
    return res;
}


static int fmtDecodeSupeRes(DMC64Image *img, const DMGrowBuf *psrc, const DMC64ImageFormat *fmt)
{
    DMSupeResDecCtx ctx;
    DMGrowBuf tmp;
    int res;

    memset(&ctx, 0, sizeof(ctx));
    ctx.dstSize = 0x3000;

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

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

    switch (fmt->extra)
    {
        case 0x23:
        case 0x25:
            if ((res = fmtDecodeSupeResSection(&ctx, 0x0000, 0x03e8)) != DMERR_OK ||
                (res = fmtDecodeSupeResSection(&ctx, 0x0400, 0x1f40)) != DMERR_OK)
                goto out;
            break;

        case 0x24:
        case 0x26:
            if ((res = fmtDecodeSupeResByte   (&ctx)) != DMERR_OK)
                goto out;

            ctx.dstBuf[0x2710] = ctx.dbyte;

            if ((res = fmtDecodeSupeResSection(&ctx, 0x1f40, 0x03e8)) != DMERR_OK ||
                (res = fmtDecodeSupeResSection(&ctx, 0x2328, 0x03e8)) != DMERR_OK ||
                (res = fmtDecodeSupeResSection(&ctx, 0x0000, 0x1f40)) != DMERR_OK)
                goto out;

            break;
    }

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

out:
    dmFree(ctx.dstBuf);
    return res;
}


typedef struct
{
    DMGrowBuf *buf;

    int cnt1, cnt2, dbyte;
} DMSupeResEncCtx;


static BOOL fmtEncodeSupeResRun(DMSupeResEncCtx *ctx)
{
    if (ctx->cnt2 == 255)
        ctx->cnt2 = 0x24;
    else
    if (ctx->cnt2 > 216)
    {
        if (!dmGrowBufPutU8(ctx->buf, 0x25))
            return FALSE;
    }
    else
        ctx->cnt2 += 0x26;

    if (!dmGrowBufPutU8(ctx->buf, ctx->cnt2))
        return FALSE;

    ctx->cnt1 = ctx->cnt2 = 0;

    return TRUE;
}


static BOOL fmtEncodeSupeResFlush(DMSupeResEncCtx *ctx)
{
    ctx->cnt1 += 0x26;

    if (!dmGrowBufPutU8(ctx->buf, 0x20) ||
        !dmGrowBufPutU8(ctx->buf, ctx->cnt1))
        return FALSE;

    return fmtEncodeSupeResRun(ctx);
}


static BOOL fmtEncodeSupeResSection(DMSupeResEncCtx *ctx, const DMC64MemBlock *blk)
{
    for (size_t offs = 0; offs < blk->size; offs++)
    {
        ctx->dbyte = blk->data[offs];

        if ((ctx->cnt1 == 0 && offs + 1 < blk->size && ctx->dbyte == blk->data[offs + 1]) ||
            (ctx->cnt1 != 0 && ctx->dbyte == ctx->cnt2))
        {
            ctx->cnt1++;
            ctx->cnt2 = ctx->dbyte;

            if (ctx->cnt1 >= 215 &&
                !fmtEncodeSupeResFlush(ctx))
                return FALSE;

            continue;
        }
        else
        if (ctx->cnt1 != 0 && ctx->dbyte != ctx->cnt2)
        {
            if (!fmtEncodeSupeResFlush(ctx))
                return FALSE;
        }

        ctx->cnt2 = ctx->dbyte;

        if (!fmtEncodeSupeResRun(ctx))
            return FALSE;
    }

    if (ctx->cnt1 > 0)
        return fmtEncodeSupeResFlush(ctx);

    return TRUE;
}


static int fmtEncodeSupeRes(DMGrowBuf *buf, const DMC64Image *img, const DMC64ImageFormat *fmt)
{
    DMSupeResEncCtx ctx;
    BOOL bres = FALSE;

    // Output magic header and data type
    if (!dmGrowBufPut(buf, fmtSupeRes_MagicID_1, sizeof(fmtSupeRes_MagicID_1)) ||
        !dmGrowBufPutU8(buf, fmt->extra))
    {
        return dmError(DMERR_MALLOC,
            "Error outputting SupeRes magic header.\n");
    }

    memset(&ctx, 0, sizeof(ctx));
    ctx.buf = buf;

    switch (fmt->extra)
    {
        case 0x23:
        case 0x25:
            bres =
                fmtEncodeSupeResSection(&ctx, &img->screen[0]) &&
                fmtEncodeSupeResSection(&ctx, &img->bitmap[0]);
            break;

        case 0x24:
        case 0x26:
            ctx.cnt2 = img->bgcolor;
            bres =
                fmtEncodeSupeResRun(&ctx) &&
                fmtEncodeSupeResSection(&ctx, &img->screen[0]) &&
                fmtEncodeSupeResSection(&ctx, &img->color[0]) &&
                fmtEncodeSupeResSection(&ctx, &img->bitmap[0]);
            break;
    }

    return bres ? DMERR_OK : DMERR_MALLOC;
}


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 fmtDecodeHiresPETSCIICharsetData(const DMC64EncDecOp *op, DMC64Image *img,
    const DMGrowBuf *buf, const DMC64ImageCommonFormat *fmt)
{
    (void) fmt;
    Uint8 val;

    switch (buf->data[op->offs])
    {
        case 0x14: val = 0; break; // upper case
        case 0x17: val = 1; break; // lower case
        default:
            return DMERR_INVALID_DATA;
    }

    img->extraInfo[D64_EI_CHAR_CASE] = val;
    img->extraInfo[D64_EI_MODE] = D64_FMT_HIRES | D64_FMT_CHAR;
    img->extraInfo[D64_EI_CHAR_CUSTOM] = 0;

    return DMERR_OK;
}


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

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

    buf->data[op->offs + 2] = val;

    return DMERR_OK;
}


static int fmtEncodeMarqPETSCIIData(const DMC64EncDecOp *op, DMGrowBuf *buf,
    const DMC64Image *img, const DMC64ImageCommonFormat *fmt)
{
    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 fmtEncodeHiresPETSCIICharsetData(op, buf, img, fmt);
}


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 fmtDecodePetsciiKrisszHuData(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 fmtDecodeMarqOldData(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 const Uint8 fmtCocaPETSCII_ID1[] =
{
    0x01, 0x08, 0x0b, 0x08, 0x0a, 0x00, 0x9e, 0x32, 0x30, 0x36,
    0x31, 0x00, 0x00, 0x00, 0x78, 0xa2, 0x00, 0xbd, 0x5a, 0x08,
    0x9d, 0x00, 0x04, 0xbd, 0x5a, 0x09, 0x9d, 0x00, 0x05, 0xbd,
    0x5a, 0x0a, 0x9d, 0x00, 0x06, 0xbd, 0x5a, 0x0b, 0x9d, 0x00,
    0x07, 0xbd, 0x5a, 0x0c, 0x9d, 0x00, 0xd8, 0xbd, 0x5a, 0x0d,
    0x9d, 0x00, 0xd9, 0xbd, 0x5a, 0x0e, 0x9d, 0x00, 0xda, 0xbd,
    0x5a, 0x0f, 0x9d, 0x00, 0xdb, 0xe8, 0xd0, 0xcd, 0xad, 0x42,
    0x0c, 0x8d, 0x20, 0xd0, 0xad, 0x43, 0x0c, 0x8d, 0x21, 0xd0,
    0xad, 0x44, 0x0c, 0x8d, 0x18, 0xd0, 0x4c, 0x55, 0x08
};


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

    if (buf->len == 2115 &&
        DM_MEMCMP_SIZE(buf->data, fmtCocaPETSCII_ID1) == 0 &&
        buf->data[0x0c44 - 0x0801 + 2] == 0x14)
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


static int fmtEncodeCocaPETSCIIData(const DMC64EncDecOp *op, DMGrowBuf *buf,
    const DMC64Image *img, const DMC64ImageCommonFormat *fmt)
{
    memcpy(buf->data,
        fmtCocaPETSCII_ID1, sizeof(fmtCocaPETSCII_ID1));

    return fmtEncodeHiresPETSCIICharsetData(op, buf, img, fmt);
}


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->extra == 0xfe &&
            buf->len != 10242) // Attempt to avoid misprobes of "Rainbow Painter (unpacked)"
            return DM_PROBE_SCORE_MAX;

        // Unpacked variant
        if (fmt->extra != 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->extra;

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

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

    // 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(DMC64ScanLine *scan,
    const DMC64Image *img, const int rasterX, const int rasterY)
{
    DM_C64_GENERIC_SC_PIXEL_DEFS(img)
    (void) vshift;

    return dmC64GetGenericMCPixel(scan->col, img,
        bmoffs, scroffs,
        6 - (rasterX & 6), 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;

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

    // 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(DMC64ScanLine *scan,
    const DMC64Image *img, const int rasterX, const int rasterY)
{
    DM_C64_GENERIC_MC_PIXEL_DEFS(img)

    return dmC64GetGenericMCPixel(scan->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

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

    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(DMC64ScanLine *scan,
    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(scan->col, img,
        bmoffs, scroffs,
        vshift, rasterY & 7,
        0, 0, bgcol);
}


static int fmtGetPixelTruePaint(DMC64ScanLine *scan,
    const DMC64Image *img, const int rasterX, const int rasterY)
{
    DM_C64_GENERIC_SC_PIXEL_DEFS(img)
    (void) vshift;

    return dmC64GetGenericMCPixel(scan->col, img,
        bmoffs, scroffs,
        6 - (rasterX & 6), 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(DMC64ScanLine *scan,
    const DMC64Image *img, const int rasterX, const int rasterY)
{
    DM_C64_GENERIC_MC_PIXEL_DEFS(img)
    const int vbank = rasterY & 7;
    Uint8 color1, color2, bgcol = img->bgcolor;
    int res;

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

    *(scan->col) = (color1 * D64_NCOLORS) + color2;
    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);
}


static const Uint8 fmtFormatXX3_MagicID_1[] =
{
    0x01, 0x08, 0x0B, 0x08,  0xF0, 0x02, 0x9E, 0x32,
    0x30, 0x36, 0x31, 0x00,  0x00, 0x00, 0xA9, 0x14,
    0x8D, 0x18, 0xD0, 0xA2,  0x00, 0xA9, 0x20, 0x9D,
    0x00, 0x04, 0x9D, 0x00,  0x05, 0x9D, 0x00, 0x06,
};

static int fmtProbeFormatXX3(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    if (buf->len == fmt->size &&
        DM_MEMCMP_SIZE(buf->data, fmtFormatXX3_MagicID_1) == 0
        )
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


static const Uint8 fmtFormatXX4_MagicID_1[] =
{
    0x00, 0x1f, 0x78, 0xa9, 0x3b, 0x8d, 0x11, 0xd0,
    0xa9, 0x18, 0x8d, 0x16, 0xd0, 0xa9, 0x18, 0x8d,
};

static int fmtProbeFormatXX4(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    if (buf->len >= fmt->size &&
        DM_MEMCMP_SIZE(buf->data, fmtFormatXX4_MagicID_1) == 0
        )
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


static const Uint8 fmtFormatXX5_MagicID_1[] =
{
    0x00, 0x10, 0xa9, 0x01, 0x8d, 0x86, 0x02, 0x20,
    0x44, 0xe5, 0xa9, 0x16, 0x8d, 0x18, 0xd0, 0xa2,
};

static int fmtProbeFormatXX5(const DMGrowBuf *buf, const DMC64ImageFormat *fmt)
{
    if (buf->len >= fmt->size &&
        DM_MEMCMP_SIZE(buf->data, fmtFormatXX5_MagicID_1) == 0
        )
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


static int fmtGetPixelXX5(DMC64ScanLine *scan,
    const DMC64Image *img, const int rasterX, const int rasterY)
{
    DM_C64_GENERIC_SC_PIXEL_DEFS(img)

    (void) vshift;

    return dmC64GetGenericMCPixel(scan->col, img,
        bmoffs, scroffs,
        6 - (rasterX & 6),
        (rasterY & 7),
        rasterX & 1, 0, img->bgcolor);
}


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

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

    // 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(DMC64ScanLine *scan,
    const DMC64Image *img, const int rasterX, const int rasterY)
{
    DM_C64_GENERIC_SC_PIXEL_DEFS(img)
    const int bitmap = rasterX & 1;
    Uint8 bgcol = (unsigned) rasterY < img->extraData[0].size ?
        img->extraData[0].data[rasterY] : img->bgcolor;

    (void) vshift;

    return dmC64GetGenericMCPixel(scan->col, img,
        bmoffs, scroffs,
        6 - (rasterX & 6),
        yb + (bitmap * 8),
        bitmap, 0, bgcol);
}


static int fmtGetPixelBFLI(DMC64ScanLine *scan,
    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(scan->col, img,
        bmoffs & 0x1fff, scroffs & 0x3ff,
        vshift, vbank,
        vbb, 0, img->bgcolor);
}


static int fmtGetPixelPentelPaint(DMC64ScanLine *scan,
    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;

    *(scan->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 %" DM_PRIu_SIZE_T
                " bytes (should be %" DM_PRIu_SIZE_T ").\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 %" DM_PRIu_SIZE_T " 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(DMC64ScanLine *scan,
    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)
    {
        *(scan->col) = img->extraData[15].data[cindex];
        return DMERR_OK;
    }

    return -1;
}


static int fmtGetPixelCrestSHFLI(DMC64ScanLine *scan,
    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)
    {
        *(scan->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(scan, img, spr_index1, 0, spr_xd, spr_yd, mask)) == DMERR_OK ||
            res != -1)
            return res;

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

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

    return DMERR_OK;
}


static int fmtGetPixelHCB(DMC64ScanLine *scan,
    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(scan->col, img,
        bmoffs, scroffs,
        vshift, vbank,
        0, vbank, bgcol);
}


static int fmtGetPixelCrestHIFLIorCDHM(DMC64ScanLine *scan,
    const DMC64Image *img, const int rasterX, const int rasterY)
{
    DM_C64_GENERIC_SC_PIXEL_DEFS(img)

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


static int fmtGetPixelECI(DMC64ScanLine *scan,
    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;

    *(scan->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;

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

    // 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(dop, dtype, start, oindex, bindex, osize, bsize, oflags) \
    { (dop), (dtype), (start) + ((osize) * (oindex)), (bindex), (bsize), 0, NULL, NULL, (oflags) }

#define DEF_REPEAT_BLOCK_8(dop, dtype, start, sindex, osize, bsize, oflags) \
    DEF_REPEAT_BLOCK((dop), (dtype), (start), 0, ((sindex) + 0), (osize), (bsize), (oflags)), \
    DEF_REPEAT_BLOCK((dop), (dtype), (start), 1, ((sindex) + 1), (osize), (bsize), (oflags)), \
    DEF_REPEAT_BLOCK((dop), (dtype), (start), 2, ((sindex) + 2), (osize), (bsize), (oflags)), \
    DEF_REPEAT_BLOCK((dop), (dtype), (start), 3, ((sindex) + 3), (osize), (bsize), (oflags)), \
    DEF_REPEAT_BLOCK((dop), (dtype), (start), 4, ((sindex) + 4), (osize), (bsize), (oflags)), \
    DEF_REPEAT_BLOCK((dop), (dtype), (start), 5, ((sindex) + 5), (osize), (bsize), (oflags)), \
    DEF_REPEAT_BLOCK((dop), (dtype), (start), 6, ((sindex) + 6), (osize), (bsize), (oflags)), \
    DEF_REPEAT_BLOCK((dop), (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(DO_COPY, 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(DO_COPY, 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(DO_COPY, 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(DO_COPY, 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(DO_COPY, 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(DO_COPY, 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(DO_COPY, 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_FMT_SPRITES,
        D64_SCR_WIDTH, D64_SCR_HEIGHT,
        D64_SCR_CH_WIDTH, 24,
        1, 1,
        NULL, NULL,
        fmtGetPixelCrestSHFLI,
        {
            DEF_REPEAT_BLOCK_8(DO_COPY, DS_SCREEN_RAM,  0x0000, 0  , 0x0400,   0x0400, DF_NORMAL),
            DEF_REPEAT_BLOCK_8(DO_COPY, 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, 0, DM_FMT_RDWR,
        fmtProbeDrazPaint20Packed,
        fmtDecodeDrazPaintPacked, fmtEncodeDrazPaintPacked,
        { 0 }, &dmC64CommonFormats[4]
    },

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

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

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

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

    {
        "vid", "Vidcom 64 (unpacked)", 0x5800, 10050, 0, 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, 0, 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, 0, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        { 0 }, &dmC64CommonFormats[6]
    },

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

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

    {
        "klp", "Koala Painter (packed)", 0x6000, 0, 0xfe, DM_FMT_RDWR,
        fmtProbeKoalaPainterPacked,
        fmtDecodeStaticRLEMarkerMode2, fmtEncodeStaticRLEMarkerMode2,
        { 0 }, &dmC64CommonFormats[0]
    },

    {
        "aas", "Advanced Art Studio (unpacked)", 0x2000, 10018, 0, 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, 0, 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, 0, 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, 0, 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, 0, 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, 0, 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, 0, 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, 0, 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, 0, 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, 0, DM_FMT_RDWR,
        fmtProbeAmicaPaintPacked,
        fmtDecodeAmicaPaintPacked, fmtEncodeAmicaPaintPacked,
        { 0 }, &dmC64CommonFormats[0]
    },

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

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

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

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

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

    {
        "jj", "Doodle (packed)", 0x5c00, 0, 0xfe, DM_FMT_RDWR,
        fmtProbeDoodle,
        fmtDecodeStaticRLEMarkerMode2, fmtEncodeStaticRLEMarkerMode2,
        { 0 }, &dmC64CommonFormats[10]
    },

    {
        "mon", "Monomagic (unpacked)", 0x2000, 8194, 0, 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, 0, 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 [mono] (unpacked)", 0x6000, 8002, 0, 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
    },

    {
        "gic", "Gigapaint hires [color] (unpacked)", 0x6000, 9002, 0, 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_COPY       , DS_SCREEN_RAM  , 0x1f40, 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, 0, 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(DO_COPY, 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(DO_COPY, 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, 0, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        { 0 }, &dmC64CommonFormats[1]
    },

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

    {
        "dfed", "Dolphins FLI Editor v3.2 (unpacked)", 0x3b00, 17665, 0, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        { 0 }, &dmC64CommonFormats[1]
    },

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

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

    {
        "flnt", "Flinterlazer 1.0 (unpacked)", 0x284e, 38812, 0, 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(DO_COPY, 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(DO_COPY, DS_SCREEN_RAM , 0xA000 - 0x284e, 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_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, 0, DM_FMT_RDWR,
        NULL,
        NULL, NULL,
        { 0 }, &dmC64CommonFormats[7]
    },

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

    {
        "fpt", "Face Painter (unpacked)", 0x4000, 10004, 0, 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, 0, 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, 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
    },

    {
        "xx3", "Unknown $0801 format (viewer) (unpacked)", 0x0801, 10500, 0, DM_FMT_RD,
        fmtProbeFormatXX3,
        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  , 0x09f2 - 0x0801, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_SCREEN_RAM  , 0x2932 - 0x0801, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_D020        , 0x09e6 - 0x0801, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x09e7 - 0x0801, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_COLOR_RAM   , 0x2d1a - 0x0801, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_LAST       , 0              , 0     , 0,  0,   0, NULL, NULL, DF_NORMAL },
            },
        },
        NULL
    },

    {
        "xx4", "Unknown $1f00 format (unpacked)", 0x1f00, 10260, 0, DM_FMT_RD,
        fmtProbeFormatXX4,
        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  , 0x2000 - 0x1f00, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY      , DS_SCREEN_RAM  , 0x3f40 - 0x1f00, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY      , DS_COLOR_RAM   , 0x4328 - 0x1f00, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_OP    , DS_BGCOL       , 0x4710 - 0x1f00, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_OP    , DS_D020        , 0x4711 - 0x1f00, 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, 0, DM_FMT_RDWR,
        fmtProbeFunPaint2,
        fmtDecodeFunPaint2, fmtEncodeFunPaint2Unpacked,
        { 0 }, &dmC64CommonFormats[3]
    },

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

    {
        "gun", "GunPaint 1.1 (unpacked)", 0x4000, 33603, 0, 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(DO_COPY, 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(DO_COPY, 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, 0, 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, 0, DM_FMT_RD | DM_FMT_BROKEN,
        NULL,
        NULL, NULL,
        {
            D64_FMT_HIRES | D64_FMT_FLI | D64_FMT_SPRITES,
            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, 0, 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(DO_COPY, 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, 0, DM_FMT_RDWR,
        fmtProbeCrestSHFLI,
        NULL, NULL,
        { 0 }, &dmC64CommonFormats[11]
    },

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

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

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

    {
        "mrqp", "Marq's PETSCII editor (new format) (unpacked)", 0x0801, 0, 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              , 20 - 2, 0,  0,   0, NULL, fmtEncodeMarqPETSCIIData, 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_FUNC       , 0              , 20 - 2, 0,  0,   0, fmtDecodeHiresPETSCIICharsetData, NULL, DF_NORMAL },

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

    {
        "mqpo", "Marq's PETSCII editor (old format) (unpacked)", 0x0801, 2499, 0, 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 },

                { 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, fmtDecodeMarqOldData, NULL, DF_NORMAL },

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

    {
        "pkhu", "petscii.krissz.hu editor (unpacked)", 0x0801, 0, 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, fmtDecodePetsciiKrisszHuData, NULL, DF_NORMAL },

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

    {
        "poca", "Petscii Coca editor (unpacked)", 0x0801, 0, 0, DM_FMT_RDWR,
        fmtProbeCocaPETSCII,
        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_FUNC       , 0              , 0x0c44 - 0x0801, 0,  0,   0, NULL, fmtEncodeCocaPETSCIIData, DF_NORMAL },
                { DO_COPY       , DS_SCREEN_RAM  , 0x085a - 0x0801, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_COLOR_RAM   , 0x0c5a - 0x0801, 0,  0,   0, NULL, NULL, DF_NORMAL },

                { DO_SET_MEM_LO , DS_D020        , 0x0c42 - 0x0801, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x0c43 - 0x0801, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_FUNC       , 0              , 0x0c44 - 0x0801, 0,  0,   0, fmtDecodeHiresPETSCIICharsetData, NULL, DF_NORMAL },

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

    {
        "acpe", "Abyss Connection PETSCII-Editor 4.61 (unpacked)", 0x3000, 2026, 0, DM_FMT_RDWR,
        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_FUNC       , 0              , 0x33ea - 0x3000, 0,  0,   0, NULL, fmtEncodeHiresPETSCIICharsetData, DF_NORMAL },
                { DO_COPY       , DS_SCREEN_RAM  , 0x0000         , 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_COLOR_RAM   , 0x0400         , 0,  0,   0, NULL, NULL, DF_NORMAL },

                { DO_SET_MEM_LO , DS_D020        , 0x33e8 - 0x3000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_BGCOL       , 0x33e9 - 0x3000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_FUNC       , 0              , 0x33ea - 0x3000, 0,  0,   0, fmtDecodeHiresPETSCIICharsetData, NULL, DF_NORMAL },

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

    {
        "suphi1", "SupeRes hires [clear] (packed)", -1, 0, 0x23, DM_FMT_RDWR,
        fmtProbeSupeRes,
        fmtDecodeSupeRes, fmtEncodeSupeRes,
        { 0 }, &dmC64CommonFormats[10]
    },

    {
        "suphi2", "SupeRes hires [no-clear] (packed)", -1, 0, 0x25, DM_FMT_RDWR,
        fmtProbeSupeRes,
        fmtDecodeSupeRes, fmtEncodeSupeRes,
        { 0 }, &dmC64CommonFormats[10]
    },

    {
        "supmc1", "SupeRes multicolor [clear] (packed)", -1, 0, 0x24, DM_FMT_RDWR,
        fmtProbeSupeRes,
        fmtDecodeSupeRes, fmtEncodeSupeRes,
        { 0 }, &dmC64CommonFormats[0]
    },

    {
        "supmc2", "SupeRes multicolor [no-clear] (packed)", -1, 0, 0x26, DM_FMT_RDWR,
        fmtProbeSupeRes,
        fmtDecodeSupeRes, fmtEncodeSupeRes,
        { 0 }, &dmC64CommonFormats[0]
    },

    {
        "xx5", "Unknown $1000 format (unpacked)", 0x1000, 45000, 0, DM_FMT_RD | DM_FMT_BROKEN,
        fmtProbeFormatXX5,
        NULL, NULL,
        {
            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,
            fmtGetPixelXX5,
            {
                DEF_REPEAT_BLOCK_8(DO_COPY, DS_SCREEN_RAM , 0x4000 - 0x1000, 0, 0x400, 0, DF_NORMAL),
                DEF_REPEAT_BLOCK_8(DO_COPY, DS_SCREEN_RAM , 0x8000 - 0x1000, 8, 0x400, 0, DF_NORMAL),

                { DO_COPY       , DS_BITMAP_RAM  , 0x6000 - 0x1000, 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_COPY       , DS_BITMAP_RAM  , 0xa400 - 0x1000, 1,  0,   0, NULL, NULL, DF_NORMAL },

                { DO_COPY       , DS_COLOR_RAM   , 0x8000 - 0x1000, 0,  0,   0, NULL, NULL, DF_NORMAL },

                // BG/FG always 0
                { DO_SET_MEM_LO , DS_BGCOL       , 0              , 0,  0,   0, NULL, NULL, DF_NORMAL },
                { DO_SET_MEM_LO , DS_D020        , 0              , 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_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
    },

};

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