本帖最后由 guoxiaomi 于 2017-6-1 22:40 编辑
对于防止修改存档,可以手动对一些数据进行验证。比如要检查玩家是否修改了物品等,就验证 $game_party 这个量,或者细致一点,验证 $game_party.items,不过默认 @items 是不可访问的,要稍微修改一下脚本。
这样做:
1. 准备一个并没有什么用的密钥字符串(盐),比如说 r = '1234567890'
2. 存档前,把校验码写入变量 10
- v = $game_variables[10]
- r = '1234567890'
- v = Zlib::crc32(r + $game_party.items.inspect)
复制代码
3. 读档后,进行验证。
- r = '1234567890'
- v = Zlib::crc32(r + $game_party.items.inspect)
- if v != $game_variables[10]
- p "存档验证失败,即将退出游戏!"
- exit
- end
复制代码
多个数据各自调用自己的 Object#inspect 方法,然后把字符串拼接到一起验证也是可以的。crc32 只是很简单的验证手段,不过足以应付大多数不懂程序的人了,如果觉得不放心可以用 SHA-1 等更优秀的哈希算法进行验证。但是计算速度就不保证了= =
其实直接调用 Object#hash 方法也能得到对应的验证,而且在我的电脑上它的速度比生成字符串再做 crc32 快了大概3000倍,我把这个也写进去供参考~
但是直接调用 $game_party.hash 是不推荐的,如果实在要考虑性能,那最好带上盐多做几层,就像注释里面的一样。
原因是 crc32 算法是个公开的算法,而且所有语言、版本都得到一致的结果,而 Object#hash 可能不同的 ruby 版本得到不一致的结果。
刚刚不放心测试了一下,果然是有问题的,只有String (字符串) 的 hash 值是固定不变的,其他的对象的 hash 值每次游戏会发生变化。而且每次 inspect 方法得到的数据也不完全一样,所以注释的部分还是别用了(我已经删除)。Hash,Array,String,Fixnum 和 True/False/Nil 的 inspect 方法会返回相同的字符串,但是其他的 class XXX 的实例就不保证了,所我也把原来 $game_party.inspect 改为了 其 @items 对象的 inspect 方法。
如果要防止修改内存,也可以考虑用校验的方式,比如 @items 的每一次修改都是在 Game_Party#gain_item 方法里,在这个方法的结尾计算@items 的 hash 的值保存在一个全局变量里,然后在你需要的时候检验 hash 值和对应的对象转化的字符串是否相符合。
还是拿 @items 举例~
class Game_Party alias _check_item_initialize initialize def initialize _check_item_initialize @salt = rand.inspect @item_hash = item_hash end alias _check_item_gain_item gain_item def gain_item(*args) cheat = check_item _check_item_gain_item(*args) @item_hash = item_hash if !cheat end def item_hash sprintf("%08x%08x", (@salt + @items.inspect).hash, (@items.inspect + 'rpg.blue').hash) end private :item_hash def check_item # 无作弊返回 false,否则返回 true @item_hash != item_hash end end
class Game_Party
alias _check_item_initialize initialize
def initialize
_check_item_initialize
@salt = rand.inspect
@item_hash = item_hash
end
alias _check_item_gain_item gain_item
def gain_item(*args)
cheat = check_item
_check_item_gain_item(*args)
@item_hash = item_hash if !cheat
end
def item_hash
sprintf("%08x%08x", (@salt + @items.inspect).hash, (@items.inspect + 'rpg.blue').hash)
end
private :item_hash
def check_item
# 无作弊返回 false,否则返回 true
@item_hash != item_hash
end
end
(RMXP)这段代码插入main前,可以在每次调用 gain_item 方法后判断是否为作弊,如果没有作弊则重置 hash。如果有作弊,@item_hash 的值不会更新,从而保证 check_item 一直返回 false,除非 @items 回归到作弊前的情况,或者玩家同时修改了正确的 @item_hash。
为了避免玩家猜出来正确的 @item_hash,盐字符串 @salt 的生成是随机的~所以每一个新的游戏或者存档,都有不同的 @salt,从而也不能通过其他人的存档得到对应的 @item_hash。从而获取盐字符串的方法只能穷举或者解包游戏~相比下来,解包说不定更快捷,那样就没有办法了。建议修改一下 item_hash 的具体计算方法,弄复杂一点点,比如像上面这样返回一个表示64位整数的字符串。
然后在你需要检查 items 作弊情况的时候判断 $game_party.check_item 即可。比如每次存、读档,进入菜单页面时。
如果游戏里存在不使用 gain_item 修改 @items 的情形,会被判定为作弊(当然)。如果无法避免这种情况,在每次修改了 $game_party.items 的后面都对 @item_hash 重新赋值。
我的想法就是这样了…… |