求助开源项目 UnityPy 中 Python 的 lzma.LZMADecompressor 行为和 7z 不一致,有没有人知道问题出在哪里?

129 天前
 drymonfidelia

场景 (嫌长跳过)

最近水 Discord 知道一个朋友在尝试开发战女的私服( 一款 2019 年停服的日本抽卡游戏,制作挺良心的)想起来以前上学的时候也玩过一段时间这游戏,有点情怀了,刚好我以前开发过几款停服游戏的私服 /t/1033715 /t/1049388 ,算是很有经验了,想帮他一起开发。

这个游戏虽然是手游,但 asset 加载完全按照页游的逻辑,没有提供任何预载当前用不上资源的接口,导致我们不可能在停服前对游戏资源文件进行完整备份。同时资源缓存放置在 /data/data/游戏包名/cache 底下,除非 ROOT 或者使用第三方 Recovery ,没有任何办法能够提取它的文件,各种手机的备份都是调用的 BackupService ,它备份不到 cache 目录下面的东西。这导致了即使我们能联系上很多还没有卸载游戏的朋友,能提供缓存文件备份的也是非常少数。

更逆天的一点是这个游戏每个有加密的资源文件都使用了完全不同的密钥进行 AES 加密,和文件的下载链接一起通过 API 下发。如果没有备份到对应的 API 返回解密后的内容(全部 API 响应用每个用户都不同的 AES 密钥二次加密,只有首次登录的时候会返回这个密钥,首次登录返回的内容用一个 .so 库返回的初始密钥加密)。这导致成功提取出缓存文件还有相当一部分不能用。

好在我们根据现有的存档数据完成游戏的基础功能开发后,有很多位朋友也贡献出了他们的曾经备份的文件,但是因为前面这些原因,这些文件中有相当一部分不能用的。有的是有加密但没备份到密钥,我们需要把这部分文件筛出来不能让游戏加载,有的是因为 target 是 iOS ,我们想要通过重打包来实现在 Android 上使用(纹理等做不到无损转换的,有损也总比没有好),有的 size 和 API 中返回的对应的上,但就是没有办法通过任何工具载入,不知道哪里出了问题。同时这个游戏的缓存文件名 hash 方法入参只有资源名,算出来的结果和版本无关,我们需要保留最新、可用的一份。这个游戏的 Asset Bundle 非常多,我们收集到的去重后就有接近 5 万个,这三件事显然要用脚本来完成。(写这么长也是想问问有没有 V 友有更好的思路)

问题

因为以前用过 UnityPy 这个包来编辑 Asset Bundle ,所以也是想到了用这个包来写脚本。这个包是 AssetStudio 的 Python port ,AssetStudio 能够载入这款游戏的大部分资源文件,但是 UnityPy 没办法载入这款游戏的任何资源文件,报错

_lzma.LZMAError: Corrupt input data

因为太长了,完整报错信息和样本文件请在 https://github.com/K0lb3/UnityPy/issues/237 下载。查看了 BundleFile.py 对 UnityWeb 文件的处理逻辑,和 AssetStudio 一致。修改代码打印了解析出来的压缩后的数据头尾部分、长度,和在 AssetStudio 打断点获得的数据完全一致,说明问题是出在 LZMA 解压的部分。

尝试了 LZMA 参数的各种组合都没有成功解压文件,想问一下有没有 V 友知道问题出在哪里?

1136 次点击
所在节点    Python
4 条回复
dawangyezi
129 天前
将 unitypy 库中 CompressionHelper.py 中 decompress_lzma 函数修改为:
```python
def decompress_lzma(data: bytes) -> bytes:
return lzma.LZMADecompressor(format=lzma.FORMAT_AUTO).decompress(data)
```
针对你给出的资源,我发现只要让 lzma 去推断算法就好了
让 lzma 自己去推断,不要去指定算法。关于算法推断过程因为在 C 代码中,我没有去具体调试。
dawangyezi
129 天前
我是怎么发现可以修改 decompress_lzma 来达到目的的。
1. 首先我通过命令行工具 lzma 来压缩(命令为:lzma -z a)了一个普通的文本文件 a ,它会生成一个 a.lzma 文件。
2. 通过 xxd ( hexdump 工具)我发现 a.lzma 文件的结构非常简单,一个表头表示压缩算法信息,然后紧接着就是内容区域。表头刚好就是 unitypy 中指定的 0x5d 0x00 0x00 0x80 0x00 。我又打印了 descomporess_lzma 函数参数的数据长度,和标准 lzma 算法生成的数据长度吻合。这说明只要把 decompress_lzma 的入口参数写入文件就是一个标准的 lzma 文件
3. 我使用 lzma 工具 (命令为:lzma -d a.lzma) 可以正常解压测试数据导出的文件。这说明是 decompress_lzma 实现的有问题。
4. 我通过 python 标准库的标准 lzma.open 可以打开并解压第 2 步生成的文件。这再此验证 lzma 库没有问题,是使用上的问题。
5. 综上,我推断只要将 lzma.LZMADecompressor 的参数改为默认的自动,就可以正常解压数据。实验验证猜想正确。
drymonfidelia
129 天前
@dawangyezi 真的能用,感谢,我居然没想到直接不传参数让它自己推断
julyclyde
127 天前
archive 和 compress 还是不通的吧
从#2 你的做法看来,你似乎直接入侵了 archive 内部去做 decompress 啊??

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1057468

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX