view tools/lib64fmts.c @ 1707:a0986cfd6f9d

More consistently use DMGrowBuf in the lib64gfx APIs, and implement "backwards" RLE decoding and encoding (optionally regards input/output). Not tested very much yet, there may be bugs.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 05 Jun 2018 21:58:10 +0300
parents 1036b0dcccb5
children 4fd94bf558b3
line wrap: on
line source

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


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


static int fmtProbeKoalaPaintPacked(const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    // Attempt to prevent misprobes of unpacked Koala and Run Paint
    if (len > 30 &&
        len != 10006 &&
        len != 10003 &&
        dmCompareAddr16(buf, 0, fmt->addr))
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


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

    cfg.type        = DM_COMP_RLE_MARKER;
    cfg.flags       = DM_RLE_BYTE_RUNS | DM_RLE_ORDER_2;
    cfg.rleMarkerB  = 0xfe;

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

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

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


static int fmtEncodeKoalaPaintPacked(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.type         = DM_COMP_RLE_MARKER;
    cfg.flags        = DM_RLE_BYTE_RUNS | DM_RLE_ORDER_2;
    cfg.rleMarkerB   = 0xfe;
    cfg.rleMinCountB = 3;
    cfg.rleMaxCountB = 255;
    res = dmEncodeGenericRLE(buf, &tmp, &cfg);

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


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

    return DM_PROBE_SCORE_FALSE;
}


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

    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,
        dmGrowBufCreateFromOffs(&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->type & D64_FMT_ILACE) ? "DRAZLACE! 1.0" : "DRAZPAINT 2.0";

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

    // Analyze and setup RLE
    cfg.type         = DM_COMP_RLE_MARKER;
    cfg.flags        = DM_RLE_BYTE_RUNS | DM_RLE_ORDER_1;
    cfg.rleMinCountB = 3;
    cfg.rleMaxCountB = 255;

    dmGenericRLEAnalyze(&tmp, &cfg);

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

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

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


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

    return DM_PROBE_SCORE_FALSE;
}


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


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


static const char *fmtBDP5MagicID = "BDP 5.00";

static int fmtProbeBDP5Packed(const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    if (len > 20 &&
        dmCompareAddr16(buf, 0, fmt->addr) &&
        memcmp(buf + 2, fmtBDP5MagicID, strlen(fmtBDP5MagicID)) == 0)
        return DM_PROBE_SCORE_MAX;

    return DM_PROBE_SCORE_FALSE;
}


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

    cfg.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];

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

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

out:
    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.type         = DM_COMP_RLE_MARKER;
    cfg.flags        = DM_RLE_BYTE_RUNS | DM_RLE_WORD_RUNS | DM_RLE_ORDER_1;
    cfg.rleMinCountB = 3;
    cfg.rleMaxCountB = 255;
    cfg.rleMinCountW = 256;
    cfg.rleMaxCountW = 1024;

    dmGenericRLEAnalyze(&mem, &cfg);

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

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

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


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

    return DM_PROBE_SCORE_FALSE;
}


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

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

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

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

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

    return DM_PROBE_SCORE_FALSE;
}


static int fmtDecodeAmicaPaintPacked(DMC64Image *img, const 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 ((res = dmGrowBufAlloc(&tmp, buf->len + 4, 4)) != DMERR_OK)
        return res;

    memcpy(tmp.data, buf->data, buf->len);
    tmp.len = buf->len + 1;

    // Now do an RLE decode on the enlarged buffer
    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.type         = DM_COMP_RLE_MARKER;
    cfg.flags        = DM_RLE_BYTE_RUNS | DM_RLE_ORDER_1;
    cfg.rleMarkerB   = 0xC2;
    cfg.rleMinCountB = 3;
    cfg.rleMaxCountB = 255;

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

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


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

    return DM_PROBE_SCORE_FALSE;
}


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


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


