游戏开发-防加速器作弊
##加速器作弊
游戏客户端程序中通常会涉及游戏人物的行走、攻击等,拿人物行走来说,一般会有个固定值作为行动速度,任务行走时根据时间差来求得人物的最新位置,位置偏移计算会使用到客户端本地时间
一些加速器软件篡改时间值,游戏进程获取的时间值被过快增加,玩家可以飞快的在游戏场景中移动,影响游戏平衡性
一些独立网游,发布初期版本时,就有玩家使用加速器,未防此种作弊,博主提供一种解决方案
##一种检测加速器作弊的可行方案
加速器检测,首先想到的就是时间差异检测,时间差异放在客户端做还是服务端做,怎样进行通讯
博主提供一个可行方案
客户端/服务端交互(HTTP协议)
-
初始调用防作弊接口
客户端初始时发送一次初始化请求给服务端,携带客户端时间戳
服务端记录客户端时间戳与服务端时间戳,已客户唯一标识存入hash表
-
定期调用防作弊接口
客户端间隔一段时间(10s)发送一次反作弊验证请求给服务端,携带客户端时间戳
服务端进行防作弊检测
检测不通过,返回错误
检测通过,记录客户端与服务端时间戳,更新hash表数据
-
防作弊检测
计算两次时间戳间隔
验证客户端时间戳间隔超出服务端时间戳间隔高于某阈值,则视为加速作弊,返回检测不通过
Java服务端实现
AntiCheatingController 内定义如下API接口
// 初始调用防作弊接口
@RequestMapping(value = "/initClientTimestamp", method = RequestMethod.PUT)
public void initClientTimestamp(@RequestParam(value = "userId") String userId,
@RequestParam(value = "timestamp") long clientTimestampInS) {
antiCheatingService.initClientTimestamp(userId, clientTimestampInS);
}
// 定期调用防作弊接口 执行防作弊验证
@RequestMapping(value = "/checkCheating", method = RequestMethod.PUT)
public boolean checkIsCheating(@RequestParam(value = "userId") String userId,
@RequestParam(value = "timestamp") long clientTimestampInS) {
return antiCheatingService.checkCheating(userId, clientTimestampInS);
}
AntiCheatingService 封装核心逻辑,实现如下
@Service
public class AntiCheatingService {
private Map<String, TimestampPair> userClientTimestampMap = new HashMap<>();
public void initClientTimestamp(String userId, long clientTimestampInS) {
updateTimestamp(userId, clientTimestampInS, getServerTimestampInS());
}
public boolean checkCheating(String userId, long clientTimestampInS) {
TimestampPair timestampPair = userClientTimestampMap.get(userId);
if (timestampPair == null) {
initClientTimestamp(userId, clientTimestampInS);
return false;
}
long serverTimestampInS = getServerTimestampInS();
long clientDiff = clientTimestampInS - timestampPair.getClientTimestampInS();
if (clientDiff == 0) {
return false;
}
if (clientDiff < 0) {
throw new RuntimeException("wrong clientDiff, current: " + clientTimestampInS
+ "last: " + timestampPair.getClientTimestampInS());
}
long serverDiff = getServerTimestampInS() - timestampPair.getServerTimestampIns();
if (serverDiff < 10) { // 小于10秒,检测意义不大,忽略
return false;
}
if (clientDiff <= serverDiff) {
return false;
}
BigDecimal speedUpRate = DecimalUtility.toDecimal(
(clientDiff - serverDiff) * 1.0 / serverDiff);
if (speedUpRate.compareTo(new BigDecimal("0.2")) >= 0) {
... // 检测到作弊,加速超过20%,此时可以记录作弊用户信息
return true;
}
updateTimestamp(userId, clientTimestampInS, serverTimestampInS);
return false;
}
private void updateTimestamp(String userId, long clientTimestampInS, long serverTimestampInS) {
TimestampPair timestampPair = new TimestampPair();
timestampPair.setClientTimestampInS(clientTimestampInS);
timestampPair.setServerTimestampIns(serverTimestampInS);
userClientTimestampMap.put(userId, timestampPair);
}
private long getServerTimestampInS() {
return DateTime.now().getMillis() / 1000;
}
}
Ruby游戏客户端实现
程序在启动时,创建一个子线程执行反作弊验证
子线程执行逻辑如下
初始执行init_client_timestamp,对应调用initClientTimestamp
API接口
每15s执行check_cheating,对应调用check_cheating
API接口
def init_check_cheating_thread
Thread.new {
begin
sleep(1)
init_client_timestamp
loop {
begin
check_cheating
rescue Exception => e
puts "check_cheating raise exception:#{e.message}"
puts e.backtrace.inspect
end
sleep(15)
}
rescue Exception => e
puts "init_check_cheating_thread raise exception:#{e.message}"
puts e.backtrace.inspect
end
}
end
执行HTTP请求的方式参考另一篇博文ruby实战-发送http请求
check_cheating方法中在获知用户作弊时,则出发相关回调,上层逻辑给予违规用户相关警告
在本人的游戏中,给出的警告信息如下图
##补充
部分违规用户可能会采用截获http请求伪造返回报文以达到禁用此防御措施的目的,游戏开发者需对此方案进行升级,在此不再赘述