Feedback on my anti-cheat module

Hello! This is my first anti-cheat, which I made after an incident and reviewing common exploits. It’s mainly a server-sided character check, extends to walk speed and another layer with a client script that imitates the JumpWalkSpeed function (which blocks most common exploiters that rely on an interface).

Main ServerScript, parented under ModuleScript

Module = require(script.Parent)

--FUNCTIONS

local function IsWhitel(Player)
	local whitelist = Module.whitelisted
    for _,whitel in pairs (whitelist) do
		    if type(whitel) == "string" and string.lower(whitel) == string.lower(Player.Name) then
			return true
		elseif type(whitel) == "number" and whitel == Player.UserId then
			return true
        end
	end
end

function scalechange(part, property, treshold)
	local saveddefault = part[property].Value
	part[property]:GetPropertyChangedSignal("Value"):Connect(function()
		if part[property].Value > treshold then
			--print (part.Parent, property, "changed")
			part[property].Value = saveddefault
			daechwita(part)
		end
	end)
end -- Scale Changes to Humanoid

function abilitychange(part, vlue, treshold1, treshold2, treshold3)
	local saveddefault = part[vlue]
	part:GetPropertyChangedSignal(vlue):Connect(function()
		if part[vlue] > treshold1 or treshold2 or treshold3 then
		--print (part.Parent, vlue, "changed")
		part[vlue] = saveddefault
		end
	end)
end -- Changes to humanoid properties

function partcharremoved (char)
	local r6parts = {"Head";"Torso";"LeftArm";"RightArm";"LeftLeg";"RightLeg";}
	local r15parts = {"LowerTorso"; "UpperTorso"; "Head";
		"LeftFoot";"RightFoot";"LeftLowerLeg";"RightLowerLeg";"LeftUpperLeg";"RightUpperLeg";
		"LeftHand";"LeftLowerArm";"LeftUpperArm";"RightHand";"RightLowerArm";"RightUpperArm";
	}

	char.ChildRemoved:Connect(function(instance)
		if char.Humanoid.RigType ==  Enum.HumanoidRigType.R15 then
			for _,part in pairs (r15parts) do
		    if type(part) == "string" and string.lower(part) == string.lower(instance.Name) then
				game:GetService("Players"):GetPlayerFromCharacter(char):LoadCharacter()
				end
			end
		elseif char.Humanoid.RigType ==  Enum.HumanoidRigType.R6 then
			for _,part in pairs (r6parts) do
		    if type(part) == "string" and string.lower(part) == string.lower(instance.Name) then
				game:GetService("Players"):GetPlayerFromCharacter(char):LoadCharacter()
				end
			end
		end
	end)

	
end

--Running
game.Players.PlayerAdded:Connect(function(plr)
	plr.CharacterAdded:Connect(function(character)
		if IsWhitel(plr) then
			return 
		else
		local humanoid = character:FindFirstChildOfClass("Humanoid")
		local primarypart = character.PrimaryPart
			
	--Modify the treshold numbers at the end to your choice
		
		if Module.HealthModifications then
			abilitychange(humanoid, "MaxHealth", 100, 0, 0)
		end
		
		if Module.BodyPartMissing then
			partcharremoved(character)
		end
			
		-- Comes with client imitation of abilitychange. Modify on the localscript too
		if Module.JumpWalkProperties then 
			abilitychange(humanoid,"WalkSpeed",16,0,0)
			abilitychange(humanoid,"JumpPower",50,0,60)
				
			humanoid.Running:Connect(function(speed)
				if speed > humanoid.WalkSpeed + 5 then
        			plr:LoadCharacter()
				end
			end)
				
			local jumpwalkregulator = script.Parent:WaitForChild("JumpWalkSpeed"):Clone()
			jumpwalkregulator.Name = ""; jumpwalkregulator.Parent = plr:WaitForChild("PlayerGui")
		end
			
		if Module.BodyScaling then
			scalechange(humanoid,"HeadScale", 1)
			scalechange(humanoid,"BodyWidthScale", 1)
			scalechange(humanoid,"BodyDepthScale", 1)
			scalechange(humanoid,"HeadScale", 1)
				
			--[[local bodyscaleregulator = script.Parent:WaitForChild("BodyScale"):Clone()
			bodyscaleregulator.Name = ""; bodyscaleregulator.Parent = plr:WaitForChild("PlayerGui")]]
		end
			
	-- These are modified on the Module, or is fine being left like so.
			if Module.FakeScripts then
				for count = 1, Module.FakeScriptAmount do
					local fakescript = script.Parent:WaitForChild("DummyScript"):Clone()
					fakescript.Name = ""; fakescript.Parent = plr:WaitForChild("PlayerGui")
				end
			end
	end
	end)
end)

Since this is my first attempt at overcoming cheats, I’d like to hear from the more experienced scripters on inefficiencies in my script and parts I can improve on. Thank you so much!

4 Likes

Have you tested it to make sure that it actually works and detects these changes? If I change the WalkSpeed property of my Humanoid to 900, it will still be 16 on the server so the it will not detect any change. This means that detecting Humanoid WalkSpeed exploits through the server will not work by simply just reading the property.

