Hello, I am Reapimus! Today I am making this guide to the proper way of making an anti-exploit, because far too often on the developer forum I am seeing so many people giving poor anti-exploit advice or trying to make ones that simply won’t work, and I am hoping that by making this guide, more people will learn and know how to make an effective, and proper anti-exploit for their games.
So with that introduction out of the way, lets start with this guide!
What is an Exploit?
To start off with, it’s important that you have an understanding as to what exactly an exploit is. An exploit is code that the player injects into their client in order to allow them to perform actions that would normally not be possible or allowed by the game and exploit it.
What Can Exploits Access?
That explanation of what an exploit is may have been obvious to many, however, what many don’t know is what exactly an exploit is able to access. Exploits are only capable of accessing anything that their client can see, meaning they do not have access to the source code of any server-side scripts.
They do, however, have full access to all client-side code you have for your game, as well as can see anything that your code is doing, one example being what remotes you are firing and what arguments you are providing to them.
Exploits are furthermore, capable of modifying any information that the client is sending through a remote to the server, which effectively rules out any chances of trying to create an anti-exploit where the server asks the client if it is cheating or not.
For further, more-detailed information on what exploits are, you can check out this post made by Autterfly:
What Should an Anti-Exploit Do?
There are also many misconceptions as to what exactly an anti-cheat should be capable of doing, so in the simplest terms. Yes, an anti-exploit should be checking for if a player is cheating or not and appropriately punishing them for doing so, no, an anti-cheat should not be able to immediately kick or ban a player if it detects they are exploiting.
Why is immediately kicking or banning a player it thinks is exploiting an immediate no? Because your anti-cheat will never be flawless, it is bound to make mistakes and give out false positives, which will be gone over later in this guide. You don’t want to have players complaining that they were incorrectly banned by your anti-cheat due to a false positive!
Your anti-cheat should not be capable of completely ruining a player’s experience at all, it should only be slightly inconveniencing them in the event they do accidentally trigger one of your anti-exploits checks.
False Positives
Now, naturally your anti-cheat will run into false positives due to circumstances outside of your control that cannot be avoided or fixed. Examples of this could be due to something like a player with a bad ping (lagging) seemingly going faster than should be allowed or this lag causing them to appear to be teleporting around to the server.
How can we get around this issue and reduce the number of false positives? The simple answer is to add a reasonable margin of error for each check you implement into your anti-exploit. For example: if you’re making a check to prevent speed or teleport exploits, you’re gonna want to add a margin of error so that laggy players are less likely to accidentally trigger the check, an example of this being if the maximum walk speed is 16, your margin of error should make the maximum speed the anti-exploit checks for something higher such as 24-28.
In short, each check in your anti-exploit should account for scenarios that a normal player may encounter that accidentally trigger the check, allowing them to have as little inconvenience as possible.
What Relies on the Client/what should I not use?
As I’ve seen recently from a few devforum posts, it should be gone over exactly what relies on the client for information as well as what should not be used to check for exploits, since there are many things in Roblox which will rely on information given from the client or replicate from client to server.
PlatformStanding
While it is common that most fly exploits use PlatformStanding somehow, it isn’t a good idea to check for that being enabled as this would make the assumption that all fly exploits both rely on default humanoid states to function properly and the assumption that none of your code or any developers’ code who uses your anti-exploit will ever use PlatformStanding, which isn’t a good thing.
The final notes on this one is that the client may even prevent the state of PlatformStanding replicating to the server to begin with in order to fully bypass such a check!
Humanoid.Running/Humanoid States
While Humanoid.Running can get you the client’s current WalkSpeed from the server’s end, it shouldn’t be relied upon because of the sole fact that it gets this information from the client, as should be expected. The same applies to humanoid states, both of these are determined for the most part, by the client, meaning that they can be easily spoofed and there’s nothing that can be done to stop this.
Replication exceptions
As documented by Roblox on this dev hub page, there are a few exceptions that allow the client to replicate some changes to the server, such as the Jump and Sit properties of their own character’s humanoid or the TimePosition and Playing properties of an existing sound object on the server.
They can also control the position and rotation of any unanchored parts that their client currently owns/simulates the physics for.
Finally, it isn’t documented by Roblox but it appears that the client is able to delete anything inside of their own character and have the deletion of that instance replicate to the server, which allows them to perform other kinds of exploits such as god mode by deleting their humanoid and creating a client-side only version of it.
Anti-Exploit Checking Methods
So you now fully understand how false positives should be dealt with, what relies on the client, and what an anti-exploit should be doing, now all that’s left to do is actually implementing the checks to prevent these various exploits.
The first thing I am going to mention before I start explaining some of these checks is: absolutely DO NOT trust the client or create a completely client-sided anti-exploit, they do not work as the exploiter can disable and trick them into believing everything is working as it should.
For any pieces of example code in the below sections, playerState
should be a variable referencing a table where you store information about a player’s state that persists across each check, obtaining such a table could look something like this:
local PlayerStates = {}
function getPlayerState(player)
if PlayerStates[player] == nil then
PlayerStates[player] = {}
end
return PlayerStates[player]
end
local playerState = getPlayerState(targetPlayer)
It should be noted as well that you would want to clean up/remove the player state for any players that leave.
Speed/Teleport Checks
One of the common types of exploits, is the speed or teleport exploit (these two can be a single check because they function practically identically in theory). With this one, it isn’t possible to simply check the Humanoid’s WalkSpeed nor can we check the current Velocity of the player’s character, instead, we must make use of some form of loop on the server (preferably, you’d be using RunService to handle this loop).
Then, the question comes as to how exactly do you check what a player’s speed is if we cannot look at the WalkSpeed of the Humanoid or the Velocity of the character? The simple answer is, we do a little bit of math to determine their speed based on the distance they traveled since our last check, and the time since our last check.
This would give us something like: speed = distance/time
, and an implementation of this may look something like this:
local distance = ((playerState.LastPosition - character.PrimaryPart.Position) * Vector3.new(1, 0, 1)).Magnitude
local elapsed = os.clock() - playerState.LastCheck
local speed = distance/elapsed
In this example piece of code, you may have also noticed that I am removing the Y axis of distance
, this is because the Y axis can open up the check to more false positives because players can move up/down objects like ladders faster than they can walk, and they can fall much faster than they can walk as well.
With the issue of getting the player’s speed out of the way, the next part comes in determining if they are surpassing the maximum walk speed, and how to appropriately handle this. To determine if they are surpassing it, you would check if their speed is greater than the maximum speed with whatever your threshold is set to added onto it.
For example:
if speed > character.Humanoid.WalkSpeed + THRESHOLD then
print("The player triggered the check!")
end
In this example case, the maximum walking speed has simply been determined by whatever the server sees the player’s WalkSpeed as. Now the next part would be appropriately punishing the player. For a check against teleporting and speed exploits, your best choice would be to reset the players position back to where they were during the previous check.
Which, extending the previous example, would look something like this:
if speed > character.Humanoid.WalkSpeed + THRESHOLD then
character:SetPrimaryPartCFrame(CFrame.new(playerState.LastPosition))
end
Noclip Checks
The next type of exploit that you may want to check for is noclipping, which is a somewhat tricky one to check for. For those of you who don’t know, noclipping is an exploit where a player walks through solid walls that they should not be capable of walking through.
So, how do we check for such an exploit? We cast a ray from the player’s position during the last check, toward the player’s current position, and then we cast one from the player’s current position, toward the player’s previous position. If the first ray hits something solid, then we can assume that they might be noclipping.
If the second one doesn’t hit anything at all but the first did, we can assume the player has already noclipped and is inside of the object they are noclipping through. If the second does hit something solid, we can then get the distance between the first hit and the second hit to determine how much through the object the player possibly noclipped.
Using this information, we can only consider the player to be noclipping if they are either:
- Inside of a solid object, or
- They noclipped through a large portion of a solid object
Now the second condition may sound a little bit confusing, so here is some example code of how this check may look:
local cast1 = workspace:Raycast(playerState.LastPosition, CFrame.lookAt(playerState.LastPosition, character.PrimaryPart.Position).LookVector, playerRaycastParams)
local cast2 = workspace:Raycast(character.PrimaryPart.Position, CFrame.lookAt(character.PrimaryPart.Position, playerState.LastPosition).LookVector, playerRaycastParams)
if cast1.Instance and cast2.Instance then
local depth = (cast1.Position - cast2.Position).Magnitude
if depth > THRESHOLD then
print("The player triggered the check!")
end
elseif cast1.Instance and not cast2.Instance then
print("The player is likely inside of the object!")
end
And finally, the appropriate method to punish players who trigger this check would be the same way speed/teleport exploiters are punished: sending them back to wherever they were during the last check.
Fly Checks
As was stated previously, while most fly exploits make use of PlatformStanding, it isn’t a good idea to check specifically for this property being enabled on the humanoid to detect fly exploits because it makes the assumption that the client is even accurately replicating it and that their exploit uses it and not a custom system.
So then what do we do instead to check if the player is using a fly exploit? Simple. We check if they have been off the ground for an extended period of time, however, this alone can trigger false positives during perfectly normal situations such as falling.
Which means we also need to check if the player has been off the ground for an extended period of time, and that they aren’t falling. So now with that in mind, we can use a system similar to our anti speed/teleport exploit check to identify if they are falling or not.
The first step in checking for this exploit would be to check if they are on the ground, which would also need to account for the possibility that the player’s character might be of a none-standard height (such as is the case with R15 characters), which would be done by using the size of the root part on the Y axis and the humanoid’s HipHeight if it is R15, but if it isn’t R15, we know it has a static height and can return that instead, which would look like so:
local function getRayHeight(character)
local R15 = character.Humanoid.RigType == Enum.HumanoidRigType.R15
return R15 and character.Humanoid.HipHeight + character.Humanoid.RootPart.Size.Y/2 + 1 or 3.5
end
Now that we know what the height of the player’s character is, we can use it to cast a ray toward the ground to check if the player is on it or not, which would look like so:
local cast = workspace:Raycast(character.PrimaryPart.Position, -getRayHeight(character), playerRaycastParams)
local isOnGround = cast and cast.Instance.CanCollide
Note: While I am keeping this code example here because it might be useful to someone, there was an update that made it reliable to check the humanoid.FloorMaterial
because the property can no longer be manipulated by the client, that code would look like:
local isOnGround = humanoid.FloorMaterial ~= Enum.Material.Air
Since we now know that the player is on the ground or isn’t on the ground, we can save that they are either grounded, or if they aren’t on the ground, we save that they are not on the ground, with a timestamp referring to the last time they were on the ground.
So now with that information, we need to have a loop somewhere that is checking with this information whether or not the player is flying, and also that they aren’t falling, which would look something like this:
local distance = ((playerState.LastPosition - character.PrimaryPart.Position) * Vector3.new(0, 1, 0)).Magnitude
if distance >= 0 and os.clock() - playerState.lastGrounded > 5 then
print("The player triggered the anti-fly check!")
end
With this we now know whether or not the player may be flying, and can punish them by either sending them back to their last known position, or teleporting them to a ground level. It may also be noted that you could want to check how fast they are falling because it might just be them occasionally flying downwards a bit in order to trick the check.
Other Checks
Now, the kinds of checks I have listed before are the more common kinds of checks that can apply to just about any game on the Roblox platform, and even any multiplayer game in general. You’re always gonna encounter issues that are specific to your game because not every game is the same. So I will go over some example scenarios of how you might approach an anti-exploit for a game-specific issue.
Melee Weapons
Let’s say you have a neat sword-fighting game, in order for players to have fast-paced sword combat you’re very likely going to want to handle hit detection on the client side and have it tell the server that they hit a player, but this then comes with the issue that you’re trusting the client.
To work around this issue, you implement server-side checks whenever the client reports that they hit another player. You’d want to:
- Verify the player is within reasonable distance of their target
- Verify the cooldown for their sword (so they cannot spam the requests and immediately kill any player within range)
- Verify that the player is reasonably in front and facing their target unless the sword uses an AoE (Area of Effect) attack like a spinning attack
In order to verify the distance, you’d simply check the result of (playerPosition - targetPosition).Magnitude
is within a reasonable attacking range, with a small threshold to account for a player lagging. To verify the cooldown, you’d simply store somewhere when the last time the player attacked was, and check if os.clock() - lastAttack
is greater than or equal to the cooldown period.
To check if the player was reasonably in front of their target, you’d need to use some vector math to figure out the angle between the player and their target, which is excellently explained in this devforum post
An example of this may look like:
if lastAttack - os.clock() >= COOLDOWN then
if (player.Character.PrimaryPart.Position-target.Character.PrimaryPart.Position).Magnitude <= RANGE then
local relative = (targetPosition-playerPosition)
local forward = player.Character.PrimaryPart.CFrame.LookVector
local side = relative.Unit
local theta = math.deg(math.acos(forward:Dot(side)))
if theta <= MAX_ANGLE then
print("The player successfully hit their target!")
end
end
end
Do note that in this example you may be able to use os.time()
instead of os.clock()
if you do not require precision that goes under a single second (ex: needing a cooldown of 0.5
or 1.5
would be better to use os.clock()
for, otherwise os.time()
can work perfectly fine here.
Securing your Remotes
While it isn’t directly associated with anti-exploits, securing your remotes is still an important aspect of stopping exploiters in your games! If you have an insecure remote that an exploiter can abuse to do something they shouldn’t be able to – like give themselves unlimited amounts of cash, then that’s an exploit caused by a flaw in your own code that shouldn’t have happened and should be fixed.
The easiest and most simple way to explain how to secure your remotes is by going over cases where one solution is correct and the other is wrong, and explaining exactly why one of those is more correct than the other one.
In general however, you should be doing server-side checks to validate any request coming from the client via a remote, and making sure that it is all true and that the player should actually be able to make this request instead of simply blindly trusting all the information the server is receiving from the client.
Case 1: Purchase Item Remote
You may have a shop system in your game that allows players to buy items from it using an in-game currency, which is perfectly fine, but needs to be made sure that it is secure so that the player cannot simply get free stuff by using an exploit on it.
Solution 1
buyItem.OnServerEvent:Connect(function(player, item)
playerData[player].Money -= prices[item]
table.insert(playerData[player].Inventory, item)
updatePlayerData(player)
end)
Solution 2
buyItem.OnServerEvent:Connect(function(player, item)
if playerData[player].Money >= prices[item] then
playerData[player].Money -= prices[item]
table.insert(playerData[player].Inventory, item)
updatePlayerData(player)
end
end)
In these two solutions, Solution 2 is the correct solution here because it validates that the player actually has enough money to purchase the item they want, and will only go through with the purchase if they pass this check, whereas in the previous one, there is no checks to see if they have enough.
Case 2: Donation Remote
Your game might have a system that allows a player to donate their in-game money to another player which works using a remote to do so. You would need to make sure that the player cannot simply fire it with any value and exploit it to get free money or give free money.
Solution 1
donate.OnServerEvent:Connect(function(player, target, amount)
if target and playerData[player].Money >= amount and amount > 0 then
playerData[player].Money -= amount
playerData[target].Money += amount
updatePlayerData(player)
updatePlayerData(target)
end
end)
Solution 2
donate.OnServerEvent:Connect(function(player, target, amount)
playerData[player].Money -= amount
playerData[target].Money += amount
updatePlayerData(player)
updatePlayerData(target)
end)
In these solutions, Solution 1 would be the correct solution because it checks to make sure the player has specified a valid target, checks that the player has enough money to give to this player, and checks that they didn’t specify a negative amount.
In this case, checking if the player specified a negative amount is really important, without this check, the player could give themselves free money and, by extension, would also result in the target player losing money, and going into the negatives potentially.
Which means that player would practically be stealing money from another player simply by abusing a fault in your system that was caused by poorly securing your remotes and validating/sanitizing information and data!
The Anti-Exploit’s Interactions with your Game Mechanics
Another vital thing to consider when coding your anti-exploit is how it will interact with your game’s mechanics. One example being, if you have a sprint feature or there’s a way for a player’s walk speed to be changed, you are going to need to design your speed and teleport checks to account for this mechanic.
Now accounting for game mechanics in anti-exploit checks can certainly be tricky, which is why you should determine whether or not a certain check is worth it, and if it is even possible to implement said check given your game mechanics, before you decide to start implementing it.
It is incredibly important that you choose these well and make them play well with your game mechanics, there have been various examples of anti-cheats completely destroying a game’s playerbase due to being poorly implemented or just outright bad.
Conclusion
This has been one big guide on making proper anti-exploits, and I hope that any of you who have fully read through this guide will now make some anti-exploits the right way and correctly implement them into your games with this new knowledge. That’s all for this guide!