xEric_xD xEric_xD - 24 days ago 11
C++ Question

Inflate (decompress) PNG file using ZLIB library C++

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

Answer

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.

Comments