Tutorial on how to Anti Cheat on the Client Side, I guess.
Decided to make a tutorial show casing something nobody seems to be talking about. Hope you find it useful I guess.
Spoofing, you should probably read
[Edit June 7, 2025, Added Spoofing]
Spoofing
Since a bunch of people keep telling me about spoofing and how it can bypass Client Sided Anti Cheat but never actually explain what spoofing really is and how it can bypass an Anti Cheat (At best they just use a bunch of poorly explained exploit terms that don’t matter since I don’t exploit). Did my research on it and decided to mention it here.
Anways its basically the player sending fake data on the client side allowing it to bypass client side checks and send fake data through RemoteEvents.
If you were me back then, the player “sending fake data on the client side” sounds pretty confusing. To make it easier to understand let’s pretend we have an instance of something. You can think of an instance as a dictionary/table of data where how we access that data is by calling the instance local part = workspace.Part
. Everytime we call for the instance’s data via part.Position
or whatever property, we’re basically calling for that instance’s table data. However, what spoofing does is basically replaces that table data with their own table data allowing it to bypass client sided checks.
As an example, let’s say we have an instance of a Humanoid on the Client Side. If we decided to check if the Humanoid’s speed is greater than 16 and set it back to 16 if true, we’d do:
if Humanoid.WalkSpeed > 16 then
Humanoid.WalkSpeed == 16
end
However if an exploiter decided to spoof the Humanoid’s Data/Walkspeed, the exploiter can send whatever number it wants while still having the Humanoid’s Walkspeed greater than 16. Let’s say the exploiter changed their walkspeed to 20, then we check if their walkspeed is greater than 16, the exploiter, if he spoofed it, can send a fake number of 16 while still having a speed of 20 to prevent the script setting it back to 16. Again, this only works IF it’s done on the Client Side.
This also goes for any functions attached to the instance.
Humanoid:GetPropertyChangedSignal("WalkSpeed"):Connect(function()
print("Something")
end)
An exploiter can spoof the function to basically make it not run at all. Again, allowing for the exploiter to bypass any Client Sided Detection.
If you want a more in depth tutorial just check this post: Exploiters and how they spoof stuff in your own game
So does that mean you shouldn’t make a Client Sided Anti Cheat? No, most exploiters are just kids who don’t know how to exploit and in turn, don’t know how to spoof. Even then, there’s Roblox’s own exploit detector they have built in. Not sure what it does, however Im assuming the more the exploiter tampers with the game, the easier it’ll be for Roblox to auto detect him as an exploiter and ban him or something.
Mechanism
There's an unintended feature(? more like a bug) for when you use modules. If you require a module script and disable or delete the script that's running it,Whatever you put inside the module script will continue to run.
Why, IDK, it was a bug I ran into a few years back that I just so happened to have remembered after someone brought up the topic about anti-cheats.
Anyways, this allows you to have a Client-Sided anti cheat system that can’t be disabled.
Example of it being done on Server Side:
Module Script
while true do
task.wait(1)
print("Running")
end
return nil
Server Script in workspace
task.wait(5)
print("Requiring ModuleScript")
task.wait(1)
require(script.ModuleScript)
Video showcasing Script: Don’t mind the lag spike when deleting, my laptop is garbage
In short, just make your anti cheat on a module and require it on a client-sided script
Usage
Usage
How you use this knowledge really depends on how your game works. Though to begin with, we can make an instance detector to detect when an instance is spawned. To make this we need- A remote function (to check if instance exists in server)
- Server sided script (to return the function)
- Client Sided script (the anti cheat detector, I guess)
- Module Script (just read the Mechanism on why we need it)
Im gonna assume you know how remote functions work and not explain anything about the remote function.
Server Script Code placed inside ServerScriptService:
local RemoteEvents = game:GetService("ReplicatedStorage").RemoteEvents
local toServer = RemoteEvents.toServer
toServer.ObjInServer.OnServerInvoke = function(plr,obj:Instance)
return obj
end
What this script does is gets the RemoteFunction, in this case it’s in ReplicatedStorage.RemoteEvents.toServer
where RemoteEvents and toServer are just folders for organization, then detects when the RemoteFunction has been invoked checking to see if an obj(any instance of sort) is replicated on the server. If it is replicated on the server it’ll return the instance, if not, it will return nil.
Local Script Code placed inside StarterPlayerScript:
local ModuleScript = require(script:WaitForChild("ModuleScript"))
game:GetService("StarterPlayer").StarterPlayerScripts.ClientDetector:Destroy()
script:Destroy()
What this does is require the Module Script which is parented under the Local Script, we require the module script because it’s where we’ll be writing our anti cheat code. Why we write it in the ModuleScriipt? Well go read Mechanism, if you haven’t. Anyways, we later destroy any trace of the Script so the exploiter doesn’t have any clue we have an anti cheat system.
Gonna be explained in sections so it’s easier to understand
ModuleScript Code Parented inside the Local Script:
local toServer = game:GetService("ReplicatedStorage").RemoteEvents.toServer
local ObjInServer = toServer.ObjInServer -- RemoteFunction
local plr = game:GetService("Players").LocalPlayer
Gets the RemoteFunction, player, and character of the Client.
local function ObjExsist(obj:Instance):boolean
if ObjInServer.Parent == nil then -- Checks if the RemoteFunction has been deleted
plr:Kick("Exploiting")
end
if not ObjInServer:InvokeServer(obj) then
task.wait(.2) -- prevents Parent error happening on "obj"
obj:Destroy()
end
end
What this function first does is, check if the RemoteFunction ObjInServer is parented is nil, basically checks if the Client has deleted the RemFunc, if so, kick the player, since if the Client has deleted the RemFunc, that’ll tell us it’s an exploiter.
Then it checks if the obj exists on the Server
if not ObjInServer:InvokeServer(obj) then
will either return the obj if it exists on the Server or nil if obj doesn’t exists on the server. So basically, if object not in server then do. Anyways the next line of code deletes the object since it doesn’t exists on the Server, and therefore, we’ll know it was spawned in the Client.
workspace.ChildAdded:Connect(ObjExsist)
Pretty short and self explanatory, if something gets added to workspace, it calls the ObjExsists function (Not the RemoteFunction) and deletes it if it doesn’t exists on Server.
Entire ModuleScript Code
local toServer = game:GetService("ReplicatedStorage").RemoteEvents.toServer
local ObjInServer = toServer.ObjInServer -- RemoteFunction
local plr = game:GetService("Players").LocalPlayer
local function ObjExsist(obj:Instance):boolean
if ObjInServer.Parent == nil then -- Checks if the RemoteFunction has been deleted
plr:Kick("Exploiting")
end
if not ObjInServer:InvokeServer(obj) then
task.wait(.2) -- prevents Parent error happening on "obj"
obj:Destroy()
end
end
workspace.ChildAdded:Connect(ObjExsist)
Video the entire script working I guess:
https://youtu.be/AujNr5B8QcE
My Version of Usage
My version of Usage
I HIGHLY recommend making your own and using maybe some of my code. This is due to the fact that your game probably uses different mechanics than mine. Which may result in the AntiCheat system deleting something that you wanted to stay.I won’t be explaining in depth how mine works either since it’s very long and I don’t want to get into it. There’s comments if you plan on using it and I’ll just list the main facts,
- It checks if player has tampered with their Humanoid’s WalkSpeed, JumpPower, HipHeight, and PlatformStand and sets the properties back to it’s ServerSide
- It checks if any object is added to client and deletes it if it isn’t in the Server. It also Kicks the player if the obj was a Local Script.
- It checks if any basepart’s cancollide has been turned off and sets it back to it’s server side.
- It makes an Attribute “CanCheck” (Boolean) on literally everything so the script doesn’t repeat itself and cause additional Calls on RemoteFunction. This may lag your game depending on it’s size.
- There’s a ignoreTbl that makes the AntiCheat ignore whatever’s in there.
Here’s my entire Module Script Code I used, you can just replace the old one if you were for some reason following along with the tutorial.
My anti cheat module
--[[
RULES, I guess,
- If object or property wasn't replicated on server, the object will get deleted or property set back
- Adds a Attribute "CanCheck" (boolean) to literally everything so scipt doesn't repeat, so may cause
lag upon joining depending on your device
]]
local toServer = game:GetService("ReplicatedStorage").RemoteEvents.toServer
local ObjInServer = toServer.ObjInServer
local CheckProperty = toServer.CheckProperty
local ignoreTbl = {} -- anything in ignoreTbl will get ignored by anti cheat
local plr = game:GetService("Players").LocalPlayer
table.insert(ignoreTbl,workspace:WaitForChild("Camera",5)) -- prevent camera glitching
-- Checks if object is replicated on server
local function ObjExsist(obj:Instance):boolean
if ObjInServer.Parent == nil then
task.wait(.2) -- prevents Parent error happening
obj:Destroy()
plr:Kick("Exploiting")
return false
end
if not ObjInServer:InvokeServer(obj) then
if obj:IsA("LocalScript") then
plr:Kick("Exploiting")
end
task.wait(.2) -- prevents Parent error happening
obj:Destroy()
return false
end
return true
end
-- Checks if Humanoid and sets up functions if it is
local function CheckHumanoid(obj:Humanoid):boolean
if obj:IsA("Humanoid") then
obj:GetPropertyChangedSignal("WalkSpeed"):Connect(function()
if CheckProperty then
obj.WalkSpeed = CheckProperty:InvokeServer(obj,"WalkSpeed")
else
plr:Kick("Exploiting")
end
end)
obj:GetPropertyChangedSignal("JumpPower"):Connect(function()
if CheckProperty then
obj.JumpPower = CheckProperty:InvokeServer(obj,"JumpPower")
else
plr:Kick("Exploiting")
end
end)
obj:GetPropertyChangedSignal("HipHeight"):Connect(function()
if CheckProperty then
obj.HipHeight = CheckProperty:InvokeServer(obj,"HipHeight")
else
plr:Kick("Exploiting")
end
end)
obj:GetPropertyChangedSignal("PlatformStand"):Connect(function()
if CheckProperty then
obj.PlatformStand = CheckProperty:InvokeServer(obj,"PlatformStand")
else
plr:Kick("Exploiting")
end
end)
return true
end
return false
end
-- Checks if BasePart and sets up functions if it is
local function CheckBasePart(obj:BasePart):boolean
if obj:IsA("BasePart") then
obj:GetPropertyChangedSignal("CanCollide"):Connect(function()
if CheckProperty then
obj.CanCollide = CheckProperty:InvokeServer(obj,"CanCollide")
else
plr:Kick("Exploiting")
end
end)
obj:GetPropertyChangedSignal("Anchored"):Connect(function()
if CheckProperty then
obj.Anchored = CheckProperty:InvokeServer(obj,"Anchored")
else
plr:Kick("Exploiting")
end
end)
obj:GetPropertyChangedSignal("Parent"):Connect(function()
if CheckProperty then
if obj.Parent == nil and CheckProperty:InvokeServer(obj,"Parent") ~= nil then
plr:Kick("Exploiting")
end
else
plr:Kick("Exploiting")
end
end)
return true
end
return false
end
-- Checks if part added is replicated on server and does it again for every new child added
local function partAdded(obj:Instance)
obj:SetAttribute("CanCheck",true) -- Sets this so RemoteFunctions don't run more than twince
task.spawn(function() -- checks what class it is and sets it I guess
CheckHumanoid(obj)
CheckBasePart(obj)
end)
obj.ChildAdded:Connect(function(child)
if ObjExsist(child) and not obj:GetAttribute("CanCheck") then
partAdded(child)
end
end)
for _,stuff in obj:GetChildren() do
task.spawn(function()
partAdded(stuff)
end)
end
end
-- Starts checking if obj and any object added in obj, if client has messed with it
local function mainFunc(obj)
task.spawn(function()
for _,stuff in ignoreTbl do
if stuff == obj then
return
end
end
if ObjExsist(obj) and not obj:GetAttribute("CanCheck") then
partAdded(obj)
end
end)
end
plr:WaitForChild("PlayerGui",5).ChildAdded:Connect(mainFunc)
task.spawn(function() -- deletes any object added to Freecam script(script that's added by Default by Roblox)
mainFunc(plr.PlayerGui:WaitForChild("Freecam",10))
end)
plr:WaitForChild("Backpack",5).ChildAdded:Connect(mainFunc)
plr:WaitForChild("PlayerScripts",5).ChildAdded:Connect(mainFunc)
game:GetService("ReplicatedFirst").ChildAdded:Connect(mainFunc)
for _,stuff in workspace:GetChildren() do
task.spawn(function()
for _,trackobj in ignoreTbl do
if stuff == trackobj then
return
end
end
if ObjExsist(stuff) and not stuff:GetAttribute("CanCheck") then
partAdded(stuff)
end
end)
end
workspace.ChildAdded:Connect(function(child)
for _,trackobj in ignoreTbl do
if trackobj == child then
return
end
end
if ObjExsist(child) and not child:GetAttribute("CanCheck") then
partAdded(child)
end
end)
--[[ Used to check whats in ignoreTbl feel free to delete
local UserInput = game:GetService("UserInputService")
UserInput.InputBegan:Connect(function(input,isType)
if not isType then
if input.KeyCode == Enum.KeyCode.E then
print(ignoreTbl)
end
end
end)
]]
return nil
Here’s the model of the Anti Cheat System I made if you want to test it out:
https://create.roblox.com/store/asset/103466681912144/Anti-Cheat-System
Really the main take away is the Mechanism, you can, at least as far as my knowledge about exploiters go, have an Client Side anti Cheat without having the exploiters just turn it off.