view tools/lib64gfx.c @ 1308:8f71ca1900ea

Update header blurps and copyrights.
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 19 Aug 2017 15:21:41 +0300
parents 4b025a96ad46
children 00f47a678482
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-2017 Tecnic Software productions (TNSP)
 *
 * Please read file 'COPYING' for information on license and distribution.
 */
#include "lib64gfx.h"

#define BUF_SIZE_INITIAL   (16*1024)
#define BUF_SIZE_GROW      (4*1024)


DMC64Image * dmC64ImageAlloc(int width, int height, int ch_width, int ch_height)
{
    int i;
    DMC64Image *img = dmMalloc0(sizeof(DMC64Image));

    if (img == NULL)
        return NULL;

    img->width     = width;
    img->height    = height;
    img->ch_width  = ch_width;
    img->ch_height = ch_height;

    for (i = 0; i < C64_SCR_MAX_BANK; i++)
    {
        if ((img->color[i] = dmMalloc0(ch_width * ch_height)) == NULL)
            goto err;

        if ((img->bitmap[i] = dmMalloc0(ch_width * ch_height * 8)) == NULL)
            goto err;

        if ((img->screen[i] = dmMalloc0(ch_width * ch_height)) == NULL)
            goto err;

        if ((img->charmem[i] = dmMalloc0(C64_MAX_CHARS * C64_CHR_SIZE)) == NULL)
            goto err;
    }

    return img;

err:
    dmC64ImageFree(img);
    return NULL;
}


void dmC64ImageFree(DMC64Image *img)
{
    if (img != NULL)
    {
        int i;
        for (i = 0; i < C64_SCR_MAX_BANK; i++)
        {
            dmFree(img->color[i]);
            dmFree(img->bitmap[i]);
            dmFree(img->screen[i]);
            dmFree(img->charmem[i]);
        }
        dmMemset(img, 0, sizeof(DMC64Image));
        dmFree(img);
    }
}


char * dmC64GetImageTypeString(char *buf, const size_t len, const int type)
{
    *buf = 0;

    if (type & D64_FMT_FLI)
        strncat(buf, "FLI ", len - strlen(buf));

    strncat(buf, (type & D64_FMT_MC) ? "MCol " : "HiRes ", len - strlen(buf));

    if (type & D64_FMT_ILACE)
        strncat(buf, "Ilace ", len - strlen(buf));

    if (type & D64_FMT_CHAR)
        strncat(buf, "CharMap", len - strlen(buf));

    return buf;
}


// Based on Pepto's palette, stolen from VICE
DMColor dmC64Palette[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 },
};


#define DM_GET_ADDR_LO(addr) ((addr) & 0xff)
#define DM_GET_ADDR_HI(addr) (((addr) >> 8) & 0xff)


static BOOL dmCompareAddr16(const Uint8 *buf, const size_t offs, const Uint16 addr)
{
    return buf[offs    ] == DM_GET_ADDR_LO(addr) &&
           buf[offs + 1] == DM_GET_ADDR_HI(addr);
}


int dmC64ConvertCSDataToImage(DMImage *img,
    int xoffs, int yoffs, const Uint8 *buf,
    int width, int height, BOOL multicolor,
    int *colors)
{
    int yc, widthpx = width * 8;
    Uint8 *dp;

    if (img == NULL)
        return DMERR_NULLPTR;

    if (xoffs < 0 || yoffs < 0 ||
        xoffs > img->width - widthpx ||
        yoffs > img->height - height)
        return DMERR_INVALID_ARGS;

    dp = img->data + (yoffs * img->pitch) + xoffs;

    if (multicolor)
    {
        for (yc = 0; yc < height; yc++)
        {
            const int offs = yc * width;
            int xc;
            Uint8 *d = dp;

            for (xc = 0; xc < widthpx / 2; xc++)
            {
                const int b = buf[offs + (xc / 4)];
                const int v = 6 - ((xc * 2) & 6);
                const Uint8 c = colors[(b >> v) & 3];

                *d++ = c;
                *d++ = c;
            }

            dp += img->pitch;
        }
    }
    else
    {
        for (yc = 0; yc < height; yc++)
        {
            const int offs = yc * width;
            int xc;
            Uint8 *d = dp;

            for (xc = 0; xc < widthpx; xc++)
            {
                const int b = buf[offs + (xc / 8)];
                const int v = 7 - (xc & 7);
                const Uint8 c = colors[(b >> v) & 1];

                *d++ = c;
            }

            dp += img->pitch;
        }
    }

    return DMERR_OK;
}


