Z_DATA_ERROR 中途充气

Z_DATA_ERROR midway through inflate

我需要解压缩在游戏的保存数据中找到的一些 zlib 压缩文件。我无法访问游戏的源代码。每个文件都以 0x789C 开头,这告诉我它们确实是用 zlib 压缩的。但是,所有对这些文件进行膨胀的调用都无法完全解压缩并且 return Z_DATA_ERROR。使用 zlib 版本 1.2.5、1.2.8 和 1.2.11 得到相同的结果。

尽管 zlib 告诉我输入数据已损坏,但我相信这不是问题,因为游戏能够毫无问题地解压缩这些文件,而且这并非孤立于单个数据流。我有 成百上千 的独特数据流以相同的方式压缩,它们都在解压过程中的某个地方抛出一个 Z_DATA_ERROR

另外,解压成功的部分解压数据是正确的。输出完全符合预期。

此外,大约有 10% 的时间,zlib 会解压缩整个文件,但结果不正确。大块的解压缩数据包含一遍又一遍重复的相同字节,这告诉我这是误报。

这是我的解压代码:

//QByteArray is a Qt wrapper for a char *
QByteArray Compression::DecompressData(QByteArray data)
{
    QByteArray result;

    int ret;
    z_stream strm;
    static const int CHUNK_SIZE = 1;//set to 1 just for debugging
    char out[CHUNK_SIZE];

    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;
    strm.avail_in = data.size();
    strm.next_in = (Bytef*)(data.data());

    ret = inflateInit2(&strm, -15);
    if (ret != Z_OK)
    {
        qDebug() << "init error" << ret;
        return QByteArray();
    }

    do
    {
        strm.avail_out = CHUNK_SIZE;
        strm.next_out = (Bytef*)(out);

        ret = inflate(&strm, Z_NO_FLUSH);
        qDebug() << "debugging output: " << ret << QString::number(strm.total_in, 16);//This tells me which input byte caused the failure
        Q_ASSERT(ret != Z_STREAM_ERROR);

        switch (ret)
        {
        case Z_NEED_DICT:
            ret = Z_DATA_ERROR;
        case Z_DATA_ERROR:
        case Z_MEM_ERROR:
            (void)inflateEnd(&strm);
            return result;
        }

        result.append(out, CHUNK_SIZE - strm.avail_out);
    } while (strm.avail_out == 0);

    inflateEnd(&strm);
    return result;
}

这是示例文件数据 compressed data 的 pastebin,删除了 0x789C 和尾随的 CRC。我可以提供无穷无尽的示例文件。他们都有同样的问题。

运行 通过上述函数的数据将正确解压缩流的开头,但在输入字节 0x18C 上失败。当文件的开头以 0x000B 开头并且解压缩的数据比输入数据长时,您可以告诉它正确解压缩。

我希望我了解更多关于 deflate 压缩的知识来自己解决这个问题。我最初的想法是游戏已经决定使用自定义版本的 zlib 或者需要给 zlib 一个额外的参数才能正确解压它。几天来我四处打听并尝试了很多东西,我真的需要有这方面知识的人来权衡一下。感谢您的宝贵时间!

提供的数据确实是一个无效的 deflate 流,两者距离太远,并且在 deflate 流结束后有八个字节的垃圾。您的代码没有明显的错误。

如您所述,在偏移量 396 处,十个距离中的第一个距离太远了。这就是 inflate 停止的地方。在偏移量 3472 处,几乎在末尾,有一个存储块,其长度不检查其补码。

对于太远的距离,您可以尝试在 inflateInit2() 之后使用 inflateSetDictionary() 设置一个 32K 零字节的字典。然后减压将继续进行,用零填充给定的位置。这可能是也可能不是游戏正在做的事情。存储块错误没有明显的补救措施。

事实上,游戏作者可能故意通过修改 zlib 以供自己使用来扰乱您或任何试图解压缩其内部数据的人。