0
votes

I'm trying to use ZLIB to inflate (decompress) .FLA files, thus extracting all its contents. Since FLA files use a ZIP format, I am able to read the local file headers(https://en.wikipedia.org/wiki/Zip_(file_format)) from it, and use the info inside to decompress the files.

It seems to work fine for regular text-based files, but when it comes to binary (I've only tried PNG and DAT files), it fails to decompress them, returning "Z_DATA_ERROR".

I'm unable to use the minilib library inside ZLIB, since the Central directory file header inside FLA files differs slightly from normal zip files (which is why im reading the local files header manually).

Here's the code I use to decompress a chunk of data:

void DecompressBuffer(char* compressedBuffer, unsigned int compressedSize, std::string& out_decompressedBuffer)
{
    // init the decompression stream
    z_stream stream;

    stream.zalloc = Z_NULL;
    stream.zfree = Z_NULL;
    stream.opaque = Z_NULL;
    stream.avail_in = 0;
    stream.next_in = Z_NULL;
    if (int err = inflateInit2(&stream, -MAX_WBITS) != Z_OK)
    {
        printf("Error: inflateInit %d\n", err);
        return;
    }

    // Set the starting point and total data size to be read
    stream.avail_in = compressedSize;
    stream.next_in  = (Bytef*)&compressedBuffer[0];

    std::stringstream strStream;

    // Start decompressing
    while (stream.avail_in != 0)
    {
        unsigned char* readBuffer = (unsigned char*)malloc(MAX_READ_BUFFER_SIZE + 1);
        readBuffer[MAX_READ_BUFFER_SIZE] = '\0';
        stream.next_out = readBuffer;
        stream.avail_out = MAX_READ_BUFFER_SIZE;

        int ret = inflate(&stream, Z_NO_FLUSH); 

        if (ret == Z_STREAM_END) 
        {
            // only store the data we have left in the stream
            size_t length = MAX_READ_BUFFER_SIZE - stream.avail_out;
            std::string str((char*)readBuffer);
            str = str.substr(0, length);
            strStream << str;
            break;
        }
        else
        {       
            if (ret != Z_OK)
            {
                printf("Error: inflate %d\n", ret); // This is what it reaches when trying to inflate a PNG or DAT file
                break;
            }

            // store the readbuffer in the stream
            strStream << readBuffer;
        }

        free(readBuffer);
    }

    out_decompressedBuffer = strStream.str();

    inflateEnd(&stream);
}

I have tried zipping a single PNG file and extracing that. This doesn't return any errors from Inflate(), but doesn't correctly inflate the PNG either, and the only corresponding values seem to be the first few.

The original file (left) and the uncompressed via code file (right):

Hex editor versions of both PNGs

1
Can you please share details on how you got the inflating to work? I am attempting to do it but I am getting "invalid or incomplete deflate data". Is using some inflateInit settings the key or is there more to it? I will be gratefull if you can elaborate on what you have done reading the headers and the differences you have mentioned about the central directory.Ognyan M

1 Answers

1
votes

You do things that rely on the data being text and strings, not binary data.

For example

std::string str((char*)readBuffer);

If the contents of readBuffer is raw binary data then it might contain one or more zero bytes in the middle of it. When you use it as a C-style string then the first zero will act as the string terminator character.

I suggest you try to generalize it, and remove the dependency of strings. Instead I suggest you use e.g. std::vector<int8_t>.

Meanwhile, during your transition to a more generalized way, you can do e.g.

std::string str(readBuffer, length);

This will create a string of the specified length, and the contents will not be checked for terminators.