I do a thing where I track how often an event is fired by a player and how often it could possibly be fired. If an event is fired at unreasonable speeds(clicking something 10 times a second when my client coding doesn’t allow it for than a couple times a second), I will usually kick the player.
Even though my events are fully logic protected(to my knowledge of course), I don’t want exploiters to even have a good chance to tamper with them, so I kick them for even trying to spam them.
I think I would suggest simply ignoring clicks which are too fast. Some people can click extremely fast. I can click over 10 times a second easily and I don’t think I’d like to be kicked for clicking too fast personally.
I said I track events that are fired faster than possible. You are not sending me a different build position on a grid 25 times a second. That, or 10x+ faster, is what exploiters do and I will kick for that.
What benefit does this provide if your game is already secure, though?
Despite limiting the rate to a normally unrealistic amount, this aggressive approach can still cause innocent players to be punished in certain circumstances. For example, a client’s connection to the server is temporarily disrupted and, in that time, they place an object in a different build position 30 times, press a button limited to 1 click/second 12 times in 12 seconds, etc. on the client.
When the connection is regained, all the queued remote requests would reach the server at once, and the server would detect that the innocent client has surpassed the maximum rate of event invocations that you implemented and kick them.
I didn’t do anything about event tamperers until one guy was mass spamming building and deleting models on his plot, causing lag for the rest of the server. That is “technically” legal, as its only doing a what a normal player could do, but 500x. Why put a checker for just that instance when I can put a checker for everything?
Why not just ignore excessive building requests (and other requests that take a lot of performance/memory/etc. to handle), instead of kicking the client that made the requests? This prevents both the server lagging and innocent players with high latency being kicked.
That can deter people and stop some simple things that could target your games remotes but it can still be bypassed. Just validate stuff on the server side and you won’t have to worry about a key system. All they would have to do is hook the function which gets the key and fire the remotes as they wish.
Key validation is useless against any modern exploit at this point. Most exploits expose upvalues and locals meaning you can’t hide things behind contexts. Anything you store on the client is editable/readable.
So would it be completely pointless to continue having a key validation system in place?
still confuses most exploiters. I’m already checking everything the client sends to the server
It wouldn’t be pointless I guess since it will stop people who don’t know what they’re doing but it doesn’t make a huge difference to be honest. I guess it’ll stop copied scripts since changing how your code works will make their code obsolete.
It’s fully allowed and valid to make it harder for exploiters on the client. It will bounce off most of the noobs that just got their hands on a new exploit and will discourage them to continue. My bet is that mainly kids are using these exploits to interrupt the enjoyment of other players, and as soon as they meet a challenge they’ll quit because they will just find another game to do this with.
However, you’ll need to reconsider for this particular reason: Those who actually try to find a way around your system, will almost always succeed, granted the time, dedication and motivation to do so.
So by having a key system in place, you’re pretty much discouraging most of the exploiters. However, there are those who make exploits and those who are interested in money finding a way to bypass it.
So, although you can spend your time dealing with local validation, it’s not going to stop those who try. Proper server validation is what “guarantees” to stop exploits, because the server is in charge of all players, what they’re able to do, and what gets replicated or not. I’m not saying you should stop doing this local validation system, that’s perfectly ok to use (as long as you’ve set it up correctly,) because then you’re a step closer to getting rid of bad players.
Just an addition to this key idea… I think I came up with a way to “sign” remote requests.
E.g. authorize a request or range of requests.
It uses sha hashing and there are security flaws with this idea. Under no circumstances should this be used as a credential system however forging these tokens is not worth any exploiters time.
Step 1. Generalize data. Your function should return the same value for valid inputs but any different value for invalid ones.
Step 2. Create a key to sign with. Could be hard coded on the server or generated using some cryptography formula
Step 3. Concatenate function result with key and hash the value
Step 4. Send it to the client. The client will pass whatever values you want to verify and then you repeat the process with the supplied data. Then you compare the result.
One main use would be to include a time component. You can create a time based token using this and the client can continue to use it until it expires. You can also include multiple checks by concatenating values with a separator.
Server generates token for dataset
Server sends only the token to the client
The client makes a request using the token
The token contains the embedded dataset. The server generates a new token using data gathered from the client request (e.g. client supplied or other data like time).
The server compares the tokens and if they match the request is granted.
Yes. The token restricts what they can use it for though. It works due to the fact that they cannot access the server’s private key therefore they cannot forge these tokens.
The token has two components. One, the private key. Two, the “generalized” data.
Here’s some example psuedocode:
-- Client
local myToken, myTimeDiff = receiveSomeEvent()
sendSomeEvent(myToken, myTimeDiff, myData)
-- Server
function makeKey(timeDiff, theirTimeDiff)
timeDiff = timeDiff or theirTimeDiff
return sha256(privateKey..":"..math.min(theirTimeDiff, timeDiff)) -- If timeDiff is greater than theirTimeDiff it will become theirTimeDiff therefore the verification will pass.
end
function verify(token, otherTime, criteria)
local curTime = os.time()+tick()%1
if makeKey(curTime-otherTime, criteria) == token then
return true -- Key is valid
else
return false -- Key is invalid
end
end
sendTheKey(makeKey(nil, theirTimeDiff), theirTimeDiff)
local lastTime = 0
local function whenSomeEvent(token, theirSupposedTimeDiff, data)
if token == makeKey(nil, theirSupposedTimeDiff) then -- Their token matches with the data criteria
if verify(token, lastTime, theirSupposedTimeDiff) then -- Their token contains the correct criteria which we just verified so making a new token with the extra data we have gathered should succeed
lastTime = os.time() + tick()%1
doStuff(data) -- It succeeded
end
end
end