Gun security using GUID?

I am trying to find ways to prevent exploiters from cheating and getting tags “illegally” and was thinking I could use GUID on the server to track bullets and make sure everything lines up when the client says they hit someone.

Firstly, I wanna state that I am using client hit detection. This is because using the server results in terrible UX (client will clearly see a bullet they fired hit a player, but that player won’t die)

So what I was thinking of doing is like so on the client:

local Origin = firePoint
local Direction = (mousePosition - Origin).Unit

local ActiveCast = Caster:Fire(Origin, Direction, SPEED, CastBehavior) -- Fire shot

ActiveCast.RayInfo.CosmeticBulletObject.BrickColor = player.TeamColor -- Set bullet color to team color
ActiveCast.UserData = {
	Creator = player,
	Origin = Origin,
	Direction = Direction
}
local GUID = Shoot:InvokeServer(ActiveCast.UserData, mousePosition)

ActiveCast.UserData = {
    Id = GUID,
	Creator = player,
	Origin = Origin,
	Direction = Direction
}

And then when the client hits a player

PlayerHit:FireServer(
	HitPlayer,
	result.Instance,
	result.Position,
	cast.UserData
)

castData.UserData would return this table

HitCharacter.Humanoid:TakeDamage(100) -- Kill player on your screen

castData.UserData = {
    Id = GUID,
	Creator = player,
	Origin = Origin,
	Direction = Direction
}

And then on the server I can check and compare the Id and make sure it’s a valid shot, and then confirm the kill on the server??

2 Likes

I might be missing something here but can’t they just false Invoke the server, get a GUID key and fire a false hit detection.

Something like this(?):

local GUID = Shoot:InvokeServer(faketablereplacinguserdata, fakemousepos)
PlayerHit:FireServer(
	HitPlayer,
	Instance,
	Position,
	{Id = GUID, ...}
)

The server creates the GUID, so if they insert their own GUID, then when it goes to the server on hit, it won’t be able to find their shot, and thus not kill the player

I’m omitting a lot of code above here, but this is what the server basically does

local ActiveCast = Caster:Fire(Origin, Direction, SPEED, CastBehavior) -- Fire shot

local GUID = HttpService:GenerateGUID()

ActiveCast.UserData = {
	Id = GUID,
	Creator = player,
	Origin = Origin,
	Direction = Direction
}
	
------------------------------------
BulletsFired[GUID] = ActiveCast.UserData

And so BulletsFired is basically a storage of all the bullets players have told the server they’ve fired. Obviously (in the future) I need to do like checks to make sure they aren’t shooting more than they should, shooting in the right direction they claim to be shooting from, etc.

and then also on the server, when the client fires PlayerHit, this is what the server checks for

--// Ray hit
local function Hit(player, playerHit, hitInstance, hitPosition, cast)
	if not cast then return end -- No cast sent
	
	if type(cast) ~= "table" then return end -- Cast wasn't a table
	
	local Bullet = BulletsFired[cast.Id]
	if not Bullet then return end -- Bullet does not exist (ILLEGAL)

and this should be able to see if said bullet actually existed?

I could probs do other checks on that hit function, like compare the origin and direction of cast from the client is telling us vs what the server knows, etc.

But the GUID that the exploiter is getting is from the server? They are not making their own.

You can’t really make a system that prevents exploiter from firing false shots. All you could do is make sanity checks that’s relevant (RPM, Ammo, Time between shot and hit). As I said the exploiter could get a GUID key and use that. Remember exploiter can see everything that a local script can see. Meaning they can see your remote’s so returning a key for a request isn’t reliable since the server can’t determine if the request is authentic or not.

This doesn’t directly answer your question, but I noticed you kill the player on the clientside then check on the server.

What’s the point of this? I can only think of two scenarios and it’s to bridge the gap between server and client lag or it’s to punish exploiters.

Problem with purely server is if I tag a player, my client will say I tagged them, but the server will say I didn’t. While yes, that can prevent exploits doing it on the server, the amount of backlash I’d face for players saying they hit someone and that person not doing would be greater. I know games like Arsenal use client side hit detection.

The idea tho, is while yes, an exploiter could say theyve tagged all these players, the server still does checks to make sure they fired a bullet, that bullet is near the players position, etc. to make sure its valid.

Killing a player on the client is purely visual. So exploiter can do it all they want, the server at the end of the day is what confirms and kills the player

