背景:我正在开发一个小游戏,利用玩家的延迟进行延迟补偿。该游戏是开源的,因此目前对系统进行逆向工程并延迟响应时间以人为增加报告的延迟是一项非常容易的任务,从而导致可能不公平的优势。
我目前的延迟检索策略是:
- 每隔固定的时间间隔,我都会向玩家发送一条标记为"ping"的消息。(这与ICMP无关(
- 此 ping 消息由一个特殊的"ping"操作码和一个带有序列号的有效负载组成
- 一旦客户端收到所述消息,他就会发回一个带有"pong"操作码和具有相同序列号的有效负载的消息
- 当服务器收到标记为"pong"的消息时,它会计算发送和接收之间经过的时间。这是往返时间
- 我们的延迟是 rtt/2
在伪代码中
服务器:
function now() {
return current UTC time in millis
}
i = 0
function nextSequence() {
return i++
}
sendingTimestamps = []
function onPingEvent() {
id = nextSequence()
sendingTimestamps[id] = now()
sendPingMessage(id)
}
function onPongReceived(id) {
received = now()
sent = sendingTimestamps[id]
rtt = received - sent
latency = rtt / 2
}
客户:
function onPingReceived(id) {
sendPongMessage(id)
}
如您所见,客户端很容易在他的代码中添加延迟来增加他报告的延迟。
有没有更好的方法来获得客户端延迟,以减少作弊空间?
下面的答案是评论中讨论的主题的摘要,将它们全部放在一个地方。
延迟补偿应依赖于事件的精确时间戳,而不是平均数据包延迟
即使对于两个连续的数据包,转换时间也可能有很大差异。建议的测量平均延迟并假设每个收到的数据包在毫秒前发送"延迟"以进行滞后补偿的方法太不准确了。应改为应用以下方案:
服务器在其一侧开始模拟世界,并向所有客户端发送命令 START。客户端启动模拟世界并从其创建开始计算即时报价。每当客户端发生任何事件时,客户端都会将其与时间戳一起发送到服务器。就像"用户在tick #183处按下火力"。由于数据包转换时间的原因,服务器对游戏的模拟遥遥领先,但服务器可以"回到过去"来处理用户的订单并解决后果。
时间戳和事件仍然可以伪造
AFAIU验证客户端输入的问题通常是无法解决的。在客户端中实现的任何算法都可以重新创建为伪造事件/时间戳/数据包。封闭代码可以反转,所以它不是一个答案。即使是像反恐精英或守望先锋这样全球传播的游戏也有作弊者,尽管它们是由大公司开发的,我敢打赌,这些公司有单独的部门专注于游戏安全。一些公司开发类似防病毒软件的模块,这些模块检查游戏文件的完整性或部分 RAM 快照的哈希,但它仍然可以被绕过。
问题是伪造算法需要付出多少努力。需要付出的努力越多,造假者就越少。简单的时间戳验证如下:
如果您在事件 #1 之后在 TCP 流中收到事件 #2,但其时间戳在事件 #1 之前,则它是伪造的。
如果时间戳远远落后于服务器的时间,则警告并踢玩家非常糟糕的延迟。如果是真正的玩家,反正游戏对他来说是不能玩的,否则你就踢了黑客。如果我没记错的话,CS 服务器会这样做。