Making a good speed hack detector

You know what, I’ve done some testing and I’ve actually made it pretty alright.
Now this was fully tested in Studio, I highly recommend you test it yourself in your own game but even when changing the player’s state on the client (yes, it does replicate - I tested to make sure) and the server gives lenience of 100 studs per second (I check every .4 seconds so it translates to 40 studs on the Y axis every interval if the player is falling).
I added a Speed Gain feature to the module and tested with a +100 stud conveyor, and it works really really well!

So this is the new module for it.

local SpeedhackDetector = { tracking = {}, whitelist = {}, additionalSpeeds = {tags = {}, speeds = {}}, lastCheck = tick() }

local XZ = Vector3.new(1, 0, 1)
local Y = Vector3.yAxis

function SpeedhackDetector.Add(character)
	SpeedhackDetector.tracking[character] = character.PrimaryPart.CFrame
	
	character.Destroying:Connect(function()
		SpeedhackDetector.tracking[character] = nil
	end)
end

function SpeedhackDetector.WhitelistCharacter(character, duration)
	local tag = tick()

	SpeedhackDetector.whitelist[character] = tag
	
	task.delay(duration, function()
		if SpeedhackDetector.whitelist[character] == tag then
			SpeedhackDetector.whitelist[character] = nil
		end
	end)
end

function SpeedhackDetector.BlacklistCharacter(character)
	SpeedhackDetector.whitelist[character] = nil
end

function SpeedhackDetector.ChangeSpeedGain(character, speedGain)
	SpeedhackDetector.additionalSpeeds.speeds[character] = speedGain
end

function SpeedhackDetector.IncreaseSpeedGain(character, tag, speedGain)
	if not SpeedhackDetector.additionalSpeeds.speeds[character] then
		SpeedhackDetector.additionalSpeeds.speeds[character] = 0
		SpeedhackDetector.additionalSpeeds.tags[character] = {}
	end
	
	if SpeedhackDetector.additionalSpeeds.tags[character][tag] then
		return
	end
	
	SpeedhackDetector.additionalSpeeds.tags[character][tag] = true
	SpeedhackDetector.additionalSpeeds.speeds[character] += speedGain
end

function SpeedhackDetector.DecreaseSpeedGain(character, tag, speedGain)
	local current = SpeedhackDetector.additionalSpeeds[character] or 0
	
	SpeedhackDetector.additionalSpeeds.tags[character][tag] = nil
	SpeedhackDetector.additionalSpeeds.speeds[character] = math.max(current - speedGain, 0)
end

function SpeedhackDetector.Update()
	for character, lastCFrame in SpeedhackDetector.tracking do
		local humanoid: Humanoid = character:FindFirstChild("Humanoid")
		
		if not humanoid or SpeedhackDetector.whitelist[character] then
			continue
		end
		
		--// Did a test, terminal velocity of jumping is roughly ~1.063*jumpPower.
		--// I tested on different masses, gravities, jump powers, etc. All gave this result.
		local distancePerSecond = humanoid.WalkSpeed * 1.1
		local jumpDistancePerSecond = humanoid.JumpPower * 1.063 * 1.1
		
		if humanoid:GetStateEnabled(Enum.HumanoidStateType.Freefall) then
			jumpDistancePerSecond = 100
		end
		
		local speedGain = SpeedhackDetector.additionalSpeeds.speeds[character]

		if speedGain then
			distancePerSecond += speedGain
			jumpDistancePerSecond += speedGain
		end
		
		local maxDistance = (tick() - SpeedhackDetector.lastCheck) * distancePerSecond
		local maxDistanceY = (tick() - SpeedhackDetector.lastCheck) * jumpDistancePerSecond
				
		local currentCFrame = character.PrimaryPart.CFrame
		
		local compLast = lastCFrame.Position * XZ
		local compCurrent = currentCFrame.Position * XZ
		
		local compLastY = lastCFrame.Position * Y
		local compCurrentY = currentCFrame.Position * Y
		
		if (compLast - compCurrent).Magnitude > maxDistance or (compLastY - compCurrentY).Magnitude > maxDistanceY then
			print("Player exceeded max distance")
			character:PivotTo(lastCFrame)
			SpeedhackDetector.tracking[character] = lastCFrame
		else
			SpeedhackDetector.tracking[character] = currentCFrame
		end
	end
	
	SpeedhackDetector.lastCheck = tick()