1 Like

But what I’m saying is the server creates a unique key and assigns bullet the shot with that key. This is all on the server yeah. So yes, the player can see the specific id, but when they fire the PlayerHit, they need to pass that valid key back to the server. The server will then check if that key has a bullet and the server also checks if the bullet direction is correct, if the bullet is near a player, if the bullet is in the same trajectory as what the server has it set to (the server also does FastCast)

So the server has all this information stored. If a player passes an invalid key, the server hit function wont kill the player, wont do anything, cause they’ve returned an invalid key. If they send to the server a real key, then the server does checks to make sure that key they are sending is the same key that they fired earlier.

Basically, everytime the player shoots, the server replicates that shot on the server, tracks its trajectory etc. That bullet (on the server) has a unique key (or id) assigned to it. So when a player says
“Bullet Id: 123456789 has hit Player1”

The server is gonna look for the bullet with an Id of 123456789 and see whether or not that bullet

  1. Exists
  2. Has the same traajectory the client is telling us it has
  3. If the bullet is close enough to the player who the client claims they’ve hit

It dosen’t matter if the server creates the key. The exploiter can still get valid key because they can invoke the remote function that gets the key and the server won’t know if it’s authentic or not. They then use that valid key to make a false positive.

Oh yeah

I’m big on fps frameworks and experienced on these kind of stuff and I don’t recommend casting bullet on the server and also dealing the damage on the client since it can create confusion among those laggy folks.

The server FastCast is purely just rays (no bullets being shown) and rays aren’t intensive.

The point I’m trying to say is, when a player invokes the Shoot remote, the server is going to do a ton of checks, like check if the positions,etc. they are sending are correct (by checking the players position on the server, etc.) and if everything checks out on the server, then the server returns that Id and stores the bullet in a table. If the client then fires the hit remote event, they can’t send anything invalid or the server will know. If they send an invalid GUID, they get kicked, cause the server has stored all the real GUIDs that have been created. If their GUID matches a servers bullet GUID, then the server checks to make sure that the servers raycast is correct and close enough to hit a player. If not, they get kicked (for obviously sending a fake result)

This is server right, above I have other checks, but if the servers feels that the Shoot Invoke is valid enough, then it will create the server side raycast. Note the BulletsFired

-- Set up cast behavior
	local CastBehavior = FastCast.newBehavior()
	CastBehavior.RaycastParams = CastParams
	CastBehavior.Acceleration = Vector3.new(0, -GRAVITY, 0)
	
	local Origin = castData.Origin--firePoint
	local Direction = castData.Direction--(mousePosition - Origin).Unit
	
	local ActiveCast = Caster:Fire(Origin, Direction, SPEED, CastBehavior) -- Fire shot
	
	local GUID = HttpService:GenerateGUID(false) -- Generate a unique bullet Id
	
	ActiveCast.UserData = {
		Id = GUID,
		Creator = player,
		Origin = Origin,
		Direction = Direction
	}
	
	BulletsFired[GUID] = ActiveCast.UserData -- Store the fired bullet

Then when client fires Hit, server is like so

local function Hit(player, playerHit, hitInstance, hitPosition, cast)
    if not cast then return end -- No cast sent
	
	if type(cast) ~= "table" then return end -- Cast wasn't a table
	
	local Bullet = BulletsFired[cast.Id]
	if not Bullet then player:Kick("Don't hack kid") return end -- Bullet does not exist (ILLEGAL)

so the cast id they send MUST match an id that was assigned on the server. They can’t send anything they want, because the server has all the real ids stored, so the only way to get past this is by sending the id the server has assigned to that bullet.

BulletsFired is serverside. So the server will always have accurate accounts of what bullets exist and dont exist. So an exploiter can create a amillion bullets on their screen, but the server wont have any invalid ones stored, and thus all those bullets on their client can hit things, but since the server doesnt have record of them existing, they wont do anything

1 Like

I feel like you’re not understanding me. The exploiter will get a key thats valid because they will send valid information and then send false hit detection positive say for example you’re an exploiter, you just get the remote event, find the arguments and Invoke get the key and send a hit detection with the valid key.

I feel like you’re not understanding me :sweat_smile:

