除了Unity格式的资源,对于通用格式的资源,比如csv,json,xml,lua文件等,可能也包含非常重要的信息,并且文件尺寸通常不大。就可以用前面提到的方式,打包时做对称加密,运行期先读到内存做解密,然后加载初始化。
需要注意的是,不管加密什么格式的资源,加密的密钥务必要隐藏好,至少不要用明文字符串,应在运行期用算法动态生成,然后尽可能让这个函数不容易被发现和读懂。每发布一次版本,都可以更换一次密钥,使得破解者用老版本的密钥无法破解新版本的资源。
另外,网上有VirBox Protector这种加固工具,也包含了资源加密的功能。
玩家存档加密
重要的数据都需要加密。和资源一样,玩家存档本质也是一种重要的数据,会序列化成文件,所以加密思路和资源加密类似。不同的是存档数据由玩家玩的时候动态生成,而且可能在不同代码版本间流通,需要考虑兼容性。对于强联网游戏,玩家存档数据中重要的部分都存储在服务端,只要设计得当,客户端无论如何怎么修改数据,都不会导致严重的后果。但对于弱联网游戏,玩家在没联网的情况也能玩,就不得不以客户端的数据为主导,防破解的难度很大。
存档可存放在自定义的文件中,这种情况下加密方式可以和资源加密一样。对于Unity包,本地存档常放在PlayerPrefs中,本质上是键值对,我们无法对PlayerPrefs整个文件操作,就可以对键和值分别做加密,或只对值做加密。和资源加密一样,注意保护好加密密钥。如果要更换密钥,需要处理数据的前后兼容问题。除了文件加密外,玩家存档在内存中的数据应做内存加密。
一种破解方式是,玩家把自己的存档文件传到网上,其他玩家下载下来复制到本地,实现存档转移。比如有些游戏淘宝上就有卖家将高进度或破解后的个人存档出售。为了防御这种情况,可以让一个玩家的存档包含了自己的标识符信息,使得在另一个玩家的设备上无法打开。一个简单的方案是,存档的加密密钥有玩家UDID或设备ID参与,比如用原始密钥和UDID做异或拼接等操作,或者原始密钥和UDID的MD5做异或操作。
时间防作弊
很多游戏功能依赖于系统时间,比如体力恢复,建筑升级,各种CD时间。对于强联网游戏,所有时间都由服务端控制,比较好处理。弱联网游戏则相对比较麻烦。如果完全信任本地时间,那么玩家可通过修改本地系统时间来达到很多目的。所以,整体思路是,联网的时候完全信任网络时间。没联网的时候,就用系统本地时间。等到联网后再对时间做校正,以及做作弊判定。
网络时间可通过NTP协议或自己的服务端获取。NTP其实不太可靠,有时会连不上,建议使用自己的服务端。注意由于网络传输的延时及不稳定性,获取到的网络时间会在真实时间值附近波动,所以在作弊判定时,应留有足够的阈值。
iOS或安卓原生层都有接口可获取设备开机到现在的流逝时间,比如在安卓上,接口是SystemClock.elapsedRealtime()。该数值不会受到玩家修改本地时间而影响,所以是一个更值得信赖的数值。但该接口的问题是设备重启后,这个数值会重新从零开始计算。
借助这个设备启动流逝时间的机制,可设计一个联网时完全可靠的时间获取逻辑,不受玩家调整本地时间的影响。方案如下:
1 游戏启动后开启协程获取网络时间,若没网络或没获取到就隔一段时间再触发,直到获取成功。
2 获取到网络时间时,记录获取到的网络时间为N1,记录此刻设备重启后流逝的时间D1。
3 以后任意时刻要获取当前的时间,就先获取此时设备重启后流逝的时间D2,计算当前时间为:
Tn = N1 + (D2 - D1)
N1,D1,D2都是完全可信赖的,所以任意时刻的Tn也是准确的。
由于访问原生层接口可能会有一定性能消耗,如果时间获取调用频率很高,就可以优化为每帧只访问一次原生层接口,缓存该值,该帧的后续操作都访问缓存的值,直到下一帧再调用原生层接口。
没联网的时候,就使用系统本地时间。再次联网时,对时间做校正,以及作弊判定。要判定玩家是否修改了系统本地时间来作弊,有如下方式:
1 正常情况下,玩家的本地时间和联网时间可能有一定差值。但只要玩家不调本地时间,该差值应几乎在某一固定值附近波动。如果检测到该差值有很大变化,就可以判定为作弊。
2 正常情况下,玩家的本地时间会一直往前走。如果检测到本地时间有后退的情况,就可以判定为作弊。
判定为作弊后,如何惩罚玩家,就取决于业务需求了。