view src/dmzlib.c @ 2208:90ec1ec89c56

Revamp the palette handling in lib64gfx somewhat, add helper functions to lib64util for handling external palette file options and add support for specifying one of the "internal" palettes or external (.act) palette file to gfxconv and 64vw.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 14 Jun 2019 05:01:12 +0300
parents 749f8f808531
children 36edd316184a
line wrap: on
line source

/*
 * DMLib
 * -- ZLib implementation
 * Public domain zlib decode v0.2 by Sean Barrett 2006-11-18
 * Modified and reformatted for DMLib by Matti 'ccr' Hamalainen
 *
 * For more information, refer to following RFCs:
 * http://tools.ietf.org/html/rfc1950 - ZLIB compressed data format
 * http://tools.ietf.org/html/rfc1951 - DEFLATE format
 *
 */
#include "dmzlib.h"


#define DM_ZLIB_TMPBUF_SIZE (16 * 1024)
#define DMZLIB_ASSERT(x) //do { printf("%s: %d\n", # x, x); } while (0) // dummy


// @TODO: should statically initialize these for optimal thread safety
static Uint8 *dm_zdefault_length = NULL,
             *dm_zdefault_distance = NULL;


int dmZLibInit()
{
    int i;

    // Use <= to match clearly with DEFLATE spec
    if ((dm_zdefault_length   = dmMalloc(DM_ZLIB_HUFF_CODES)) == NULL ||
        (dm_zdefault_distance = dmMalloc(DM_ZLIB_HUFF_DIST)) == NULL)
    {
        return dmErrorDBG(DMERR_MALLOC,
            "Failed to allocate zlib decompression tables.\n");
    }

    // Literals 0  ..143:  8 bits, codes 00110000 .. 10111111
    for (i = 0; i <= 143; i++)
        dm_zdefault_length[i] = 8;

    // Literals 144..255:  9 bits, codes 110010000 .. 111111111
    for (; i <= 255; i++)
        dm_zdefault_length[i] = 9;

    // Literals 256..279:  7 bits, codes 0000000 .. 0010111
    for (; i <= 279; i++)
        dm_zdefault_length[i] = 7;

    // Literals 280..287:  8 bits, codes 11000000 .. 11000111
    for (; i <= 287; i++)
        dm_zdefault_length[i] = 8;

    // Default distances
    for (i = 0; i <= 31; i++)
        dm_zdefault_distance[i] = 5;

    return DMERR_OK;
}


void dmZLibClose()
{
    dmFree(dm_zdefault_length);
    dm_zdefault_length = NULL;

    dmFree(dm_zdefault_distance);
    dm_zdefault_distance = NULL;
}


static inline int dmBitReverse16(int n)
{
    // "Reverse" a 16bit word through bitshifts
    n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1);
    n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2);
    n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4);
    n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8);
    return n;
}


// To bit reverse N bits, reverse 16 and shift
// e.g. 11 bits, bit reverse and shift away 5
static inline int dmBitReverseN(int v, const int bits)
{
    DMZLIB_ASSERT(bits <= 16);
    return dmBitReverse16(v) >> (16 - bits);
}