static int fmtProbeTruePaintPacked(const Uint8 *buf, const size_t len, 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 (len >= 512 && dmCompareAddr16(buf, 0, fmt->addr) &&
        memcmp(buf + 2, magicID, sizeof(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.
//
static BOOL fmtTruePaintGetByte(const Uint8 *src, size_t *srcOffs, Uint8 *data, int *res, const int mode)
{
    if (!dmReverseGetByte(src, srcOffs, data))
    {
        *res = dmError(DMERR_INVALID_DATA,
            "TruePaintRLE: Out of input data (N=%d)\n", mode);
        return FALSE;
    }
    else
        return TRUE;
}


static int fmtDecodeTruePaintPacked(DMC64Image *img, const DMGrowBuf *src, const DMC64ImageFormat *fmt)
{
    int res = DMERR_OK;
    Uint8 *dst = NULL;
    DMGrowBuf dstTmp;
    const Uint8 *codeBook1, *codeBook2;
    size_t
        srcOffs, dstOffs,
        dstLen  = 0x4be8;
        // 1b7e-67e8 decoded by original depacker
        // 1c00-67e8 is the actual area used tho

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

    // Allocate output buffer
    if ((dst = dmMalloc0(dstLen)) == NULL)
    {
        res = dmError(DMERR_MALLOC,
            "Could not allocate memory for temporary decompression buffer.\n");
        goto out;
    }

    // Begin decompression
    srcOffs = src->len;
    dstOffs = dstLen;

    while (srcOffs > 0 && dstOffs > 0)
    {
        Uint8 data;
        int count = 1, scount;
        BOOL found = FALSE;

        if (!fmtTruePaintGetByte(src->data, &srcOffs, &data, &res, -1))
            goto out;

        for (int n = 0; n < 8; n++)
        if (codeBook1[n] == data && !found)
        {
            found = TRUE;
            switch (n)
            {
                case 4: // Y = 4, JTO = $0B
                    if (!fmtTruePaintGetByte(src->data, &srcOffs, &data, &res, n))
                        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 (!fmtTruePaintGetByte(src->data, &srcOffs, &data, &res, n))
                        goto out;
                    break;

                case 2: // Y = 2, JTO = $07
                    if (!fmtTruePaintGetByte(src->data, &srcOffs, &data, &res, n))
                        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;
            }
        }

        for (scount = count; count; count--)
        {
            if (!dmReversePutByte(dst, &dstOffs, data))
            {
                res = dmError(DMERR_INVALID_DATA,
                    "TruePaintRLE: Out of output space for run: %d x $%02x!\n",
                    scount, data);
                goto out;
            }
        }
    }

finish:
    res = dmC64DecodeGenericBMP(img, dmGrowBufCreateFrom(&dstTmp, dst, dstLen), fmt);

out:
    dmFree(dst);
    return res;
}


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


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

    return DM_PROBE_SCORE_FALSE;
}


static int fmtDecodeFormatXX2(DMC64Image *img, const 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.
    size_t nlen = buf->len < XX2_MIN_SIZE ? XX2_MIN_SIZE : buf->len;
    Uint8 *mem = dmMalloc0(nlen);
    if (mem == NULL)
        return DMERR_MALLOC;

    memcpy(mem, buf->data, buf->len);
    res = dmC64DecodeGenericBMP(img, dmGrowBufCreateFrom(&tmp, mem, nlen), fmt);

    dmFree(mem);
    return res;
}


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


static int fmtProbeFunPaint2(const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    if (len > 30 &&
        dmCompareAddr16(buf, 0, fmt->addr) &&
        memcmp(buf + 2, fmtFunPaint2MagicID, strlen(fmtFunPaint2MagicID)) == 0)
        return DM_PROBE_SCORE_MAX;
    else
        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.type        = DM_COMP_RLE_MARKER;
        cfg.flags       = DM_RLE_BYTE_RUNS | DM_RLE_ORDER_1;
        cfg.rleMarkerB  = buf->data[15];

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

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

    return res;
}


static int fmtEncodeFunPaint2Unpacked(DMGrowBuf *buf, const DMC64Image *img, const DMC64ImageFormat *fmt)
{
    // Add the header bits
    if (!dmGrowBufPut(buf, (Uint8 *) fmtFunPaint2MagicID, strlen(fmtFunPaint2MagicID)) ||
        !dmGrowBufPutU8(buf, 0))
        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.type         = DM_COMP_RLE_MARKER;
    cfg.flags        = DM_RLE_BYTE_RUNS | DM_RLE_ORDER_1;
    cfg.rleMinCountB = 3;
    cfg.rleMaxCountB = 255;

    dmGenericRLEAnalyze(&mem, &cfg);

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

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

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


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

    return dmC64GetGenericMCPixel(
        img, bmoffs, scroffs, vshift,
        vbank, vbitmap, 0,
        img->extraData[vb].data[vr] & 15);
}


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

    return dmC64GetGenericMCPixel(
        img, bmoffs, scroffs, vshift,
        vbank, vbitmap, 0,
        img->extraData[vb].data[vr] & 15);
}


static Uint8 fmtGetPixelBFLI(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int vshift, const int bitmap, const int raster)
{
    const int vbb = bmoffs > 0x1fff ? 1 : 0;
    const int vbank = (raster & 7) + (vbb * 8);
    const int vbmoffs = bmoffs & 0x1fff;
    const int vscroffs = scroffs & 0x3ff;

//fprintf(stderr, "bmoffs=%d, scroffs=%d, vshift=%d, vbitmap=%d, raster=%d\n", vbmoffs, vscroffs, vshift, vbb, raster);

    return dmC64GetGenericMCPixel(
        img, vbmoffs, vscroffs, vshift,
        vbank, vbb, 0, img->bgcolor);
}


static Uint8 fmtGetPixelBlackMailFLI(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int shift, const int bitmap, const int raster)
{
    const int vbank = raster & 7;

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


static Uint8 fmtGetPixelFLIDesigner(
    const DMC64Image *img, const int bmoffs, const int scroffs,
    const int shift, const int bitmap, const int raster)
{
    return dmC64GetGenericMCPixel(img, bmoffs, scroffs, shift, raster & 7, bitmap, 0, img->bgcolor);
}


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

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


//
// Helper macros for defining screen ram layouts
// common for FLI type foramts
//
#define DEF_SCREEN_RAM(start, oindex, bindex, osize) { DO_COPY, DS_SCREEN_RAM, (start) + ((osize) * (oindex)), (bindex), 0, NULL, NULL }
#define DEF_SCREEN_RAMS_8(start, sindex, osize) \
    DEF_SCREEN_RAM((start), 0, (sindex + 0), (osize)), \
    DEF_SCREEN_RAM((start), 1, (sindex + 1), (osize)), \
    DEF_SCREEN_RAM((start), 2, (sindex + 2), (osize)), \
    DEF_SCREEN_RAM((start), 3, (sindex + 3), (osize)), \
    DEF_SCREEN_RAM((start), 4, (sindex + 4), (osize)), \
    DEF_SCREEN_RAM((start), 5, (sindex + 5), (osize)), \
    DEF_SCREEN_RAM((start), 6, (sindex + 6), (osize)), \
    DEF_SCREEN_RAM((start), 7, (sindex + 7), (osize))



//
// Many formats actually share memory layout, and there are packed and
// unpacked versions of several formats. We'll reuse these here through
// this common formats ops array, referred from dmC64ImageFormats[]
//
const DMC64EncDecOpList dmC64CommonFormatOps[] =
{
        { // #0: Koala Paint type memory layout
            { DO_COPY     , DS_BITMAP_RAM  , 0x0000, 0,  0, NULL, NULL },
            { DO_COPY     , DS_SCREEN_RAM  , 0x1f40, 0,  0, NULL, NULL },
            { DO_COPY     , DS_COLOR_RAM   , 0x2328, 0,  0, NULL, NULL },
            { DO_SET_MEM  , DS_BGCOL       , 0x2710, 0,  0, NULL, NULL },
            { DO_LAST     , 0              , 0     , 0,  0, NULL, NULL },
        },

        { // #1: Amica Paint, Run Paint, etc. layout
            { DO_COPY     , DS_BITMAP_RAM  , 0x0000, 0,  0, NULL, NULL },
            { DO_COPY     , DS_SCREEN_RAM  , 0x1f40, 0,  0, NULL, NULL },
            { DO_COPY     , DS_COLOR_RAM   , 0x2328, 0,  0, NULL, NULL },
            { DO_SET_MEM  , DS_BGCOL       , 0x2710, 0,  0, NULL, NULL },
            { DO_LAST     , 0              , 0     , 0,  0, NULL, NULL },
        },

        { // #2: Art Studio etc. Hires
            { DO_COPY     , DS_BITMAP_RAM  , 0x0000, 0,  0, NULL, NULL },
            { DO_COPY     , DS_SCREEN_RAM  , 0x1f40, 0,  0, NULL, NULL },
            { DO_LAST     , 0              , 0     , 0,  0, NULL, NULL },
        },

        { // #3: FunPaint II
            DEF_SCREEN_RAMS_8(0x0000, 0, 0x400),
            { DO_COPY     , DS_BITMAP_RAM  , 0x2000, 0,  0, NULL, NULL },
            { DO_COPY     , DS_EXTRA_DATA  , 0x3f40, 0,  100, NULL, NULL },
            { DO_COPY     , DS_COLOR_RAM   , 0x4000, 0,  0, NULL, NULL },
            DEF_SCREEN_RAMS_8(0x43e8, 8, 0x400),
            { DO_COPY     , DS_BITMAP_RAM  , 0x63e8, 1,  0, NULL, NULL },
            { DO_COPY     , DS_EXTRA_DATA  , 0x8328, 1,  100, NULL, NULL },
            { DO_DEC_FUNC , 0              , 0x2742, 0,  1, fmtTruePaintGetLaceType, NULL },
            { DO_LAST     , 0              , 0     , 0,  0, NULL, NULL },
        },

        { // #4: DrazPaint 1.x & 2
            { DO_COPY     , DS_COLOR_RAM   , 0x0000, 0,  0, NULL, NULL },
            { DO_COPY     , DS_BITMAP_RAM  , 0x0800, 0,  0, NULL, NULL },
            { DO_COPY     , DS_SCREEN_RAM  , 0x0400, 0,  0, NULL, NULL },
            { DO_SET_MEM  , DS_BGCOL       , 0x2740, 0,  0, NULL, NULL },
            { DO_LAST     , 0              , 0     , 0,  0, NULL, NULL },
        },

        { // #5: DrazLace 1.0
            { DO_COPY     , DS_COLOR_RAM   , 0x0000, 0,  0, NULL, NULL },
            { DO_COPY     , DS_BITMAP_RAM  , 0x0800, 0,  0, NULL, NULL },
            { DO_COPY     , DS_SCREEN_RAM  , 0x0400, 0,  0, NULL, NULL },
            { DO_SET_MEM  , DS_BGCOL       , 0x2740, 0,  0, NULL, NULL },
            { DO_COPY     , DS_BITMAP_RAM  , 0x2800, 1,  0, NULL, NULL },
            { DO_DEC_FUNC , 0              , 0x2742, 0,  1, fmtDrazLaceGetLaceType, NULL },
            { DO_ENC_FUNC , 0              , 0x2742, 0,  1, NULL, fmtDrazLaceSetLaceType },
            { DO_LAST     , 0              , 0     , 0,  0, NULL, NULL },
        },

        { // #6: TruePaint
            { DO_COPY     , DS_SCREEN_RAM  , 0x0000, 0,  0, NULL, NULL },
            { DO_SET_MEM  , DS_BGCOL       , 0x03e8, 0,  0, NULL, NULL },
            { DO_COPY     , DS_BITMAP_RAM  , 0x0400, 0,  0, NULL, NULL },
            { DO_COPY     , DS_BITMAP_RAM  , 0x2400, 1,  0, NULL, NULL },
            { DO_COPY     , DS_SCREEN_RAM  , 0x4400, 1,  0, NULL, NULL },
            { DO_COPY     , DS_COLOR_RAM   , 0x4800, 0,  0, NULL, NULL },
            { DO_DEC_FUNC , 0              , 0x0000, 0,  0, fmtTruePaintGetLaceType, NULL },
            { DO_LAST     , 0              , 0     , 0,  0, NULL, NULL },
        },
};


//
// Array with data for supported formats
//
const DMC64ImageFormat dmC64ImageFormats[] =
{
    {
        D64_FMT_MC, "d2p", "DrazPaint 1.4/2.0 (packed)", 0x5800, 0, DM_FMT_RDWR,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        fmtProbeDrazPaint20Packed,
        fmtDecodeDrazPaintPacked, fmtEncodeDrazPaintPacked,
        NULL, NULL,
        NULL,
        { }, &dmC64CommonFormatOps[4]
    },

    {
        D64_FMT_MC, "drp", "DrazPaint (unpacked)", 0x5800, 10051, DM_FMT_RDWR,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        { }, &dmC64CommonFormatOps[4]
    },

    {
        D64_FMT_MC | D64_FMT_ILACE, "dlp", "DrazLace 1.0 (packed)", 0x5800, 0, DM_FMT_RDWR,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        fmtProbeDrazLace10Packed,
        fmtDecodeDrazPaintPacked, fmtEncodeDrazPaintPacked,
        NULL, NULL,
        NULL,
        { }, &dmC64CommonFormatOps[5]
    },

    {
        D64_FMT_MC | D64_FMT_ILACE, "drl", "DrazLace 1.0 (unpacked)", 0x5800, 18242, DM_FMT_RDWR,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        { }, &dmC64CommonFormatOps[5]
    },

    {
        D64_FMT_MC, "bdp5", "Boogie Down Paint 5 (packed)", 0x5000, 0, DM_FMT_RDWR,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        fmtProbeBDP5Packed,
        fmtDecodeBDP5Packed, fmtEncodeBDP5Packed,
        NULL, NULL,
        NULL,
        { }, &dmC64CommonFormatOps[0] // Memory format is same as Koala
    },

    {
        D64_FMT_MC, "vid", "Vidcom 64 (unpacked)", 0x5800, 10050, DM_FMT_RDWR,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        {
            { DO_COPY     , DS_COLOR_RAM   , 0x0000, 0,  0, NULL, NULL },
            { DO_SET_MEM  , DS_BGCOL       , 0x07e8, 0,  0, NULL, NULL },
            { DO_COPY     , DS_BITMAP_RAM  , 0x0800, 0,  0, NULL, NULL },
            { DO_COPY     , DS_SCREEN_RAM  , 0x0400, 0,  0, NULL, NULL },
            { DO_LAST     , 0              , 0     , 0,  0, NULL, NULL },
        },
        NULL
    },

    {
        D64_FMT_MC, "p64", "Picasso 64 (unpacked)", 0x1800, 10050, DM_FMT_RDWR,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        {
            { DO_COPY     , DS_COLOR_RAM   , 0x0000, 0,  0, NULL, NULL },
            { DO_SET_MEM  , DS_BGCOL       , 0x07fe, 0,  0, NULL, NULL },
            { DO_COPY     , DS_BITMAP_RAM  , 0x0800, 0,  0, NULL, NULL },
            { DO_COPY     , DS_SCREEN_RAM  , 0x0400, 0,  0, NULL, NULL },
            { DO_LAST     , 0              , 0     , 0,  0, NULL, NULL },
        },
        NULL
    },

    {
        D64_FMT_MC | D64_FMT_ILACE, "mci", "Truepaint (unpacked)", 0x9c00, 19434, DM_FMT_RD,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        fmtGetPixelTruePaint,
        { }, &dmC64CommonFormatOps[6]
    },

    {
        D64_FMT_MC | D64_FMT_ILACE, "mcip", "Truepaint (packed)", 0x0801, 0, DM_FMT_RD,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        fmtProbeTruePaintPacked,
        fmtDecodeTruePaintPacked, NULL,
        NULL, NULL,
        fmtGetPixelTruePaint,
        { }, &dmC64CommonFormatOps[6]
    },

    {
        D64_FMT_MC, "kla", "Koala Paint (unpacked)", 0x6000, 10003, DM_FMT_RDWR,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        { }, &dmC64CommonFormatOps[0]
    },

    {
        D64_FMT_MC, "klp", "Koala Paint (packed)", 0x6000, 0, DM_FMT_RDWR,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        fmtProbeKoalaPaintPacked,
        fmtDecodeKoalaPaintPacked, fmtEncodeKoalaPaintPacked,
        NULL, NULL,
        NULL,
        { }, &dmC64CommonFormatOps[0]
    },

    {
        D64_FMT_MC, "aas", "Advanced Art Studio (unpacked)", 0x2000, 10018, DM_FMT_RDWR,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        {
            { DO_COPY     , DS_BITMAP_RAM  , 0x0000, 0,  0, NULL, NULL },
            { DO_COPY     , DS_SCREEN_RAM  , 0x1f40, 0,  0, NULL, NULL },
            { DO_COPY     , DS_COLOR_RAM   , 0x2338, 0,  0, NULL, NULL },
            { DO_SET_MEM  , DS_BGCOL       , 0x2329, 0,  0, NULL, NULL },
            { DO_LAST     , 0              , 0     , 0,  0, NULL, NULL },
        },
        NULL
    },

    {
        D64_FMT_MC, "ami", "Amica Paint (packed)", 0x4000, 0, DM_FMT_RDWR,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        fmtProbeAmicaPaintPacked,
        fmtDecodeAmicaPaintPacked, fmtEncodeAmicaPaintPacked,
        NULL, NULL,
        NULL,
        { }, &dmC64CommonFormatOps[1]
    },

    {
        D64_FMT_MC, "rpm", "Run Paint (unpacked)", 0x6000, 10006, DM_FMT_RDWR,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        { }, &dmC64CommonFormatOps[1]
    },

    {
        D64_FMT_MC, "ipc", "Interpaint MC (unpacked)", 0x4000, 10003, DM_FMT_RDWR,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        { }, &dmC64CommonFormatOps[1]
    },

    {
        D64_FMT_HIRES, "art", "Art Studio (unpacked)", 0x2000, 9009, DM_FMT_RD,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        { }, &dmC64CommonFormatOps[2]
    },

    {
        D64_FMT_HIRES, "iph", "Interpaint (unpacked)", 0x4000, 9002, DM_FMT_RD,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        { }, &dmC64CommonFormatOps[2]
    },

    {
        D64_FMT_HIRES, "dd", "Doodle (unpacked)", 0x1c00, 9218, DM_FMT_RDWR,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        {
            { DO_COPY     , DS_SCREEN_RAM  , 0x0000, 0,  0, NULL, NULL },
            { DO_COPY     , DS_BITMAP_RAM  , 0x0400, 0,  0, NULL, NULL },
            { DO_LAST     , 0              , 0     , 0,  0, NULL, NULL },
        },
        NULL
    },

    {
        D64_FMT_HIRES, "mon", "Monomagic (unpacked)", 0x2000, 8194, DM_FMT_RDWR,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        {
            { DO_COPY     , DS_BITMAP_RAM  , 0x0000, 0, 0, NULL, NULL },
            { DO_SET_OP   , DS_SCREEN_RAM  , 0xCF  , 0, 0, NULL, NULL },
            // Default colors used by MM are --^^
            { DO_LAST     , 0              , 0     , 0, 0, NULL, NULL },
        },
        NULL
    },

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

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

    {
        D64_FMT_MC | D64_FMT_FLI, "bml", "Blackmail FLI (unpacked)", 0x3b00, 17474, DM_FMT_RDWR,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        fmtGetPixelBlackMailFLI,
        {
            { DO_COPY     , DS_EXTRA_DATA  , 0x0000, 0,  200, NULL, NULL },
            { DO_COPY     , DS_COLOR_RAM   , 0x0100, 0,  0, NULL, NULL },
            DEF_SCREEN_RAMS_8(0x0500, 0, 0x400),
            { DO_COPY     , DS_BITMAP_RAM  , 0x2500, 0,  0, NULL, NULL },
            { DO_LAST     , 0              , 0     , 0,  0, NULL, NULL },
        },
        NULL
    },

    {
        D64_FMT_MC | D64_FMT_FLI, "fli", "FLI Designer (unpacked)", 0, 17409, DM_FMT_RD,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        fmtProbeFLIDesigner,
        NULL, NULL,
        NULL, NULL,
        fmtGetPixelFLIDesigner,
        {
            { DO_COPY     , DS_COLOR_RAM   , 0x0000, 0,  0, NULL, NULL },
            DEF_SCREEN_RAMS_8(0x0400, 0, 0x400),
            { DO_COPY     , DS_BITMAP_RAM  , 0x2400, 0,  0, NULL, NULL },
            { DO_LAST     , 0              , 0     , 0,  0, NULL, NULL },
        },
        NULL
    },

    {
        D64_FMT_MC, "xx1", "Unknown $2000 format (unpacked)", 0x2000, 10242, DM_FMT_RD,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL,
        NULL, NULL,
        NULL, NULL,
        NULL,
        {
            { DO_COPY     , DS_BITMAP_RAM  , 0x0000, 0,  0, NULL, NULL },
            { DO_COPY     , DS_SCREEN_RAM  , 0x2000, 0,  0, NULL, NULL },
            { DO_COPY     , DS_COLOR_RAM   , 0x2400, 0,  0, NULL, NULL },
            { DO_SET_OP   , DS_BGCOL       , 0x00  , 0,  0, NULL, NULL },
            { DO_SET_OP   , DS_EXTRA_DATA  , 10240 , 0,  0, NULL, NULL },
            { DO_LAST     , 0              , 0     , 0,  0, NULL, NULL },
        },
        NULL
    },

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

    {
        D64_FMT_MC | D64_FMT_FLI | D64_FMT_ILACE,
        "fp2", "FunPaint II (unpacked)", 0x3ff0, 33694, DM_FMT_RD,
        C64_SCR_WIDTH, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        fmtProbeFunPaint2,
        fmtDecodeFunPaint2, fmtEncodeFunPaint2Unpacked,
        NULL, NULL,
        fmtGetPixelFunPaint2,
        { }, &dmC64CommonFormatOps[3]
    },

    {
        D64_FMT_MC | D64_FMT_FLI | D64_FMT_ILACE,
        "fp2p", "FunPaint II (packed)", 0x3ff0, 0, DM_FMT_RD,
        C64_SCR_WIDTH, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL,
        fmtDecodeFunPaint2, fmtEncodeFunPaint2Packed,
        NULL, NULL,
        fmtGetPixelFunPaint2,
        { }, &dmC64CommonFormatOps[3]
    },

    {
        D64_FMT_MC | D64_FMT_FLI | D64_FMT_ILACE,
        "gun", "GunPaint (unpacked)", 0x4000, 0, DM_FMT_RD,
        C64_SCR_WIDTH, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        fmtProbeGunPaint,
        NULL, NULL,
        NULL, NULL,
        fmtGetPixelGunPaint,
        {
            DEF_SCREEN_RAMS_8(0x0000, 0, 0x400),
            { DO_COPY     , DS_BITMAP_RAM  , 0x2000, 0,  0, NULL, NULL },
            { DO_COPY     , DS_EXTRA_DATA  , 0x3f4f, 0,  177, NULL, NULL },
            { DO_COPY     , DS_COLOR_RAM   , 0x4000, 0,  0, NULL, NULL },
            DEF_SCREEN_RAMS_8(0x4400, 8, 0x400),
            { DO_COPY     , DS_BITMAP_RAM  , 0x6400, 1,  0, NULL, NULL },
            { DO_COPY     , DS_EXTRA_DATA  , 0x47e8, 1,  20, NULL, NULL },
            { DO_DEC_FUNC , 0              , 0x2742, 0,  1, fmtTruePaintGetLaceType, NULL },
            { DO_LAST     , 0              , 0     , 0,  0, NULL, NULL },
        },
        NULL
    },

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

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