0%

我只是想给我的存档加个道具而已——《河洛群侠传》存档简易修改的记录

背景

这《河洛群侠传》,玩到一半才发现漆笑儿和开明王城图只能二选一,被这个选项恶心到了,所以我决定自己把这个道具加进我的存档里。在一顿搜索后发现了阿B上的一篇文章,于是按照文章的方法导出并修改了明文的存档。但这种方法要修改dll文件,于是我转而寻找其他更加简单的方法(但事实证明,我找的方法一个比一个麻烦_(:з)∠)_)。先是mod,但因为单个存档不能同时适应两个mod,所以最后变成了直接修改非明文存档这样的结果。

研究

用dnSpy反编译了游戏的存读档过程,发现存档文件里包含三部分数据:

  • 文件头HELUO_1_0
  • 一个包含mod名称,mod ID,游戏难度,存档时的截图等数据的struct,名字叫Header_HELUO_1_0
  • 包含其他数据的struct,名字叫GameData

其中第二和第三部分是压缩过的,所以如果要修改这两个部分必须先解压缩数据。
背包里的道具数据在第三部分,所以我的目标是对第三部分进行修改。

同时发现,对存档数据的编码、加解压使用的是MessagePack-Csharp库。

走的弯路,和用过的方法

噢,原来是MessagePack+LZ4啊,好的,我已经完全搞懂了。

由于对MessagePack一窍不通+没有发现读取存档的代码里对存档文件进行分段的操作,导致我花了好长时间才搞清楚过程。同时又因为对C#不了解,于是打算用python来写解包工具,又遇上无数的坑。
首先是LZ4MessagePackSerializer.Deserialize和LZ4MessagePackSerializer.Serialize都需要在调用时都必须要把数据的struct当作类型参数传进去,但gamedata结构有点复杂,不像Header_HELUO_1_0那样可以反编译后直接拿出来放进我的代码里,所以我决定退而求其次,手动把gamedata先分离出来,解压,然后用python的msgpack来deserialize。(万幸msgpack可以直接decode)
然后是MessagePack-Csharp的LZ4MessagePackerSerializer在压缩时,似乎并不是直接压缩,而是压缩之前还有一些重要操作。这些操作出现在调用LZ4压缩的ToLZ4BinaryCore方法和LZ4解压的DeserializeCore方法中。而我既读不懂这段代码,也找不到用python的msgpack和lz4代替的方法, 所以干脆直接照抄,把这一整段代码截下来放进我的代码里调用。

完成

最后我的补丁程序变成了python + C# dll,C# dll里包含导出存档的gamedata、将gamedata合并回去、LZ4压缩和解压缩等方法,而python则使用pythonnet库调用dll,和使用msgpack库来encode、decode导出来的gamedata。解码后的gamedata跟明文存档中一样是json格式,所以只要在inventory中添加一项:

1
{"ItemId":"it801069","Count":1,"Durability":0,"MaxDurability":0,"Weight":0,"EffectId":[],"IsNew":true,"Stolen":false,"Level":0,"Hurt":{},"HurtDifference":0.0,"ForgeMaterials":{},"QualityTitle":"","QuenchHoleCount":0,"QuenchEffect":[],"ReforgeType":-1,"Id":null}

就大功告成了。至此本菜鸟终于成功的修改了新版本的存档文件,可喜可贺。

相关文件
dll的源码
python源码
编译好的文件

(7.17 update: 原来的版本居然会影响存档……虽然不会坏档,但打完补丁之后就变成了10倍大小,还再也不能用相同的办法解包了……换成新版了。)

注:
dll中包含部分MessagePack的代码 https://github.com/neuecc/MessagePack-CSharp
MessagePack-CSharp基于MIT开源
部分代码通过反编译《河洛群侠传》获得

感想

做完之后回头看才发现原来这件事情其实很简单(╥_╥)但怎么弄了这么久 (╥_╥)
果然入门用python一时爽,泛型之类复杂一点的来一点就不会了……我这就去学C#
另外,感谢你看到这里❤如果思路或者代码让你血压拉满

那,对不起嘛