static int dmZLibBuildHuffmanTables(DMZHuffmanContext * ctx, const Uint8 * sizelist, const int num)
{
    int i, k = 0;
    int code, nextCode[16], sizes[17];

    dmMemset(sizes, 0, sizeof(sizes));
    dmMemset(ctx->fast, 0, sizeof(ctx->fast));

    for (i = 0; i < num; i++)
        sizes[sizelist[i]]++;

    sizes[0] = 0;
    for (i = 1; i < 16; i++)
    {
        if (sizes[i] > (1 << i))
        {
            return dmErrorDBG(DMERR_INTERNAL,
                "Sizes assert failed while building Huffman codes.\n");
        }
    }

    code = 0;
    for (i = 1; i < 16; i++)
    {
        nextCode[i] = code;
        ctx->firstCode[i] = (Uint16) code;
        ctx->firstSymbol[i] = (Uint16) k;
        code += sizes[i];

        if (sizes[i] && code - 1 >= (1 << i))
        {
            return dmErrorDBG(DMERR_INVALID_DATA,
                "Bad Huffman code lengths.\n");
        }

        ctx->maxCode[i] = code << (16 - i);       // preshift for inner loop
        code <<= 1;
        k += sizes[i];
    }

    ctx->maxCode[16] = 0x10000;   // sentinel
    for (i = 0; i < num; i++)
    {
        int s = sizelist[i];
        if (s)
        {
            int c = nextCode[s] - ctx->firstCode[s] + ctx->firstSymbol[s];
            Uint16 fastv = (Uint16) ((s << 9) | i);
            ctx->size[c] = (Uint8) s;
            ctx->value[c] = (Uint16) i;
            if (s <= DM_ZLIB_HFAST_BITS)
            {
                int k = dmBitReverseN(nextCode[s], s);
                while (k < DM_ZLIB_HFAST_SIZE)
                {
                    ctx->fast[k] = fastv;
                    k += (1 << s);
                }
            }
            nextCode[s]++;
        }
    }

    ctx->initialized = TRUE;
    return DMERR_OK;
}


static inline Uint8 dmZGet8(DMZLibContext * ctx)
{
    if (ctx->inBuffer >= ctx->inBufferEnd)
        return 0;

    return *ctx->inBuffer++;
}


static void dmZFillBits(DMZLibContext * ctx)
{
    do
    {
        DMZLIB_ASSERT(ctx->codeBuffer < (1U << ctx->numBits));
        ctx->codeBuffer |= dmZGet8(ctx) << ctx->numBits;
        ctx->numBits += 8;
    }
    while (ctx->numBits <= 24);
}


static inline unsigned int dmZReceive(DMZLibContext * ctx, int n)
{
    unsigned int val;

    if (ctx->numBits < n)
        dmZFillBits(ctx);

    val = ctx->codeBuffer & ((1 << n) - 1);
    ctx->codeBuffer >>= n;
    ctx->numBits -= n;

    return val;
}


static int dmZLibHuffmanDecodeSlow(DMZLibContext * ctx, DMZHuffmanContext * huff, int *val)
{
    int b, s, k;
    *val = 0;

    // not resolved by fast table, so compute it the slow way
    // use jpeg approach, which requires MSbits at top
    k = dmBitReverse16(ctx->codeBuffer);
    for (s = DM_ZLIB_HFAST_BITS + 1; ; s++)
    {
        if (k < huff->maxCode[s])
            break;
    }

    if (s == 16)
    {
        return dmErrorDBG(DMERR_DATA_ERROR,
            "Bad Huffman code.\n");
    }

    // code size is s, so:
    b = (k >> (16 - s)) - huff->firstCode[s] + huff->firstSymbol[s];
    DMZLIB_ASSERT(huff->size[b] == s);

    ctx->codeBuffer >>= s;
    ctx->numBits -= s;
    *val = huff->value[b];

    return DMERR_OK;
}


static inline int dmZLibHuffmanDecode(DMZLibContext * ctx, DMZHuffmanContext * huff, int *val)
{
    int b;
    if (ctx->numBits < 16)
        dmZFillBits(ctx);

    b = huff->fast[ctx->codeBuffer & DM_ZLIB_HFAST_MASK];
    if (b)
    {
        int s = b >> 9;
        ctx->codeBuffer >>= s;
        ctx->numBits -= s;
        *val = b & 511;
        return DMERR_OK;
    }

    // Not resolved by fast table, so compute it the slow way
    return dmZLibHuffmanDecodeSlow(ctx, huff, val);
}


static int dmZLibExpand(DMZLibContext * ctx, Uint8 *outBuffer, size_t n)
{
    Uint8 *newBuf;
    size_t cur, limit;

    ctx->outBuffer = outBuffer;

    if (!ctx->expandable)
    {
        return dmErrorDBG(DMERR_BOUNDS,
            "Output buffer limit hit, and is not expandable.\n");
    }

    cur   = ctx->outBuffer - ctx->outBufferStart;
    limit = ctx->outBufferEnd - ctx->outBufferStart;

    while (cur + n > limit)
        limit *= 2;

    if ((newBuf = (Uint8 *) dmRealloc(ctx->outBufferStart, limit)) == NULL)
    {
        return dmErrorDBG(DMERR_MALLOC,
            "Could not reallocate buffer.\n");
    }

    ctx->outBufferStart = newBuf;
    ctx->outBuffer      = newBuf + cur;
    ctx->outBufferEnd   = newBuf + limit;

    return DMERR_OK;
}


static const int dm_zlib_length_base[31] =
{
    3, 4, 5, 6, 7, 8, 9, 10, 11, 13,
    15, 17, 19, 23, 27, 31, 35, 43, 51, 59,
    67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0
};

static const int dm_zlib_length_extra[31] =
{
    0, 0, 0, 0, 0, 0, 0, 0,
    1, 1, 1, 1,
    2, 2, 2, 2,
    3, 3, 3, 3,
    4, 4, 4, 4,
    5, 5, 5, 5,
    0, 0, 0
};

static const int dm_zlib_dist_base[32] =
{
    1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
    257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193,
    12289, 16385, 24577, 0, 0
};

static const int dm_zlib_dist_extra[32] =
{
    0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8,
    9, 9, 10, 10, 11, 11, 12, 12, 13, 13
};


static int dmZLibParseHuffmanBlock(DMZLibContext * ctx)
{
    Uint8 *outBuffer = ctx->outBuffer;
    for (;;)
    {
        int z, ret;
        if ((ret = dmZLibHuffmanDecode(ctx, &ctx->zlength, &z)) != DMERR_OK)
            return ret;

        if (z < 256)
        {
            if (outBuffer >= ctx->outBufferEnd)
            {
                if ((ret = dmZLibExpand(ctx, outBuffer, 1)) != DMERR_OK)
                    return ret;

                outBuffer = ctx->outBuffer;
            }
            *outBuffer++ = (Uint8) z;
        }
        else
        {
            Uint8 *ptr;
            int len, dist;
            if (z == 256)
            {
                ctx->outBuffer = outBuffer;
                return DMERR_OK;
            }

            z -= 257;

            len = dm_zlib_length_base[z];
            if (dm_zlib_length_extra[z])
                len += dmZReceive(ctx, dm_zlib_length_extra[z]);

            if ((ret = dmZLibHuffmanDecode(ctx, &ctx->zdistance, &z)) != DMERR_OK)
                return ret;

            dist = dm_zlib_dist_base[z];
            if (dm_zlib_dist_extra[z])
                dist += dmZReceive(ctx, dm_zlib_dist_extra[z]);

            if (outBuffer - ctx->outBufferStart < dist)
            {
                return dmErrorDBG(DMERR_DATA_ERROR,
                    "Bad Huffman block distance.\n");
            }

            // Check if output buffer needs to expand
            if (outBuffer + len > ctx->outBufferEnd)
            {
                if ((ret = dmZLibExpand(ctx, outBuffer, len)) != DMERR_OK)
                    return ret;
                outBuffer = ctx->outBuffer;
            }

            ptr = outBuffer - dist;
            if (dist == 1)
            {                   // run of one byte; common in images.
                Uint8 v = *ptr;
                do { *outBuffer++ = v; } while (--len);
            }
            else
            {
                do { *outBuffer++ = *ptr++; } while (--len);
            }
        }
    }
}


#define DM_ZLIB_NCODELENGTH_SIZES 19

static const Uint8 dm_zlib_length_dezigzag[DM_ZLIB_NCODELENGTH_SIZES] =
{
    16, 17, 18,  0,  8,  7,  9,
    6 , 10, 5 , 11,  4, 12,  3,
    13, 2 , 14, 1 , 15
};