end

return SpeedhackDetector

And I added a folder that contains items that will speed you up by touching it, but the API supports anything.

local function InitializeSpeeder(speedPart)
	local gain = speedPart:GetAttribute("SpeedGain")
	
	speedPart.Touched:Connect(function(h)
		local c = h:FindFirstAncestorOfClass("Model")
		
		if c and c:FindFirstChild("Humanoid") then
			SpeedhackDetector.IncreaseSpeedGain(c, speedPart, gain)
		end
	end)
	
	speedPart.TouchEnded:Connect(function(h)
		local c = h:FindFirstAncestorOfClass("Model")

		if c and c:FindFirstChild("Humanoid") then
			task.delay(1, SpeedhackDetector.DecreaseSpeedGain, c, speedPart, gain)
		end
	end)
end

for _, speeder in workspace.SpeederUppers:GetChildren() do
	InitializeSpeeder(speeder)
end

We can walk on the conveyor
https://gyazo.com/036d1cf6e741b772ae058c0d64d7a046

But not speed hack!
https://gyazo.com/fa8f3d14aa2317e81f324a497f8c804c

This is still not perfect though, exploiters can use around up to 110 Jump Power before being detected.
And this doesn’t address the velocity you get when jumping off a Truss.

Simulating 400 ping- it works well :slight_smile:
https://gyazo.com/ae185df197f3e15683c3bed28ba61e35

1 Like

exploiters can spoof their ping, they got full control over the client

exploiters can fire the touched event on any part they want, making that useless. they can just fire the touched event and be able to speed around

In order to spoof their engine calculated ping, they would have to literally hack the client and modify the code. That would involve reverse engineering the client and reading assembly code. Not an easy feat I can assure you. I’ve had training in it, and I’m telling you it’s not easy.

Games tend to combat this by calculating the distance walked and then pushing the player back to the position where the server expects them to be at. If you lag you will experience something known as ‘rubber banding’ which is quite the common phenomenon and there’s nothing to be done about it.

There are a few horrible ways to approach and handle this problem that I’ve seen some people in this discussion suggest like @bIorbee, @O3_O2:

WHAT NOT TO DO:

  1. Calculating distance covered by players every Nth second.
  2. Calculating distance covered by players every 0.15 seconds.
  3. Calculating distance covered by players every Heartbeat step.
  4. Kick the players when violating either step 1, 2 or 3.

WHY NOT:

  • The reason you do not want to use step 1 and 2 is because exploiters can figure out the interval when the validation takes place and can teleport to any position on the map and then quickly back, essentially tricking the anti-cheat before facing consequences.

  • The reason you do not want to use step 3 is because running a distance calculation on every frame after the physics simulation have taken place gets very laggy with multiple people and can be more prone to false positives anyway (see my next point on false positives). As a side note, always try to use accumulator loops to mitigate any server lag.

  • The reason you do not want to use step 4: You should never punish someone for covering a distance by kicking them. People can get flinged over great distances by either exploiters or weird physics. Your character might fall off the map. You might have teleport functionality in your game that you’d have to incorporate into your validator. Etc etc.

What is the solution?

Games tend to combat this by calculating the distance walked and then pushing the player back to the position where the server expects them to be at. If you lag you will experience something known as ‘rubber banding’ which is quite the common phenomenon and there’s nothing to be done about it.

The thing I really hate about Roblox’s characters is that you automatically have network ownership of the character. There’s no way to create a middle layer between that traffic unless you create your own character rig on the server (perhaps a clone of R6 or R15) and send movement data over through remotes perhaps in 10 ticks (data would consist of things like which keys are being held and what direction you are facing etc etc, it can be formatted in many ways.) and then move the character rig on the server according to the information passed through the remotes from the client (max 20 ticks per remote due to new limitations) which would be a horrible thing to do because that creates network lag. It would in theory however be the best way you could accurately validate and update positional data, prevent animation exploits and prevent people from teleporting past the maximum movement distance before the validation kicks in ‘after’ the movement was already made. You could then proceed to use body movers to make the motion between each character movement tick smoother.