static int fmtProbeDrazPaint20Packed(const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    const char *ident = (const char *) buf + 2;

    if (len > 22 &&
        dmCompareAddr16(buf, 0, fmt->addr) &&
        strncmp(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 dmDecodeGenericRLE(Uint8 **mem, Uint8 **pdstEnd, const Uint8 *src, const Uint8 *srcEnd, const Uint8 rleMarker)
{
    Uint8 *dst, *dstEnd;

    if ((*mem = dmMalloc(C64_RAM_SIZE)) == NULL)
        return DMERR_MALLOC;

    dst       = *mem;
    dstEnd    = *mem + C64_RAM_SIZE;

    while (src <= srcEnd && dst <= dstEnd)
    {
        int c = *src++;
        if (c == rleMarker && src + 2 <= srcEnd)
        {
            int cnt = *src++;
            c = *src++;
            while (cnt-- && dst <= dstEnd)
                *dst++ = c;
        }
        else
            *dst++ = c;
    }

    *pdstEnd = dst;

    return DMERR_OK;
}


static int fmtDecodeDrazPaintPacked(DMC64Image *img, const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    int res;
    Uint8 *mem = NULL, *dstEnd;

    if ((res = dmDecodeGenericRLE(&mem, &dstEnd, buf + 0x0e, buf + len, *(buf + 0x0d))) != DMERR_OK)
        goto out;

    res = dmC64DecodeGenericBMP(img, mem, dstEnd - mem + 1, fmt);

out:
    dmFree(mem);
    return res;
}


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

    return DM_PROBE_SCORE_FALSE;
}


static BOOL fmtDrazLaceSetLaceType(DMC64Image *img, const struct _DMC64EncDecOp *op, const Uint8 *buf, const size_t len)
{
    (void) len;

    img->laceType = buf[op->offs] ? D64_ILACE_RES : D64_ILACE_COLOR;
    img->laceBank1 = img->laceBank2 = 0;
    return TRUE;
}


#define AMICA_DM_PROBE_SIZE 2048
static int fmtProbeAmicaPaintPacked(const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    size_t i, n;
    if (len < AMICA_DM_PROBE_SIZE || !dmCompareAddr16(buf, 0, fmt->addr))
        return DM_PROBE_SCORE_FALSE;

    // Interpaint Hi-Res gives a false positive
    if (len == 9002)
        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 Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    int res;
    Uint8 *mem = NULL, *dstEnd;

    if ((res = dmDecodeGenericRLE(&mem, &dstEnd, buf, buf + len, 0xC2)) != DMERR_OK)
        goto out;

    res = dmC64DecodeGenericBMP(img, mem, dstEnd - mem + 1, fmt);

out:
    dmFree(mem);
    return res;
}


static BOOL fmtTruePaintSetLaceType(DMC64Image *img, const struct _DMC64EncDecOp *op, const Uint8 *buf, const size_t len)
{
    (void) op;
    (void) buf;
    (void) len;
    img->laceType = D64_ILACE_RES;
    img->laceBank1 = 0;
    img->laceBank2 = 1;
    return TRUE;
}


static BOOL fmtSetFLIType(DMC64Image *img, const struct _DMC64EncDecOp *op, const Uint8 *buf, const size_t len)
{
    (void) buf;
    (void) len;
    img->fliType = op->bank;
    return TRUE;
}


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

    return DM_PROBE_SCORE_FALSE;
}


static int fmtDecodeFormatXX2(DMC64Image *img, const Uint8 *buf, const size_t len, const DMC64ImageFormat *fmt)
{
    int res;
    size_t nlen = len < 4000 ? 4000 : len;
    Uint8 *mem = dmMalloc(nlen);
    if (mem == NULL)
        return DMERR_MALLOC;

    memcpy(mem, buf, len);
    res = dmC64DecodeGenericBMP(img, mem, nlen, fmt);

    dmFree(mem);
    return res;
}




const DMC64ImageFormat dmC64ImageFormats[] =
{
    {
        D64_FMT_MC, "d2p", "DrazPaint 2.0 (packed)", 0x5800, 0,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        fmtProbeDrazPaint20Packed, fmtDecodeDrazPaintPacked,
        NULL, NULL, NULL,
        4,
        {
            { DT_COLOR_RAM,    0x0000, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0800, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x0400, 0,  0, NULL, NULL },
            { DT_COLOR_REG,    0x2740, 0,  DC_BGCOL, NULL, NULL },
        }
    },

    {
        D64_FMT_MC | D64_FMT_ILACE, "dlp", "DrazLace 1.0 (packed)", 0x5800, 0,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        fmtProbeDrazLace10Packed, fmtDecodeDrazPaintPacked,
        NULL, NULL, NULL,
        6,
        {
            { DT_COLOR_RAM,    0x0000, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0800, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x0400, 0,  0, NULL, NULL },
            { DT_COLOR_REG,    0x2740, 0,  DC_BGCOL, NULL, NULL },
            { DT_BITMAP,       0x2800, 1,  0, NULL, NULL },
            { DT_DEC_FUNCTION, 0x2742, 0,  1, fmtDrazLaceSetLaceType, NULL },
        }
    },

    {
        D64_FMT_MC, "drp", "DrazPaint (unpacked)", 0x5800, 10051,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL, NULL,
        NULL, NULL, NULL,
        4,
        {
            { DT_COLOR_RAM,    0x0000, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0800, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x0400, 0,  0, NULL, NULL },
            { DT_COLOR_REG,    0x2740, 0,  DC_BGCOL, NULL, NULL },
        }
    },

    {
        D64_FMT_MC | D64_FMT_ILACE, "drl", "DrazLace 1.0 (unpacked)", 0x5800, 18242,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        NULL, NULL,
        NULL, NULL, NULL,
        6,
        {
            { DT_COLOR_RAM,    0x0000, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0800, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x0400, 0,  0, NULL, NULL },
            { DT_COLOR_REG,    0x2740, 0,  DC_BGCOL, NULL, NULL },
            { DT_BITMAP,       0x2800, 1,  0, NULL, NULL },
            { DT_DEC_FUNCTION, 0x2742, 0,  1, fmtDrazLaceSetLaceType, NULL },
        }
    },

    {
        D64_FMT_MC | D64_FMT_ILACE, "mci", "Truepaint (unpacked)", 0x9c00, 19434,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        NULL, NULL,
        NULL, NULL, NULL,
        6,
        {
            { DT_SCREEN_RAM,   0x0000, 0,  0, NULL, NULL },
            { DT_COLOR_REG,    0x03e8, 0,  DC_BGCOL, NULL, NULL },
            { DT_BITMAP,       0x0400, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x2400, 1,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x4400, 1,  0, NULL, NULL },
            { DT_COLOR_RAM,    0x4800, 0,  0, NULL, NULL },
            { DT_DEC_FUNCTION, 0x0000, 0,  0, fmtTruePaintSetLaceType, NULL },
        }
    },

    {
        D64_FMT_MC, "kla", "Koala Paint (unpacked)", 0x6000, 10003,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL, NULL,
        NULL, NULL, NULL,
        4,
        {
            { DT_BITMAP,       0x0000, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1f40, 0,  0, NULL, NULL },
            { DT_COLOR_RAM,    0x2328, 0,  0, NULL, NULL },
            { DT_COLOR_REG,    0x2710, 0,  DC_BGCOL, NULL, NULL },
        }
    },

    {
        D64_FMT_MC, "ocp", "Advanced Art Studio (unpacked)", 0x2000, 10018,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL, NULL,
        NULL, NULL, NULL,
        4,
        {
            { DT_BITMAP,       0x0000, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1f40, 0,  0, NULL, NULL },
            { DT_COLOR_RAM,    0x2338, 0,  0, NULL, NULL },
            { DT_COLOR_REG,    0x2329, 0,  DC_BGCOL, NULL, NULL },
        }
    },

    {
        D64_FMT_MC, "ami", "Amica Paint (packed)", 0x4000, 0,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        fmtProbeAmicaPaintPacked, fmtDecodeAmicaPaintPacked,
        NULL, NULL, NULL,
        4,
        {
            { DT_COLOR_RAM,    0x2328, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0000, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1f40, 0,  0, NULL, NULL },
            { DT_COLOR_REG,    0x2710, 0,  DC_BGCOL, NULL, NULL },
        }
    },

    {
        D64_FMT_MC, "rpm", "Run Paint (unpacked)", 0x6000, 10006,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL, NULL,
        NULL, NULL, NULL,
        4,
        {
            { DT_COLOR_RAM,    0x2328, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0000, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1f40, 0,  0, NULL, NULL },
            { DT_COLOR_REG,    0x2710, 0,  DC_BGCOL, NULL, NULL },
        }
    },

    {
        D64_FMT_HIRES, "art", "Art Studio (unpacked)", 0x2000, 9009,
        C64_SCR_WIDTH    , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL, NULL,
        NULL, NULL, NULL,
        2,
        {
            { DT_BITMAP,       0x0000, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1f40, 0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_HIRES, "iph", "Interpaint (unpacked)", 0x4000, 9002,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        NULL, NULL,
        NULL, NULL, NULL,
        2,
        {
            { DT_BITMAP,       0x0000, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1f40, 0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_HIRES, "dd", "Doodle (unpacked)", 0x1c00, 9218,
        C64_SCR_WIDTH   , C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH, C64_SCR_CH_HEIGHT,
        NULL, NULL,
        NULL, NULL, NULL,
        2,
        {
            { DT_SCREEN_RAM,   0x0000, 0,  0, NULL, NULL },
            { DT_BITMAP,       0x0400, 0,  0, NULL, NULL },
        }
    },

    {
        D64_FMT_MC | D64_FMT_FLI, "bml", "Blackmail FLI (unpacked)", 0x3b00, 17474,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL, NULL,
        NULL, NULL, NULL,
        11,
        {
            { DT_COLOR_RAM,    0x0100, 0,  0, NULL, NULL },

            { DT_SCREEN_RAM,   0x0500, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x0900, 1,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x0d00, 2,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1100, 3,  0, NULL, NULL },

            { DT_SCREEN_RAM,   0x1500, 4,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1900, 5,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1d00, 6,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x2100, 7,  0, NULL, NULL },

            { DT_BITMAP,       0x2500, 0,  0, NULL, NULL },
            { DT_DEC_FUNCTION, 0x0000, D64_FLI_8BANK,  0, fmtSetFLIType, NULL },
        }
    },

    {
        D64_FMT_MC | D64_FMT_FLI, "fli", "FLI Designer (unpacked)", 0x3c00, 17409,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL, NULL,
        NULL, NULL, NULL,
        11,
        {
            { DT_COLOR_RAM,    0x0000, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x0400, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x0800, 1,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x0c00, 2,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1000, 3,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1400, 4,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1800, 5,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x1c00, 6,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x2000, 7,  0, NULL, NULL },
            { DT_BITMAP,       0x2400, 0,  0, NULL, NULL },
            { DT_DEC_FUNCTION, 0x0000, D64_FLI_8BANK,  0, fmtSetFLIType, NULL },
        }
    },

    {
        D64_FMT_MC, "xx1", "Unknown $2000 format (unpacked)", 0x2000, 10242,
        C64_SCR_WIDTH / 2, C64_SCR_HEIGHT,
        C64_SCR_CH_WIDTH , C64_SCR_CH_HEIGHT,
        NULL, NULL,
        NULL, NULL, NULL,
        4,
        {
            { DT_BITMAP,       0x0000, 0,  0, NULL, NULL },
            { DT_SCREEN_RAM,   0x2000, 0,  0, NULL, NULL },
            { DT_COLOR_RAM,    0x2400, 0,  0, NULL, NULL },
            { DT_COLOR_SET,    0x00  , 0,  DC_BGCOL, NULL, NULL },
        }
    },

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

    {
        D64_FMT_MC, "xx2", "Unknown $2000 format (unpacked)", 0x2000, 0,
        XX2_WIDTH_CH * 4, XX2_HEIGHT_CH * 8,
        XX2_WIDTH_CH    , XX2_HEIGHT_CH,
        fmtProbeFormatXX2, fmtDecodeFormatXX2,
        NULL, NULL, NULL,
        4,
        {
            { DT_BITMAP,       0x0000, 0,  XX2_BSIZE, NULL, NULL },
            { DT_COLOR_RAM,    XX2_BSIZE + XX2_SIZE, 0,  XX2_SIZE, NULL, NULL },
            { DT_SCREEN_RAM,   XX2_BSIZE, 0,  XX2_SIZE, NULL, NULL },

            { DT_COLOR_SET,    11  , 0,  DC_BGCOL, NULL, NULL },
        }
    },
};

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


// Perform probing of the given data buffer, trying to determine
// if it contains a supported "C64" image format. Returns the
// "probe score", see libgfx.h for list of values. If a match
// is found, pointer to format description is set to *pfmt.
int dmC64ProbeBMP(const Uint8 *buf, const size_t len, const DMC64ImageFormat **pfmt)
{
    int i, scoreMax = DM_PROBE_SCORE_FALSE, scoreIndex = -1;

    for (i = 0; i < ndmC64ImageFormats; i++)
    {
        const DMC64ImageFormat *fmt = &dmC64ImageFormats[i];
        int score = DM_PROBE_SCORE_FALSE;
        if (fmt->probe == NULL && fmt->size > 0 && fmt->addr > 0)
        {
            // Generic probe just checks matching size and load address
            if (len == fmt->size && dmCompareAddr16(buf, 0, fmt->addr))
                score = DM_PROBE_SCORE_GOOD;
        }
        else
        if (fmt->probe != NULL)
            score = fmt->probe(buf, len, fmt);

        if (score > scoreMax)
        {
            scoreMax = score;
            scoreIndex = i;
        }
    }

    if (scoreIndex >= 0)
    {
        *pfmt = &dmC64ImageFormats[scoreIndex];
        return scoreMax;
    }
    else
        return DM_PROBE_SCORE_FALSE;
}


static int dmC64SanityCheckEncDecOp(const int i, const DMC64EncDecOp *op)
{
    if (op->bank < 0 || op->bank >= C64_SCR_MAX_BANK)
    {
        return dmError(DMERR_INTERNAL,
            "Invalid bank %d definition in generic encode/decode operator %d @ #%d.\n",
            op->bank, op->type, i);
    }

    if (op->type < 0 || op->type >= DT_LAST)
    {
        return dmError(DMERR_INTERNAL,
            "Invalid encode/decode operator type %d @ #%d.\n",
            op->type, i);
    }

    return DMERR_OK;
}


static BOOL dmC64GetOpSize(const DMC64EncDecOp *op, const DMC64ImageFormat *fmt, size_t *size)
{
    BOOL check;
    switch (op->type)
    {
        case DT_SCREEN_RAM:
        case DT_COLOR_RAM:
            *size = fmt->ch_height * fmt->ch_width;
            check = TRUE;
            break;

        case DT_BITMAP:
            *size = fmt->ch_height * fmt->ch_width * 8;
            check = TRUE;
            break;

        case DT_EXTRADATA:
            *size = C64_SCR_EXTRADATA;
            check = TRUE;
            break;

        case DT_CHAR_DATA:
            *size = C64_MAX_CHARS * C64_CHR_SIZE;
            check = TRUE;
            break;

        case DT_COLOR_REG:
            *size = 1;
            check = FALSE;
            break;

        default:
            *size = 0;
            check = FALSE;
    }

    if (op->size != 0)
    {
        if (check && op->size > *size)
            return FALSE;

        *size = op->size;
    }

    return TRUE;
}


int dmC64DecodeGenericBMP(DMC64Image *img, const Uint8 *buf,
    const size_t len, const DMC64ImageFormat *fmt)
{
    int i;

    if (buf == NULL || img == NULL || fmt == NULL)
        return DMERR_NULLPTR;

    if (fmt->nencdecOps < 0 || fmt->nencdecOps >= D64_MAX_ENCDEC_OPS)
    {
        return dmError(DMERR_INTERNAL,
            "Invalid number of enc/dec ops in format. Internal error.\n");
    }

    // Clear the image structure, set basics
    img->type      = fmt->type;
    img->width     = fmt->width;
    img->height    = fmt->height;
    img->ch_width  = fmt->ch_width;
    img->ch_height = fmt->ch_height;

    // Perform decoding
    for (i = 0; i < fmt->nencdecOps; i++)
    {
        const DMC64EncDecOp *op = &fmt->encdecOps[i];
        const Uint8 *src;
        size_t size;
        int res;

        // Check operation validity
        if ((res = dmC64SanityCheckEncDecOp(i, op)) != DMERR_OK)
            return res;

        // Check size
        if (!dmC64GetOpSize(op, fmt, &size))
        {
            return dmError(DMERR_INVALID_DATA,
                "Decode op SIZE out of bounds, op #%d type=%d, offs=%d ($%04x), "
                "bank=%d, size=%d ($%04x) vs. allocated %d ($%04x)\n",
                i, op->type, op->offs, op->offs, op->bank, size, size, op->size, op->size);
        }

        // Do we need to reallocate some more space?
        if (op->offs + size > len)
        {
            return dmError(DMERR_INVALID_DATA,
                "Decode out of bounds, op #%d type=%d, offs=%d ($%04x), "
                "bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                i, op->type, op->offs, op->offs, op->bank, size, size, len, len);
        }

        src = buf + op->offs;

        // Perform operation
        switch (op->type)
        {
            case DT_COLOR_RAM:   memcpy(img->color[op->bank], src, size); break;
            case DT_BITMAP:      memcpy(img->bitmap[op->bank], src, size); break;
            case DT_SCREEN_RAM:  memcpy(img->screen[op->bank], src, size); break;
            case DT_CHAR_DATA:   memcpy(img->charmem[op->bank], src, size); break;
            case DT_EXTRADATA:   memcpy(img->extradata, src, size); break;

            case DT_COLOR_REG:
                switch (op->size)
                {
                    case DC_D020: img->d020 = *src; break;
                    case DC_BGCOL:
                    case DC_D021: img->bgcolor = *src; break;
                    case DC_D022: img->d022 = *src; break;
                    case DC_D023: img->d023 = *src; break;
                    case DC_D024: img->d024 = *src; break;
                    default:
                        return dmError(DMERR_INTERNAL,
                            "Unhandled DT_COLOR_REG mode %d in ",
                            "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                            op->size, i, op->offs, op->offs, op->bank, size, size, len, len);
                }
                break;

            case DT_COLOR_SET:
                switch (op->size)
                {
                    case DC_D020: img->d020 = op->offs; break;
                    case DC_BGCOL:
                    case DC_D021: img->bgcolor = op->offs; break;
                    case DC_D022: img->d022 = op->offs; break;
                    case DC_D023: img->d023 = op->offs; break;
                    case DC_D024: img->d024 = op->offs; break;
                    default:
                        return dmError(DMERR_INTERNAL,
                            "Unhandled DT_COLOR_SET mode %d in ",
                            "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                            op->size, i, op->offs, op->offs, op->bank, size, size, len, len);
                }
                break;

            case DT_CHAR_CONFIG:
                switch (op->offs)
                {
                    case D64_CHCFG_SCREEN:
                        break;

                    case D64_CHCFG_LINEAR:
                        {
                            int bank, offs;
                            for (bank = 0; bank < C64_SCR_MAX_BANK; bank++)
                            for (offs = 0; offs < fmt->ch_height * fmt->ch_width; offs++)
                                img->screen[bank][offs] = offs & 0xff;
                        }
                        break;

                    default:
                        return dmError(DMERR_INTERNAL,
                            "Unhandled DT_CHAR_CONFIG mode %d in ",
                            "op #%d, bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                            op->offs, i, op->bank, size, size, len, len);
                }
                break;

            case DT_DEC_FUNCTION:
                if (op->decfunction == NULL)
                {
                    return dmError(DMERR_INTERNAL,
                        "Decode op is a function, but function ptr is NULL: "
                        "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                        i, op->offs, op->offs, op->bank, size, size, len, len);
                }
                if (!op->decfunction(img, op, buf, len))
                {
                    return dmError(DMERR_INTERNAL,
                        "Decode op custom function failed: op #%d, "
                        "offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                        i, op->offs, op->offs, op->bank, size, size, len, len);
                }
                break;
        }
    }

    return DMERR_OK;
}


int dmC64EncodeGenericBMP(Uint8 **pbuf, size_t *plen, const DMC64Image *img, const DMC64ImageFormat *fmt)
{
    int i, res = DMERR_OK;
    Uint8 *buf;
    size_t allocated;

    if (pbuf == NULL || plen == NULL || img == NULL || fmt == NULL)
        return DMERR_NULLPTR;

    if (fmt->nencdecOps < 0 || fmt->nencdecOps >= D64_MAX_ENCDEC_OPS)
    {
        return dmError(DMERR_INTERNAL,
            "Invalid number of enc/dec ops in format. Internal error.\n");
    }

    // Allocate the output buffer
    *plen = 0;
    if (fmt->size > 0)
        *plen = allocated = fmt->size;
    else
        allocated = 16 * 1024;

    if ((buf = dmMalloc(allocated)) == NULL)
    {
        return dmError(DMERR_MALLOC,
            "Could not allocate %d bytes of memory for C64 image encoding buffer.\n",
            allocated);
        goto error;
    }

    // Perform encoding
    for (i = 0; i < fmt->nencdecOps; i++)
    {
        const DMC64EncDecOp *op = &fmt->encdecOps[i];
        Uint8 *dst = 2 + buf + op->offs;
        size_t size;

        // Check operation validity
        if ((res = dmC64SanityCheckEncDecOp(i, op)) != DMERR_OK)
            goto error;

        // Check size
        if (!dmC64GetOpSize(op, fmt, &size))
        {
            res = dmError(DMERR_INVALID_DATA,
                "Decode op SIZE out of bounds, op #%d type=%d, offs=%d ($%04x), "
                "bank=%d, size=%d ($%04x) vs. allocated %d ($%04x)\n",
                i, op->type, op->offs, op->offs, op->bank, size, size, op->size, op->size);
            goto error;
        }

        // Do we need to reallocate some more space?
        if (2 + op->offs + size > allocated)
        {
            size_t diff = allocated - (op->offs + size + 2),
                   grow = (diff / (BUF_SIZE_GROW - 1)) * BUF_SIZE_GROW;

            allocated += grow;

            if ((buf = dmRealloc(buf, allocated)) == NULL)
            {
                res = dmError(DMERR_MALLOC,
                    "Could not re-allocate %d bytes of memory for C64 image encoding buffer.\n",
                    allocated);
                goto error;
            }
        }

        if (fmt->size == 0 && op->offs + size + 2 > *plen)
            *plen = op->offs + size + 2;

        // Perform operation
        switch (op->type)
        {
            case DT_COLOR_RAM:   memcpy(dst, img->color[op->bank], size); break;
            case DT_BITMAP:      memcpy(dst, img->bitmap[op->bank], size); break;
            case DT_SCREEN_RAM:  memcpy(dst, img->screen[op->bank], size); break;
            case DT_CHAR_DATA:   memcpy(dst, img->charmem[op->bank], size); break;
            case DT_EXTRADATA:   memcpy(dst, img->extradata, size); break;

            case DT_COLOR_REG:
                switch (op->size)
                {
                    case DC_D020: *dst = img->d020; break;
                    case DC_BGCOL:
                    case DC_D021: *dst = img->bgcolor; break;
                    case DC_D022: *dst = img->d022; break;
                    case DC_D023: *dst = img->d023; break;
                    case DC_D024: *dst = img->d024; break;
                    default:
                        res = dmError(DMERR_INTERNAL,
                            "Unhandled DT_COLOR_REG mode %d in ",
                            "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                            op->size, i, op->offs, op->offs, op->bank, size, size, *plen, *plen);
                        goto error;
                }
                break;

            case DT_ENC_FUNCTION:
                if (op->encfunction == NULL)
                {
                    res = dmError(DMERR_INTERNAL,
                        "Encode op is a function, but function ptr is NULL: "
                        "op #%d, offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                        i, op->offs, op->offs, op->bank, size, size, *plen, *plen);
                    goto error;
                }
                /*
                if (!op->encfunction(op, buf, len))
                {
                    res = dmError(DMERR_INTERNAL,
                        "Encode op custom function failed: op #%d, "
                        "offs=%d ($%04x), bank=%d, size=%d ($%04x) @ %d ($%04x)\n",
                        i, op->offs, op->offs, op->bank, size, size, len, len);
                    goto out;
                }
                */
                break;
        }
    }

    buf[0] = DM_GET_ADDR_LO(fmt->addr);
    buf[1] = DM_GET_ADDR_HI(fmt->addr);

    *pbuf = buf;
    return DMERR_OK;

error:
    dmFree(buf);
    *pbuf = NULL;
    *plen = 0;
    return res;
}


static inline Uint8 dmC64GetMCColor(const DMC64Image *img,
    const int bits, const int cbank, const int vbank, const int scroffs)
{
    switch (bits)
    {
        case  0: return img->bgcolor; break;
        case  1: return img->screen[vbank][scroffs] >> 4; break;
        case  2: return img->screen[vbank][scroffs] & 15; break;
        default: return img->color[cbank][scroffs] & 15; break;
    }
}


// Convert a generic "C64" format bitmap in DMC64Image struct to
// a indexed/paletted bitmap image.
int dmC64ConvertGenericBMP2Image(DMImage *dst, const DMC64Image *src)
{
    Uint8 *dp = dst->data;
    int yc;

    // Sanity check arguments
    if (dst == NULL || src == NULL)
        return DMERR_NULLPTR;

    if (dst->width < src->width || dst->height < src->height)
        return DMERR_INVALID_DATA;

    dmMemset(dst->data, 0, dst->size);

    // Perform conversion
    for (yc = 0; yc < src->height; yc++)
    {
        Uint8 *d = dp;
        const int y = yc / 8, yb = yc & 7;
        const int scroffsy = y * src->ch_width;
        int xc;

        if (src->type & D64_FMT_CHAR)
        {
            // Charmode conversion
            if ((src->type & D64_FMT_MC) == D64_FMT_HIRES)
            // Hi-res charmap
            for (xc = 0; xc < src->width; xc++)
            {
                const int x = xc / 8;
                const int scroffs = scroffsy + x;
                const int chr = src->screen[0][scroffs];
                const int v = 7 - (xc & 7);

                if ((src->charmem[0][chr * C64_CHR_SIZE + yb] >> v) & 1)
                    *d++ = src->color[0][scroffs];
                else
                    *d++ = src->bgcolor;
            }
            else
            // Multicolor variants
            for (xc = 0; xc < src->width; xc++)
            {
                const int x = xc / 4;
                const int scroffs = scroffsy + x;
                const int chr = src->screen[0][scroffs];
                const int col = src->color[0][scroffs] & 15;

                if (col & 8)
                {
                    const int v = 6 - ((xc * 2) & 6);
                    switch ((src->charmem[0][chr * C64_CHR_SIZE + yb] >> v) & 3)
                    {
                        case 0: *d++ = src->bgcolor; break;
                        case 1: *d++ = src->d022; break;
                        case 2: *d++ = src->d023; break;
                        case 3: *d++ = col;
                    }
                }
                else
                {
                    const int v = 7 - (xc & 7);
                    if ((src->charmem[0][chr * C64_CHR_SIZE + yb] >> v) & 1)
                        *d++ = src->color[0][scroffs];
                    else
                        *d++ = src->bgcolor;
                }
            }
        }
        else
        {
            // Perform generic BITMAP conversion
            const int bmoffsy = y * src->ch_width * 8 + yb;

            if ((src->type & D64_FMT_MC) == D64_FMT_HIRES)
            // Hi-res bitmap
            for (xc = 0; xc < src->width; xc++)
            {
                const int x = xc / 8;
                const int scroffs = scroffsy + x;
                const int bmoffs = bmoffsy + (x * 8);
                const int v = 7 - (xc & 7);

                if ((src->bitmap[0][bmoffs] >> v) & 1)
                    *d++ = src->screen[0][scroffs] >> 4;
                else
                    *d++ = src->screen[0][scroffs] & 15;
            }
            else
            // Multicolor bitmap and variants
            for (xc = 0; xc < src->width; xc++)
            {
                const int x = xc / 4;
                const int scroffs = scroffsy + x;
                const int bmoffs = bmoffsy + (x * 8);
                const int v = 6 - ((xc * 2) & 6);
                Uint8 c;

                if (src->type & D64_FMT_FLI)
                {
                    int vbank = 0;
                    switch (src->fliType)
                    {
                        case D64_FLI_2BANK:
                            vbank = yb / 4;
                            break;
                        case D64_FLI_4BANK:
                            vbank = yb / 2;
                            break;
                        case D64_FLI_8BANK:
                            vbank = yb;
                            break;
                    }
                    c = dmC64GetMCColor(src, (src->bitmap[0][bmoffs] >> v) & 3, 0, vbank, scroffs);
                    *d++ = c;
                }
                else
                if (src->type & D64_FMT_ILACE)
                {
                    *d++ = dmC64GetMCColor(src, (src->bitmap[0][bmoffs] >> v) & 3, 0, src->laceBank1, scroffs);
                    *d++ = dmC64GetMCColor(src, (src->bitmap[1][bmoffs] >> v) & 3, 0, src->laceBank2, scroffs);
                }
                else
                {
                    c = dmC64GetMCColor(src, (src->bitmap[0][bmoffs] >> v) & 3, 0, 0, scroffs);
                    *d++ = c;
                }
            }
        }
        dp += dst->pitch;
    }

    return DMERR_OK;
}


int dmC64ConvertBMP2Image(DMImage **pdst, const DMC64Image *src, const DMC64ImageFormat *fmt)
{
    int res;
    DMImage *dst;

    if (pdst == NULL || src == NULL)
        return DMERR_NULLPTR;

    // Allocate image structure
    if ((*pdst = dst = dmImageAlloc(src->width, src->height, DM_IFMT_PALETTE, -1)) == NULL)
        return DMERR_MALLOC;

    // Set palette
    dst->pal      = (DMColor *) &dmC64Palette;
    dst->ncolors  = C64_NCOLORS;
    dst->constpal = TRUE;

    // Convert
    if (fmt->convertFrom != NULL)
        res = fmt->convertFrom(dst, src);
    else
        res = dmC64ConvertGenericBMP2Image(dst, src);

    return res;
}


int dmC64DecodeBMP(DMC64Image **img, const Uint8 *buf, const size_t len,
    const size_t probeOffs, const size_t loadOffs,
    const DMC64ImageFormat **fmt, const DMC64ImageFormat *forced)
{
    if (img == NULL)
        return DMERR_NULLPTR;

    // Check for forced format
    if (forced != NULL)
        *fmt = forced;
    else
    {
        // Nope, perform a generic probe
        if (probeOffs >= len)
            return DMERR_INVALID_DATA;

        if (dmC64ProbeBMP(buf + probeOffs, len - probeOffs, fmt) == DM_PROBE_SCORE_FALSE)
            return DMERR_INVALID_DATA;
    }

    if (loadOffs >= len)
        return DMERR_INVALID_ARGS;

    if (*fmt == NULL)
        return DMERR_INVALID_DATA;

    // Allocate memory
    if ((*img = dmC64ImageAlloc((*fmt)->width, (*fmt)->height,
        (*fmt)->ch_width, (*fmt)->ch_height)) == NULL)
        return DMERR_MALLOC;

    // Decode the bitmap to memory layout
    if ((*fmt)->decode != NULL)
        return (*fmt)->decode(*img, buf + loadOffs, len - loadOffs, *fmt);
    else
        return dmC64DecodeGenericBMP(*img, buf + loadOffs, len - loadOffs, *fmt);
}