其实交错战线相关的内容一二月份的时候就基本完成了,但是因为某些原因我没有选择第一时间公开。现在来看感觉差不多可以公开了,就和大家分享一下解密和修改的探索过程。(因为隔得有点久会缺少一些相关图片资料)

游戏的Lua脚本全部被打包在luascripts文件里,这个文件是特殊加密过的ab文件,不能直接读取。有一个笨办法,也是我在刚开服那段时间用的,先dump内存看看游戏会不会把解密后的数据完整的保存在内存当中。dump内存的方法有很多种,我用的是Zygisk-Il2CppDumper,调用`il2cpp_capture_memory_snapshot();`来获取内存快照,然后自己实现序列化转储。稍微筛查一下就在heap里找到了解密前后的ab文件数据,补充一点自动化的处理就能一键dump数据了。不过这个方法肯定不是最好的,因为在模拟器上使用magisk+Zygisk-Il2CppDumper并不能说多么方便,而且每次更新lua脚本都要启动游戏从内存dump。

所以还是得打开IDA把il2cpp.so丢进去。先去`stringliteral.json`查找一下`luascripts`常量,找到地址在IDA里打开,这样能快速找到加载`luascripts`的逻辑代码。跳过中间翻找的过程,因为时间隔得有点久了没有留档。最后成功定位一个叫做`DdooEennccyypptt`的函数,会在读取`luascripts`文件之后对字节数据进行处理。解密流程大致上是维护一个下标p和值a,根据p对数据与a xor。根据反编译的逻辑写出解密脚本:

def decrypt(path):
    with open(path, 'rb') as f:
        f.seek(152)
        data = bytearray(f.read())
    length = len(data)
    l = length // 100
    p = 0
    a = (length % 254) 
    while p < length:
        v = data[p]
        data[p] = v ^ a
        a = (data[p] + v) % 256
        p += l
    with open(path+'_decrypt', 'wb') as f:
        f.write(data[:-1])

解密完成了,似乎加密就应该顺理成章了。但是注意上面的脚本中有一个`f.seek(152)`这意味着有一部分数据我们暂时没考虑没处理过,如果你尝试直接替换头部数据的话会发现文件并不能被游戏读取。如果对C#的序列化/反序列化熟悉的话,其实看这个文件头就知道应该怎么解决了

不过我们还是按第一次遇到来处理。回到前面在IDA中找到的读取`luascripts`文件的地方,肯定在这里可以找到蛛丝马迹。我们可以看到有一个`System.Runtime.Serialization.Formatters.Binary`下方法对字节数组做了处理,百度可知这是一个和序列化反序列化相关的namespace,具体可以参考微软官方的文档。而这个文件头的其实就是使用`Formatters.Binary`序列化/反序列化的特征。要想在C#中调用相关方法来直接处理这个序列化文件,需要在相同的namespace(这里是Assembly-CSharp)下实现对应的类(ABCustom)的所有被标记为[Serializable]的属性。这个过程得结合`dump.cs`多加尝试。

解决这个问题之后似乎能正确的打包了,打开游戏之后也能正常加载luascripts,修改的内容也正常呈现。但是在logcat中可以看到报错提示`luascripts`文件的大小不符,下次重启游戏的时候会重新下载。这也是很多时候使用静态方法修改游戏资产时会遇到的问题,资源校验。在交错战线这里这个问题倒是很好解决,反序列化之后的ab数据是lz4压缩的,修改之后选lzma压缩然后在后面补0到对应长度就行。事实证明这样处理之后就能正确替换了,游戏没有检测到任何异常。

相关的解密封包代码见仓库CrossCore-Lua-Tool,解包出来的lua脚本会在这个仓库更新CrossCoreLuascripts

静态的资源修改的优势是不需要繁琐的权限和额外的程序,但是一旦游戏有难以绕过的校验就无能为力。