True, but this is pure example, if the player is shot from a cannon for example, it would purely be bound to that feature and wouldn’t come from a Touched event or anything like that.

Besides we can do some basic magnitude checks or relative cframe checks to verify the player is actually able to get the speed gain.

Wrong.

How would you combat this? My only idea right now is to randomize the interval.

I don’t think you really can without just making it either have random intervals or just making it faster.
Besides the client will never be perfectly synced with the server because of various delays and ping changing rapidly.

Like I mentioned in the post you’d almost have to create a clone of the players character (then delete the original character or remove network ownership) on the server and have the client send movement data to the server through remotes with data consisting of like keyboard presses, mouse and camera (for orientation) only and then handle that movement logic for the new character rig on the server accordingly. That way you still allow correct physics and animations to take place because the client will not have network ownership of the character so they can’t exploit it at all. This allows you to create that middle layer I was talking about in my post. Sure it’s not a very flattering suggestion for obvious performance related reasons not to mention having to re-create or mimic how movement works for the character (movement between movement ticks can be made smoother with body movers).

I sort of have an idea about what you’re talking about, but my method would be extremely laggy.

What you’re explaining exactly is what Chickynoid is doing.
Check it out: Chickynoid, server authoritative character replacement

1 Like

Oh well perfect! I guess since there’s something like that already readily available for the public then that should marked as the solution. :+1:

It’s heavily a work-in-progress and it’s not perfect but it does technically fully patch those types of exploits. It’s very cool technology but the steep learning curve and the necessity of understanding the system to actually work with it and modify it makes it sort of hard to work with, but it’s possible and very cool!
It can be quite bad with 100+ ping though, I believe.

I’m not quite sure it’s solution-worthy for this person’s application but it is very cool and worth checking out. I’ve sent some good solutions that don’t really have any drawbacks except the one you mentioned- that can pretty much be fixed by replacing the interval constant with math.max(math.random(), .2) and proper use of the API. TBH idk what the OP is looking for if nothing we’ve posted is solution-worthy.

If you’re interested in getting to know Chickynoid and it’s developer, MrRocketChicken here’s a good seminar he made on it!

The solutions that were posted are good, but hard to integrate with my framework. I did some testing on the states and manually changing them from the client. It seems the default state is running. Changing to any other stay will result in it changing back to running. I think this may be enforced by Roblox. I tried it on both the client and the server. So the only ones that I really need to worry about is platform stand, physics, and sitting. Everything else switches to running in my tests.

As for the artificial lagging, there’s not much that can be done about that. If an exploiter wants to add lag to his game to defeat an anti-cheat, that really hurts them more than anyone else.
But I did figure out a formula to use.

@O3_O2 @malakopter

After doing some experiments, I found out that, surprisingly, that the client is less precise than the server when doing speed check calculations. The server error between calculated distance travel (adist) and actual traveled distance traveled (tdist) is about 5% on average. For the client, it varies a lot (up tp 20% sometimes). And that’s with no cheats active and incorporating the ping into the calculation. This is in Roblox Studio though. I haven’t tested it on the live server, which may yield far different results.

However, I was able to test my punishment code. Although there does seem to be a problem with the server taking network ownership of the root part. I may need to switch the entire character’s network ownership instead of one part.

I’ve implemented my system into a project I’m working on it’s working without any hitches and basically no false positives. You can increase the boundary value for less false positives

I’ve implemented a heater that ticks down about every 15 seconds or so. In normal use, you wouldn’t trigger the anti-cheat. Different checks are done depending on what the character state is. Different checks are done for the data coming from the client vs. the data collected on the server. Any data gathering on the client can be spoofed by a hacker, but it adds one more layer of sophistication that the hacker must overcome. Unless the exploiter hacks Roblox, they cannot bypass server side checks.

I also have input checking for all values coming in from the client across all events. So an exploiter can’t send something weird like NaN or nil because that will be detected. I have other checks too besides speed, but I’m not going to discuss that here.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.