Making an anti-cheat system, but want to know how to detect changes on the client

Hi, Tophat here.
I’m currently trying to make an anticheat system for my game.

Currently, all I need to do is to check for changes in the Humanoid’s WalkSpeed and JumpPower.
The problem is, exploiters can change it on their client, and will not be detected by the server. So, I’ll need to know if someone is changing values on their client.

I plan on using a LocalScript to detect changes. Now here me out, I KNOW NOT TO TRUST THE CLIENT. I will use a server script to check if the local script responds, using remote functions to check if the client responds. (I am following an unofficial book guide for this, search up “The Advanced Roblox Coding Book - An Unofficial Guide”) A server-sided script to check for changes has also been created as a second safety measure.

My main concern is if exploiters can EDIT LocalScripts, as in changing the code, deleting all lines of code on the script and subsequently making my efforts go into the bin. Or is my entire plan just going to fail from the start? Is there any better way?

Thanks for listening, and I hope you can help!

1 Like

Everything you implement client-sided can be bypassed as the user has access to everything on their machine (client).

Detecting these changes on the client is not your best option here.
Instead, you can patch what replicates to the server (ex. their position).

2 Likes

Is it possible to reset a player’s walkspeed and jumppower on the server by setting up a loop that sets it back to default? Or does the client override that.

1 Like

Is it possible to reset a player’s walkspeed and jumppower on the server by setting up a loop that sets it back to default?

You shouldn’t do that, but you may.

What you should do is have a loop that checks how fast they travel within x seconds since their position is replicated automatically.

2 Likes

That sounds like a good idea, but what if the player got teleported or is falling at a fast speed? I’m going to have to make more arguments for those, I guess.

Oh, and also what are the cons of making a loop to reset values?

Yes, check their Y axis as well for the falling part.

Oh, and also what are the cons of making a loop to reset values?

I just don’t recommend having that in a loop as they could bypass that as they don’t neccessarily just use speed.

1 Like

If you make a loop, you won’t be able to change the values, because it would either kick the player or set them to whatever is in the loop.

I think the max falling speed of players is always the same, so if you put it greater then that.

For teleports maybe you could make a remoteEvent that breaks the loop and then starts it again once the loop is finished?

1 Like
local module = {}

local subjects = {}

local function RemoveY(v3)
	return Vector3.new(v3.X, 0, v3.Z)
end

game:GetService("RunService").Heartbeat:Connect(function(deltaTime)
	for root, info in pairs(subjects) do
		local position = RemoveY(root.Position)
		local lastPos = info.LastPosition
		
		if (position - lastPos).Magnitude >= (info.Tolerance * deltaTime) then
			position = lastPos
			
			root:PivotTo(CFrame.new(position.X, lastPos.Y, position.Z))
		end
		
		info.LastPosition = position
	end
end)

function module.Start(character)
	local root = character:WaitForChild("HumanoidRootPart")
	local humanoid = character:WaitForChild("Humanoid")
	
	local info = {
		LastPosition = RemoveY(root.Position),
		Tolerance = 0,
		
		Connections = {}
	}
	
	local rootConnections = info.Connections do
		local function CFrameChanged() -- the server set the cframe, we dont move them back
			info.LastPosition = RemoveY(root.Position)
		end

		local function ToleranceChanged()
			info.Tolerance = (root:GetAttribute("Tolerance") or 15) * humanoid.WalkSpeed
		end

		table.insert(rootConnections, root:GetPropertyChangedSignal("CFrame"):Connect(CFrameChanged))
		table.insert(rootConnections, root:GetAttributeChangedSignal("Tolerance"):Connect(ToleranceChanged))
		table.insert(rootConnections, humanoid:GetPropertyChangedSignal("WalkSpeed"):Connect(ToleranceChanged))

		task.spawn(ToleranceChanged)
	end
		
	subjects[root] = info
end

function module.Stop(character)
	local root = character:FindFirstChild("HumanoidRootPart")
	local info = subjects[root]
	
	if info then
		for _, connection in ipairs(info.Connections) do
			connection:Disconnect()
		end

		subjects[root] = nil
	end
end

return module

I’m not sure if this is what you’re talking about, but I whipped this up. It’s not perfect or nothing, but It’s a relatively generous anti-teleport / anti-speed if you will, it doesn’t consider the Y axis though, so fly / hover hacks would still be possible

To use it, require it via require(pathToModule) and then call .Start and .Stop to start it, and stop it respectively. Pass a players character as the argument each time

3 Likes

I meant setting up a while true do loop that sets Humanoid values back to a specific number, such as 16, and the loop runs every 2 seconds. What would the disadvantages be for this?