static int dmZLibComputeHuffmanCodes(DMZLibContext * ctx)
{
    DMZHuffmanContext codeLengthCtx;
    Uint8 codeLengthSizes[DM_ZLIB_NCODELENGTH_SIZES];
    Uint8 codeLengths[288 + 32 + 137]; // padding for maximum single op
    int i, n, ret;

    // Get table sizes
    int hlit  = dmZReceive(ctx, 5) + 257; // 288 = X
    int hdist = dmZReceive(ctx, 5) + 1;   // 32  = X
    int hclen = dmZReceive(ctx, 4) + 4;   // 19  = DM_ZLIB_NCODELENGTH_SIZES

    if (hlit > 286 || hdist > 30)
    {
        return dmErrorDBG(DMERR_INVALID_DATA,
            "Too many Huffman length or distance symbols (%d, %d)",
            hlit, hdist);
    }

    // Get lengths table (uninitialized entries should be set to 0)
    codeLengths[256] = 0;
    dmMemset(codeLengthSizes, 0, sizeof(codeLengthSizes));
    for (i = 0; i < hclen; i++)
    {
        int s = dmZReceive(ctx, 3);
        codeLengthSizes[dm_zlib_length_dezigzag[i]] = (Uint8) s;
    }

    // Inflate the code lengths table
    if ((ret = dmZLibBuildHuffmanTables(&codeLengthCtx,
        codeLengthSizes, DM_ZLIB_NCODELENGTH_SIZES)) != DMERR_OK)
    {
        return dmErrorDBG(ret,
            "Invalid code lengths set.\n");
    }

    n = 0;
    while (n < hlit + hdist)
    {
        int c;
        if ((ret = dmZLibHuffmanDecode(ctx, &codeLengthCtx, &c)) != DMERR_OK)
            return ret;

        DMZLIB_ASSERT(c >= 0 && c < DM_ZLIB_NCODELENGTH_SIZES);

        if (c < 16)
            codeLengths[n++] = (Uint8) c;
        else
        if (c == 16)
        {
            int bv = dmZReceive(ctx, 2) + 3;
            if (n == 0)
            {
                return dmErrorDBG(DMERR_INVALID_DATA,
                    "Invalid bit length repeat.\n");
            }
            dmMemset(codeLengths + n, codeLengths[n - 1], bv);
            n += bv;
        }
        else
        if (c == 17)
        {
            int bv = dmZReceive(ctx, 3) + 3;
            dmMemset(codeLengths + n, 0, bv);
            n += bv;
        }
        else
        {
            int bv = dmZReceive(ctx, 7) + 11;
            dmMemset(codeLengths + n, 0, bv);
            n += bv;
        }
    }

    if (codeLengths[256] == 0)
    {
        return dmErrorDBG(DMERR_DATA_ERROR,
            "Invalid code -- missing end-of-block.\n");
    }

    if (n != hlit + hdist)
    {
        return dmErrorDBG(DMERR_DATA_ERROR,
            "Bad huffman codelengths.\n");
    }

    // Build the code tables
    if ((ret = dmZLibBuildHuffmanTables(&ctx->zlength, codeLengths, hlit)) != DMERR_OK)
    {
        return dmErrorDBG(DMERR_DATA_ERROR,
            "Invalid literal/lengths set.\n");
    }

    if ((ret = dmZLibBuildHuffmanTables(&ctx->zdistance, codeLengths + hlit, hdist)) != DMERR_OK)
    {
        return dmErrorDBG(DMERR_DATA_ERROR,
            "Invalid literal distances set.\n");
    }

    return DMERR_OK;
}


static int dmZLibParseUncompressedBlock(DMZLibContext * ctx)
{
    Uint8 header[4];
    size_t len, nlen, k, ret;

    // "Any bits of input up to the next byte boundary are ignored."
    if (ctx->numBits & 7)
        dmZReceive(ctx, ctx->numBits & 7);

    // Get the bit-packed data into header
    k = 0;
    while (ctx->numBits > 0)
    {
        header[k++] = (Uint8) (ctx->codeBuffer & 255);   // suppress MSVC run-time check
        ctx->codeBuffer >>= 8;
        ctx->numBits -= 8;
    }
    DMZLIB_ASSERT(ctx->numBits == 0);

    // now fill header the normal way
    while (k < 4)
        header[k++] = dmZGet8(ctx);

    len  = (header[1] << 8) | header[0];
    nlen = (header[3] << 8) | header[2];

    if (nlen != (len ^ 0xffff))
    {
        return dmErrorDBG(DMERR_DATA_ERROR,
            "Compressed data corrupt %04x :: %04x [%04x].\n",
            nlen, len, len ^ 0xffff);
    }

    if (len > 0)
    {
        if (ctx->inBuffer + len > ctx->inBufferEnd)
        {
            return dmErrorDBG(DMERR_BOUNDS,
                "Read past buffer, probably corrupt compressed data.\n");
        }

        // Check if output buffer needs to expand
        if (ctx->outBuffer + len > ctx->outBufferEnd &&
            (ret = dmZLibExpand(ctx, ctx->outBuffer, len)) != DMERR_OK)
        {
            return dmErrorDBG(DMERR_DATA_ERROR,
                "Could not expand output buffer: %d, %s\n",
                ret, dmErrorStr(ret));
        }

        // Copy uncompressed data
        memcpy(ctx->outBuffer, ctx->inBuffer, len);
        ctx->inBuffer  += len;
        ctx->outBuffer += len;
    }

    return DMERR_OK;
}