As an alternative example, perhaps you could compare the Humanoid’s WalkSpeed to their Velocity and send them back a few steps if they go too far over.

3 Likes

This seems pretty good, although as @Vmena said, the server cannot detect speed exploits. I would do what Vmena suggests. For the first anti-cheat, this is pretty good!

1 Like

Testing will be the best way to figure out how effective the anti-cheat is, but on the scripting side a few things I noted:

In your IsWhitel() function, it’s evident that there’s a lack of data standardisation in your whitelist. I would personally stick to just using UserIds as they are immutable (players can change their usernames, not their UserIds). Plus, comparing numbers is much more efficient than comparing strings, and you don’t have to worry about case.

In partcharremoved() you define the variables r6parts and r15parts at the top of the function. If this function is called multiple times then you should defined outside of the function to save memory, as they are constants.
Also, since they are constants, you don’t need to assert that they are strings with type(part) == "string" - you already know they are.
You’re also doing extra work by running string.lower(part) when you could simply store the strings in your tables as lower-case.
Additionally, you should store the result of game:GetService("Players") in a variable outside of the char.ChildRemoved function for the same reason.

if part[vlue] > treshold1 or treshold2 or treshold3 then won’t work. This is equivalent to

if part[vlue] > treshold1 or treshold2 == true or treshold3 = true then

What you need instead is

if (part[vlue] > treshold1) or
   (part[vlue] > treshold2) or
   (part[vlue] > treshold3) then
...
...
...
end

In terms of code readability, you should use camelCase for your variable and function names (e.g. partcharremoved becomes partCharRemoved). Additionally, your temporary variables in your for loops should be less ambiguous - by using the variable name “part” to refer to a string is confusing. I would change this to “partName”.
Also there are some typos in your variable names, but this doesn’t change the efficacy of your script.

2 Likes

You should be using a local script as like Vemna said, it will not detect the changes from the server.

1 Like

No, don’t use a local script. ANYTHING that’s on the client can simply just be edited and disabled in a few minutes of work. ALL anti-exploit checking HAS TO BE ON THE SERVER, because that’s where the clients can’t touch it

2 Likes

It wont detect the changes then. You could also make it so it can’t be disabled.

Yup, I’ve tested it a while back and found out the client doesn’t update the server on this, forgot to remove the abilitychange() function on walk speeds. I’ve found a way to check walkspeeds on the server using Humanoid.Running. Thank you for pointing this out!

Sadly, it is not that simple.

Almost every aspect of the client is insecure, as there is no way no ensure its fidelity. In real-world applications, client-sided anti-cheat software only becomes viable when exposed to the client’s kernel; this is extensively outside the scope of Roblox from the standpoint of user-generated content.

Given this, the natural deduction is that the client is insecure and can never be trusted. Applying this logic further, to have a reliable and robust anti-cheat, it can not rely on the client, seeing that the game developer can never trust the client for reasons listed above.

1 Like

I learned a lot from your feedback, thank you so much. Reading your reply, I’ll say you made great points about a lot of inefficiencies I haven’t taken into account otherwise.

Thank you for pointing out the variable names as well. The script’s organization was meant for my own spontaneity at first, I didn’t really take the significance into account. I’ll start tidying them up and apply it in my other scripts as well.

1 Like

You could make a script that makes it so every few seconds it makes sure the anti cheat script is not disabled. That would make it annoying for the hackers to disable the extra protection.

Humanoid.Running is unreliable too as it sends based on the client’s order. Velocity checks will solve almost all the skids but there are definitely work arounds. An alternative would be to make your own sort of velocity number through distance checks over time, but false positives are pretty high so you have to be lenient.

Worst case scenario, a few speed exploiters slip through the cracks. It shouldn’t really be a huge deal.

2 Likes

Clients can change the contents of a LocalScript. If I delete all the code inside a LocalScript but don’t disable it, I’ll bypass this AE.

1 Like

What
you
need
to
understand
is
that
clients
can
edit
anything
on
their
machine.

Any local scripts are available to the client’s machine, and can be edited and tampered with. There’s nothing you can do about that. That’s why you need to code with the server as the authority.

3 Likes

However, again, this falls to the same failures as earlier.

An event handler would get dedicated to testing for a property of the script within the proposed fix. This implementation, however, only works on the assumption that said event handler stays listening to the event. A general trend in client-side anti-cheat patterns on Roblox is that they work as long as the person developing the cheat runs out of motivation or does not have the right skills, however, they are not absolute in their effectiveness.

Yes, it would be not very pleasant to workaround, but it is just one more layer of security to overcome before the entirety of the client-side anti-cheat is rendered as useless.

As an alternative, a server-side anti-cheat could get implemented that performs just as well with no chance of failure assuming each utility function is correct by design.

On paper, a server-side anti-cheat that takes some more time to build but is failproof is much more desirable than a client-side cheat that functions most of the time, especially when things like monetization are on the line.