【问题标题】:Z_DATA_ERROR midway through inflateZ_DATA_ERROR 中途膨胀
【发布时间】:2018-10-23 02:18:56
【问题描述】:

我需要解压缩在游戏保存数据中找到的一些 zlib 压缩文件。我无法访问游戏的源代码。每个文件都以0x789C 开头,这告诉我它们确实是用zlib 压缩的。但是,所有对这些文件进行 inflate 的调用都无法完全解压缩并返回 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 的粘贴箱,其中 0x789C 和尾随 CRC 已删除。我可以提供无穷无尽的示例文件。他们都有同样的问题。

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

我希望我能更多地了解放气压缩以自己解决这个问题。我最初的想法是游戏决定使用自定义版本的 zlib 或者需要为 zlib 提供额外的参数才能正确解压缩。几天来我已经四处询问并尝试了很多事情,我真的需要有这方面知识的人在这里权衡。感谢您的宝贵时间!

【问题讨论】:

  • 如果您真的想深入了解这一点,提供更多的存档游戏样本和/或识别游戏以便其他人可以制作自己的游戏可能会有所帮助。
  • @mwfearnley 在这个单一的存档游戏中有数千个这样的压缩文件
  • 哦,好吧..但这只是您粘贴的一个示例流,对吗?可能查看多个流可以找到数据被破坏的一致方式......另外,我想知道您如何能够验证部分数据的正确性?
  • 我能够验证正确性,因为我知道输出中会发生什么。我对这个特定游戏的保存格式非常有经验,但是它只是这个游戏的一个版本具有不同的压缩。我试图批量解压缩所有流。他们中的一小部分确实解压缩而没有错误,但数据仍然部分错误。如果有帮助,我可能会发布所有的信息流

标签: c++ zlib compression deflate inflate


【解决方案1】:

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

正如您所指出的,在偏移量 396 处,十个距离中的第一个距离太远了。这就是通货膨胀停止的地方。在偏移量 3472 处,几乎在最后,有一个长度不检查其补码的存储块。

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

确实,游戏作者可能故意与您或任何试图解压其内部数据的人混淆,他们修改了 zlib 以供自己使用。

【讨论】:

  • 谢谢马克!是的,我的意思是抵消。我发现你超过了字节 396 很有趣。我尝试过的所有其他软件也没有超过 396。我很想知道它是否还依赖于任何给定时间内存中的其他东西?有趣的是......在 zlib 完成但输出在不应该存在的地方有大量重复数据的情况下,它总是在同一个位置。对我来说绝对是一个指标,它不仅仅是加扰,而是自定义压缩。你能说出他们使用了什么定制吗?还是比这更复杂?
  • 你能把你的解压输出发给我吗?
  • 我的错——我错过了我的工具中较早的错误消息。它将停在 396。请参阅更新的答案。
  • 感谢您提供宝贵的信息!你先生是个传奇:)
  • 在第一个块上设置了“最后一个块”位标志(源中的第一个八位字节是奇数),所以它后面没有块 - 最后 7 个字节不是流,虽然0f 98 79 17 可能是校验和。似乎唯一的问题是读取太远了。如果字典没有用零填充,您可以通过将这些部分与正确的输出进行比较来重建它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-27
  • 1970-01-01
  • 1970-01-01
  • 2018-05-12
  • 2011-09-23
  • 2016-09-29
  • 1970-01-01
相关资源
最近更新 更多