Usually you’ll come across developers on the forum constantly advising you to have any sort of anticheat scripts on the server, but in some cases it may be necessary for you to perform checks on the client. This would usually be the case in genre-games/combat technologies and not some money making simulator game, but would work wherever.
Anticheats on the client are obviously at risk of simply being deleted, but you can detect their deletion on the server.
local players = game:GetService("Players")
local runService = game:GetService("RunService")
local HTTP = game:GetService("HttpService")
local WebHook = "put your webhook here"
function checkScripts()
for i,v in pairs(players:GetChildren()) do
local character = v.Character or v.CharacterAdded:Wait()
local conn = character.ChildRemoved:Connect(function(instance)
if instance:IsA("Script") then
sendWebhook(v)
task.wait(1)
v:Kick("You were kicked for deleting scripts.")
end
conn:Disconnect()
end)
end
end
function sendWebhook(player)
local data = {
['avatar_url'] = "http://www.roblox.com/Thumbs/Avatar.ashx?x=100&y=100&Format=Png&userId=".. player.UserId,
['username'] = player.Name,
['content'] = "Script deletion"
}
local dataFinal = HTTP:JSONEncode(data)
HTTP:PostAsync(WebHook, dataFinal)
end
runService.Stepped:Connect(checkScripts)
-- you could do a while loop instead (while task.wait() do etc)
This will check if any script is removed from the character & will send a webhook to a channel of your choice to log it in your discord. You could delete it if you want and replace it with a permanent ban if you have a system in place for that.
This will protect local scripts in StarterCharacterScripts, not StarterPlayerScripts.
If you really wanted to go the extra step, you could obfuscate your client-sided code so that people can’t view the code with Dex Explorer & if they delete it they obviously would get kicked.
I didn’t realize that replicated. Still, I wouldn’t rely on it since hook functions and xpcalls can bypass the script without deleting it. Pretty interesting though
It’s not the perfect permanent solution but definitely would help some of the communities out there. For the most part I’ve been dealing with a lot of skids so haven’t had too much of an issue with just this securing the client scripts.
There’s a memory leak here, you repeatedly bind to .ChildRemoved which will use up the available memory very quickly (checkScripts() is called every step)
That’s true but below I’ve mentioned that you could use a while loop instead at any increment you want so it isn’t called every step but yeah you’re right.
There’s no point in running it whenever somebody joins the game as people could just delete the script at any time and if someone hasn’t joined the game at that exact moment, they wouldn’t be detected.
I would also add a handshake to combat disabling THIS IS AN EXAMPLE HANDSHAKE
--Server Script
local RemoteEvent = ReplicatedStorage.RemoteEvent
local UserData = {}
RemoteEvent.OnServerEvent:Connect(function(Player) --once remote fired
UserData[Player] = tick() -- reset data
end)
Players.PlayerAdded:Connect(function(Player)
UserData[Player] = tick() --set data upon joining
task.spawn(function()
if UserData[Player] - tick() > 10 then --if user didn't first within 10 seconds
Player:Kick("Possible Disabling") --kick the user
end
end)
end)
-- client script
while task.wait(5) end --fire the remote like every 5 seconds
Remote:FireServer()
end
--if the client script gets disabled the remote will no longer fire which means the script was disabled
You should make the message more vague, the one shown in the video is very apparent to what happened, and it won’t take much time for the exploiter to find out how to workaround it. Or, even better, don’t kick them at all and make them glitch out or lose connection.
Also, instead of just agreeing with NWhut, you should fix the script. This may do the trick:
function checkScripts()
for i,v in pairs(players:GetChildren()) do
local character = v.Character or v.CharacterAdded:Wait()
local con
con = character.ChildRemoved:Connect(function(instance)
if instance:IsA("Script") then
sendWebhook(v)
task.wait(1)
v:Kick("You were kicked for deleting scripts.")
end
con:Disconnect()
end)
end
end
Yeah fixed it. Also I mean it would be kinda obvious if they delete a script and then get kicked for it, they’d already know why they got kicked. In the game that we put this in, we force them to lag insanely and then disconnect on their own, just added a kick here for general usage purposes if anyone wants to use it.
You should probably put a note on how experienced exploiters can disable the script without changes replicating to the server.
A lot of the time, these checks are easily bypassed. So what if the majority of exploiters are skids? That top 10% are the ones making the exploits. I’m not saying that having a client-side anti-exploit is a bad idea, it’s just very temporary, and you should be investing more time into server-side anti-exploiting measures that can’t be broken.
There will also be people who deobfuscate your code, not to mention that, who knows what obfuscated code looks like when decompiled? Does it look obfuscated? Or is Luau optimized enough to the point where only the actual code is there, not the obfuscation?
An exploiter could also have a script that fires it every 5 seconds.
It’s a waste of bandwidth for everyone + the server to have the scripts in every player’s character as this means that there are more objects replicated to the clients that provide absolutely zero content nor functionality to any client that doesn’t possess that character model.
Now, I’m no Einstein, but:
local character = game:GetService("Players").LocalPlayer.Character
local obj = character.Health -- default 'Health' script (for example purposes)
obj.Parent = game.CoreGui -- any non-nil obj which isn't a descendant of your Character model
obj:Destroy()
The way I implemented mine was by having the client solve a simple arithmetic function dependent on its own previous results in the security loop and connected functions. Then it sends the array of results back to the server every second which verifies it. The player gets kicked if nothing was sent for 5 seconds or the values are invalid. It’s kinda dumb, but makes it harder for exploiters to replace the script with their own.
The problem with the OPs implementation is that it doesn’t check ReplicatedFirst, which is a better script container if you want your scripts to run as early as possible.