int dmZLibInitInflate(DMZLibContext *ctx)
{
    if (ctx == NULL)
        return DMERR_NULLPTR;

    dmMemset(ctx, 0, sizeof(DMZLibContext));
    return DMERR_OK;
}


void dmZLibCloseInflate(DMZLibContext *ctx)
{
    if (ctx != NULL)
    {
        dmMemset(ctx, 0, sizeof(DMZLibContext));
    }
}


int dmZLibInflate(DMZLibContext * ctx)
{
    int final, type, ret;

    ctx->numBits = 0;
    ctx->codeBuffer = 0;
    do
    {
        if (ctx->inBuffer == ctx->inBufferEnd)
            break;

        final = dmZReceive(ctx, 1);
        type  = dmZReceive(ctx, 2);
        if (type == 0)
        {
            if ((ret = dmZLibParseUncompressedBlock(ctx)) != DMERR_OK)
                return ret;
        }
        else
        if (type == 3)
        {
            return dmErrorDBG(DMERR_INVALID_DATA,
                "Invalid zlib block type.\n");
        }
        else
        {
            if (type == 1 && !ctx->zlength.initialized)
            {
                // use fixed code lengths
                if ((ret = dmZLibBuildHuffmanTables(&ctx->zlength, dm_zdefault_length, DM_ZLIB_HUFF_CODES)) != DMERR_OK)
                    return ret;

                if ((ret = dmZLibBuildHuffmanTables(&ctx->zdistance, dm_zdefault_distance, DM_ZLIB_HUFF_DIST)) != DMERR_OK)
                    return ret;
            }
            else
            if ((ret = dmZLibComputeHuffmanCodes(ctx)) != DMERR_OK)
                return ret;

            if ((ret = dmZLibParseHuffmanBlock(ctx)) != DMERR_OK)
                return ret;
        }
    }
    while (!final);

    return DMERR_OK;
}


int dmZLibParseHeader(DMZLibContext * ctx, BOOL checkPNG)
{
    // See http://tools.ietf.org/html/rfc1950
    int cmf     = dmZGet8(ctx); // Compression method and flags
    int flags   = dmZGet8(ctx); // Flags
    int cmethod = (cmf & 15);
    int cinfo   = (cmf >> 4) & 15;
    ctx->window = 1 << (8 + cinfo); // Window size (not used at the moment)

    // "The FCHECK value must be such that CMF and FLG, when viewed as
    // a 16-bit unsigned integer stored in MSB order (CMF*256 + FLG),
    // is a multiple of 31."
    if ((cmf * 256 + flags) % 31 != 0)
    {
        return dmErrorDBG(DMERR_INVALID_DATA,
            "Bad zlib header.");
    }

    // We only support compression method 8
    if (cmethod != 8)
    {
        return dmErrorDBG(DMERR_INVALID_DATA,
            "Bad or unsupported zlib compression method %d.\n",
            cmethod);
    }

    if (checkPNG && (flags & 32))
    {
        // preset dictionary not allowed in png
        return dmErrorDBG(DMERR_NOT_SUPPORTED,
            "Preset dictionary not allowed in PNG.\n");
    }
    return DMERR_OK;
}