Update: This doesn’t work at all, I’m afraid. Going to have to use positions and other methods instead.

EDIT: Figured it out, it was because it doesn’t change anything because the server sees the same value all the time when edited on the client. I fixed it by making the speed shift by 0.01 and back momentarily, enough so nobody sees a difference while allowing it to stop client-sided changes!

Ignoring that polling every 2 seconds and setting them to normal only stops them for a little bit (assuming they don’t just block the property change packet anyway) and is redundant;

What are you going to do if they move with physics, not humanoid properties? E.g they create a body position with an absurd force to go everywhere, by overseeing position overall (the thing i sent) rather than just humanoid properties, you can solve that and many more things.

This wouldn’t work, as an exploiter can just set their WalkSpeed to whatever they want as soon as you change it, or do it on RenderStepped. (That’d be if you are doing that on the server)
Or, if you’re doing that on the client, they could hook __newindex and prevent your script from setting the walkspeed altogether.

You’re better off verifying how fast the player moved on the server (don’t check for WalkSpeed, as changes on the client don’t get sent to the server), so check how fast the HumanoidRootPart moved, take ping into consideration, the WalkSpeed you expect the player to have, etc.

2 Likes

I agree, however i disagree with these parts:

the WalkSpeed you expect the player to have

The server’s version of the WalkSpeed property can be used reliably to determine how fast they’re expected to be, but an additional Tolerance value like you mentioned is recommended for non-walkspeed game idioms

take ping into consideration

This cannot be used reliably to my knowledge, this would only hurt more than it helps. Assuming a perfect case, you can make it so the player can’t send a time in the future (meaning they can’t lower their ping) but what does it matter if they can delay the ping time all they want, and get a really really non-strict movement pattern due to ping compensation?

2 Likes

Ah, ####. I’m going to have to rework my teleports and jump-boosters in my game now. Find some method to oversee position while taking in consideration for teleports, jump boosters, falling, exploding on death, people with high lag and accidental flings. RIP any freetime I have.

Or you know what? I could just make a votekick and let players deal with exploiters themselves. I could just do that as a temporary fix as I try make a legitimate automated anticheat.

Thanks for the info though, now I know so I don’t spend too much time on an easily bypassable script.

You can do what my anti-movement thing did, detect when the server itself changes the CFrame property and then reset the last position, which essentially will tell the server to shut up about movement for the current frame.

But yes, dealing with all those cases is more than it’s worth most of the time. You can just make the tolerance value absurdly high, or foregoe it entirely. Up to the creator

Yes, tolerance is definitely something that should be implemented, otherwise the slightest lag spike / unexpected movement caused by physics is gonna end up messing with all the players, however, as for WalkSpeed, you can use the server one if and only if you do all your WalkSpeed stuff on the server, but most people do sprinting, crouching, and other stuff that’d affect WalkSpeed on the client, and, since the server doesn’t really know when WalkSpeed updates (unless you specifically implement an event for that), there’s really no way to know what the player is doing or if they’re expected to go slightly faster than normal.

As for ping, it can’t be modified afaik, no exploits can mess with the value returned by Player:GetNetworkPing() since that’s calculated on the server.
Yes, they could use some form of lag switch, latency can’t be fully trusted, but it should definitely still be taken into consideration when calculating tolerance in case a player is genuinely lagging.

For anyone reading this topic and for any lurkers in the future, here are a handful of most clientside physic exploits and a solution on how to stop them from doing it.

Physic exploit 1: Changing their walkspeed.
Solution: Every heartbeat frame, check their magnitude of their current and previous position. If it’s higher than their walkspeed (you’ll need to create a mathmatical formula to get these numbers) then send them back to their original position.

As a bonus, this also stops exploiters from teleporting.

Physic exploit 2: Noclipping
Solution: Every heartbeat frame, send a raycast starting at their previous position and their current position. If they go through an object, send them back. For performance beneficial reasons, you can get objects in a 10 stud radius using OverlapParams and only check if they go through nearby parts instead of the whole game.

Physic exploit 3: Flying/Jump exploits
Solution: Be warned, this is definitely the hardest physic exploit to prevent. In short, you have to constantly check what the player is standing on every heartbeat. If they aren’t standing on an object for an x amount of time (you’ll have to factor in jump power and gravity) then reset them to their previous position.

Also, when you send a player back to their position, I suggest also taking away NetworkOwnership of their HumanoidRootPart. This means the player can’t teleport back, walk through clientside objects, etc, for a short amount of time (Until you give it back).

4 Likes

Don’t waste your time. I would only do what I can to stop movement exploits then leave it at that.

1 Like