You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

zlib解压游戏存档文件中途返回Z_DATA_ERROR问题求助

Troubleshooting Zlib Decompression Failures for Game Save Data

我之前处理过不少游戏存档的逆向问题,你的情况很典型——游戏能正常解压但自己的代码反复报错,大概率是游戏对标准zlib做了自定义修改或者对压缩数据做了预处理。结合你的描述和代码,这里有几个针对性的排查方向和修复建议:

1. 先修复解压代码的逻辑漏洞

你的代码里有几个容易被忽略的问题,先把这些修正了再排查其他可能性:

  • 不合理的CHUNK_SIZE:设为1不仅效率极低,还可能导致调试时误判问题点,先改成4096这类常规值。
  • 错误的循环终止条件:你当前用while (strm.avail_out == 0),但这只判断了缓冲区是否被填满,没考虑流是否真的结束。正确的循环应该持续到ret == Z_STREAM_END。
  • Z_NEED_DICT的错误处理:当返回Z_NEED_DICT时,你直接判定为数据错误,但这很可能是游戏用了自定义预设字典——zlib允许用预定义字典提升压缩率,游戏会把字典内置在程序里。此时strm.adler会返回字典的ADLER32校验值,你可以打印这个值,然后去游戏的资源文件或内存里查找匹配的字典数据,再用inflateSetDictionary传入。

修正后的核心循环代码示例:

static const int CHUNK_SIZE = 4096;
char out[CHUNK_SIZE];
// ...(初始化代码不变)
do {
    strm.avail_out = CHUNK_SIZE;
    strm.next_out = (Bytef*)(out);
    ret = inflate(&strm, Z_NO_FLUSH);
    
    Q_ASSERT(ret != Z_STREAM_ERROR);
    switch (ret) {
        case Z_NEED_DICT:
            // 打印字典校验值,去查找对应字典
            qDebug() << "Need dictionary with Adler32:" << QString::number(strm.adler, 16);
            // 如果找到字典,调用inflateSetDictionary(&strm, dict_data, dict_len);
            break;
        case Z_DATA_ERROR:
        case Z_MEM_ERROR:
            (void)inflateEnd(&strm);
            return result;
    }
    
    int bytes_written = CHUNK_SIZE - strm.avail_out;
    result.append(out, bytes_written);
} while (ret != Z_STREAM_END);
// 确认所有输入数据都被处理
if (strm.avail_in != 0) {
    qDebug() << "Warning: Not all input data was processed!";
}

2. 排查数据预处理/变换

游戏经常会对压缩数据做简单的变换,绕过标准zlib的直接解压,常见的方式有:

  • 位序翻转:每个字节的8位顺序被反转(比如0x12 → 0x48)。你可以写个小工具对输入数据的每个字节做位翻转,再喂给zlib试试。
  • 字节XOR加密:每个字节和固定密钥(比如游戏的魔法值、版本号)异或。你可以对比你解压出的开头数据和预期的0x000B,反向推导可能的密钥。
  • 隐藏的额外数据:你说移除了尾部CRC,但有些游戏会在压缩数据前后插入额外的校验字节或长度字段,比如开头加2字节的真实数据长度,你需要把这些额外字段剔除后再解压。

3. 验证原始数据的有效性

把带0x789C头的原始数据保存成文件,用标准zlib工具(比如zlib-flate)尝试解压:

zlib-flate -uncompress < compressed.bin > output.bin

如果工具也报错,说明数据确实被游戏做了自定义处理;如果工具能正常解压,那问题就出在你的代码实现上(比如内存管理、数据传递的问题)。

4. 尝试不同的zlib初始化参数

你当前用inflateInit2(&strm, -15)表示使用原始deflate格式(不带zlib头),但有些游戏修改了zlib的窗口大小设置。可以尝试调整窗口大小参数,比如:

  • inflateInit2(&strm, 15):带zlib头的格式(但你已经移除了头,所以这个可能不适用)
  • inflateInit2(&strm, -13):使用8KB窗口的原始deflate格式
  • inflateInit(&strm):默认初始化(会自动识别zlib头,你需要把0x789C头加回去再试)

内容的提问来源于stack exchange,提问作者mrg95

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:27:38