To get the key, they ned to send valid information, correct. But when they send invalid information on hit detection, the server picks that up. Like I said, the server is also doing a fast cast to the SAME information they are sending when they shoot. And that fast cast is being assigned the id too. So if they send invalid information on hit, the server knows, because the server has the id assigned with all the information

ActiveCast.UserData = {
	Id = GUID,
	Creator = player,
	Origin = Origin,
	Direction = Direction
}

So with that, on hit detection, I can see where the bullet is on (the server) and see if its close enough to said player for them to have been hit. So if the player says they hit “Player2”, the server can look and see where Player2 is (on the server) as well as if the ray they sent would have hit said player.

Rays may not perfectly sync up on the server, but I can use like .Magnitude to make sure that the hit position they are sending is close enough to the player for it to be valid.

I struggle to see how an exploiter could send false arguments on hit with the valid key. The server will be able to tell if what they are telling is false

playerHit, hitInstance, hitPosition, cast

are the arguments they have to send on hit. They can’t send false cast, as thats what stored the Id, so if anything inside that table does not match up with the server, the server knows they are lying.

hitPosition is where the client claims they hit the player, the server can see (from the raycast done on the server) whether that position is valid in the rays trajectory.

As long as those 2 things check out, playerHit and hitInstance are irrelevant.

So If I understand you correct

Client Shoots > Gets ID > Server cast ray > Client also replicates bullet > Hit detection on client > Server does some sanity check

Few problems, the client and the server will not sync, this may not seem as huge of a problem as it seems but the client can hit something else and on the server the hit will register as something else. This already kills performance from regular laggy players. Furthermore exploiters could just simulate they are firing while they are not (ie send direction and will automatically face the valid direction) (or just aimbot). There isn’t a reliable solution to this. Cooperating with the client is not a good idea

Yes. And the server and clients don’t sync, I know, but they’ll be close enough. The servers hit detection is purely to remove the bullet from storage

-- Server
--// Ray hit on server (empty the shot)
local function OnRayHit(cast, result, velocity, bullet)
	local GUID = cast.UserData.Id
	if not BulletsFired[GUID] then return end
	
	BulletsFired[GUID] = nil -- Clear the bullet table
end

I understand certain things can’t be solved, but relying on hit detection on the client is by far the best thing to do on Roblox. Arsenal does it, Phantom Forces does it. Server side hit detection is just way too inaccurate. This is as best as I can get it to my knowledge. All I know is client hit detection is used in those games. You can’t use server side hit detection, without players ending up leaving, disliking, and sending hate messages cause the games “broken” when I can’t control server side latency :man_shrugging:

I’m not encouraging to use server hit detection, I myself use client hit detection. I’m saying the way you check the key and the information is not reliable. Also using server hit detection will make it worst for the player.

Sounds like your system is a good step towards preventing cheating. Just make sure the bullet origin is coming from where their character is and they aren’t teleporting or using other movement-based cheating. Because if they can cheat where their character is, they don’t need to cheat where their bullets are.

What do you recommend doing for checking then?? Cause I’m lost on what’s the best practices

Put it simply, sanity checks. Is the player firing more round than they should? Is the request firing is consistent with the gun’s rpm? (factoring ping) Is the shot and the hit consistent with the approximate velocity? Cooperating with the client is almost guaranteed to fail since exploiters will most likely find a way to disable or bypass it.

It isn’t worth it to sacrifice performance for security, this system sounds like a good idea and yes most likely it would prevent rookie exploiters, but an expert at exploiting will release a code that will bypass this so it’s useless to even consider making a client cooperating anti cheat

Always keep gun’s properties on the server (damage, rpm) if the hit detection is triggered take damage from the property stored on the server. This ensure you’re not trusting the client for the gun’s damage

Regarding casting ray on the server, yes that’s a good idea but don’t be too strict on the checks, I recommend you only checking the approximate hit and not checking what part hit because they will not sync

Something that might be problematic: you have to wait for the GUID to return from the server to detect a hit on the client. What happens if the hit occurs before the client receives the GUID? My thought is that you don’t actually need to generate the id on the server, you can just do it on the client and send it along with the initial request. Then only store the id on the server if the shot passes all the sanity checks.

Sorry but this can be spoofed easily. Honestly the best way to create a secure game is to do sanity checks.

Like???

I’m doing sanity checks. This id is part of the sanity check. There’s only so much I can do. Without this Id system, exploiters can fire the hit event without even having fired a bullet

1 Like