Gun system issue: Trying to minimize exploits

Hello, so I’ve been trying to make a decent and performant gun system the past month and a half, however I’ve been running into issues that I think have to do with exploiting.

Currently, the ammo system for my weapons is handled on the server through a Remote Function event where the player clicks R on a local script, then a remote function is fired to the server to replenish the ammo on the server, then it returns back to the client to replenish the ammo there as well. If that was confusing, here is how it is done:

This is on the Client:

	if aN == "Reload" and iS == Enum.UserInputState.Begin then 
		if Ammo < MaxAmmo then 
			CAS:UnbindAction("ARShot")
			CAS:UnbindAction("Reload")
			firing = false 
			Reloading = true 
			
			AmmoCounter.Text = "Reloading..."
			local Reloaded = Reloader:InvokeServer(Ammo)

			if Reloaded then 
				Ammo = MaxAmmo
				Reloading = false
				CAS:BindAction("Reload", Reload, false, Enum.KeyCode.R, Enum.KeyCode.ButtonX)
				CAS:BindAction("ARShot", AR, false, Enum.UserInputType.MouseButton1, Enum.KeyCode.ButtonR2)
				AmmoCounter.Text = "Ammo: ".. Ammo .."/".. MaxAmmo
			end
		elseif Ammo == MaxAmmo then 
			return 
		end


	end
end

This is on the server with the first being the reload function, and the second being the ServerInvoke:

function Reload()
	
	if Ammo == 0 then 
		Reloading = true
		task.wait(2)
		Ammo = MaxAmmo
		Reloading = false
	elseif Ammo <= MaxAmmo then 
		Reloading = true
		task.wait(2)
		Ammo = MaxAmmo
		Reloading = false
	end
	
end
Reloader.OnServerInvoke = function(plr)
	local ReloadSuccess = false
	print(Ammo)
	Reload()
	task.wait(2)
	local ReloadSuccess = true
	return ReloadSuccess 
end

Now my real issue is; Do I actually have to go through all of this? Or can I just make the Ammo completely client sided. When the player dies as they reload or unequip the weapon, the reload system tends to bug or shoot an error, and due to my inexperience with client-server communication in this form of context, I don’t know what I should do. I don’t want hackers/exploiters to change the ammo count of their weapon and fire infinitely.
Also, I change an ammo count on the client for the AmmoCounter and to make sure the gun also doesn’t fire when the ammo is 0 on the client aswell.

TL; DR: I change ammo on the server to prevent hackers; Is it really necessary?

2 Likes

As someone who knows nothing about shooters, I’m not sure weather people would exploit this or not, but I would probably try to make it hard to exploit just because a shooter game with hackers makes it completely unplayable. You COULD maybe make it so if the ammo is greater than MaxAmmo, ban the player or something like that, but that wouldn’t stop hackers from just resetting the amount of ammo to be equal to max every time. Hope this helps!

1 Like

I’ve had the same question, and I solved it by these steps:

  1. Ammo, Magazine capacity must be only in local script as a variable. DO NOT use number values - they are exploitable on the client. Example:
local MaxAmmo = Config.Ammo
local Ammo = MaxAmmo
local Mag = Config.Mag

function OnFire()
      Mag -= 1
end

  1. Use RemoteEvent to replicate current mag, ammo amount to the server when player ended reloading or fired the weapon.

Pretty say, server is only cosmetic, or just indicator that may used for refilling weapon with certain ammo boxes, etc.

So, you should use local script, and then replicate changes to the server via RemoteEvent.

Good luck :new_moon_face:

1 Like

If you’re concerned about security, my go-to phrase is:

Never trust the client

As such, trusting the client’s ability to keep track of their own ammo will, in the worst case scenario, lead to infinite ammo and being able to infinitely fire without ever needing to reload.
If this security is your concern, do not trust the client to properly process this.

As for how you could implement it, just make a remote event fire whenever the client wants the gun to fire and handle all the maths there.
You can even make it feel smooth on the client by doing all the fancy effects (sound, muzzle flash, hitmarkers etc) on the client anyway, allowing the client to also keep track of their ammo by themselves. That way, if exploiters change their magazine capacity to math.huge, it will just look pretty on their screen and no one will be (wrongfully) harmed :smiley:

2 Likes

True, but not in this situation.

It’s only possible when NumberValue tracks and handles maths with the Ammo / Magazine capacity on the client.

You cannot change anything In-script while game runs on Local / Roblox’s server

Surely impossible to “hack” local script and change the stats.

So in my opinion OP should use client handling and server “cosmetic” replication.

2 Likes

You underestimate the ingenuity of some, but you do raise a good point. Most will be deterred if its in it’s own script, but don’t think that means its “unhackable”. Again, for best security practice, never trust the client.

Still, I wouldn’t want to trust the client with anything if its something you’re super concerned about.
Jacket_Howla’s solution works for ammo, but hackers may find an infinite ammo exploit.
The same design pattern may even work for processing hits, which may have advantages like less ghost hits and unfair deaths after desync. However lag switchers and remote scanners will likely have a field day.

As I’ve said, its a scale of how much you want to trust the client with things. I always assume my players are omniscient exploiters who know the ins and outs of my server code for maximal exploitation, and this way of thinking absolutely helps me make my games way more secure.

There are certain areas you don’t want to let the server handle all the processing for, stuff like player movement which feels 100x smoother if done on the client, but I don’t think a gun system falls into that category.

3 Likes

Thank you for your responses, and I do follow the whole “Never trust the client” rule. That’s actually part of my issue, as I can’t just handle the Ammo on the client which would be 10 times easier, and instead I have to handle ammo on both. Still trying to figure this out too.

1 Like

I did something similar, where when the remote event is fired onto the server script for the gun, a separate ammo value (The real one) is lowered on the server. So even if they can spam fire the gun on the client, nothing happens on the server.

1 Like

Thank you for your input! As much as that would help, I don’t think I’m looking to go that far.

2 Likes

They can just yield the server forever since it has task.wait(2), meaning them invoking the remote function in a loop will partially break the game

1 Like

anything on client can by exploited, any variable value, active connections can be disconnected, etc.
anything that lives on client can be modified

1 Like

Do you think it’s best if I use a remote event to handle ammo? I don’t really know where to go to be honest.

I had some experience with shooters in the past, so I’ll chime in a bit.

“Trust, but verify” is the better guideline here than “don’t trust the client”.

You still have to trust the client to tell you that he fired a shot - so check to see if the shot was legal.
In this case, you check to see if he had enough ammo to make that shot.

Everytime your client fires a shot, the server should:

  • receive the input, and when the input was made and received
  • verifies that the client had ammo
  • fires the shot in the direction and position that the client was facing
  • updates that client’s ammo count server-side
  • and applies damage if the shot connected.

Meanwhile, the client just deducts ammo and creates visuals for itself.

It’s not foolproof though. A client can;

  • spoof the time in which he fired a shot.
  • declare its position and rotation to be elsewhere.

Fortunately, defending against those two points isn’t too difficult:

  • a client can’t possibly fire at a time beyond the server’s time.
  • they also can’t fire too late, or you can write it off as ping.
  • you can also simply do positional sanity checks, or “rewind” to see if the player could possibly reach the position they said they were in.

Also, as others pointed out, don’t use task.wait(), and RemoteFunctions aren’t the best here. Use a RemoteEvent for your clients to communicate that they made a shot.

Also consider using timestamps instead of discrete tick to keep track of time.

1 Like

I use a remote event for firing the weapon, but I use a remote function for reloading considering it goes back to the client. I’m going to attempt to use a remote event for the reload and see how that goes. I would also like some more elaboration on what you mean by your last text where you bring up defending against the two points, and some more detail on the timestamps part as I’m inexperienced with Studios time system.

1 Like

A timestamp is a given “point in time”; discrete ticks refer to mechanisms like task.wait.
It’s the equivalent of saying “you slept at 10 and woke up at 8”, versus “you slept for 10 hours”.

The appeal of using timestamps here is that you can easily verify for a shot’s legitimacy without breaking continuity by yielding a script thread.

Say you have a double-barreled shotgun that takes 2 seconds to reload, and can fire 1 shot per second.

You fire the moment the game starts. The timeline of events would go as such:

  • 00:00: You fire your first shot.
  • 00:01: You fire the last shot and immediately go into reload.
  • 00:03: You finish reloading.

Any other shot you fire between 00:01 and 00:03 will be deemed illegitimate, because you’re halfway through a reload and you have no ammo left.

Any shot you fire between 00:01 and 00:02 is illegitimate too, since you can only fire 1 shot per second.

Timestamps make it very easy to check for exploits like that.

5 Likes

Thank you for the tip. I’ll try to become more familiar with using timestamps.

1 Like

Nothing that locked in client script can be exploited. You cannot edit local scripts in-real time.

1 Like

Yes you can, variables are registered in memory, signals, everything that lives on client can be modified.

You can spoof __namecall and make every remote not send any data regardless of what you put as arguments in :FireServer

Please research before spreading misinformation

1 Like

Not so sure that exploiters able to edit in-script variables (locals), whenever game starts, scripts were compiling and cannot be editable. They can only read script (using Script.Source)

Yes, I know that Instance-Varibles (NumberValue, etc) are exploitable.

1 Like

If you are not so sure then why talk?

I never said you have to edit Script.Srouce, that does nothing, the script has already compiled and everything has been executed to memory on the client.

CheatEngine was the first way to exploit on Roblox and CheatEngine is literally the low level cheating on Roblox, because you directly modify memory addresses and what they tie to (Health, WalkSpeed, etc).

Code below detects workspace:Raycast() call in a specific function in a localscript. and modifies it’s Result

Somewhat similar to this would have been Arsenals wallbang script. Since Arsenal used to accept clients RaycastResult, therefore you can just spoof the function or the remote so the modified raycast with no params is sent to server.

(THIS IS NOT ENCOURAGEMENT TO CHEAT, THIS IS FOR EDUCATIONAL PURPOSES ONLY)

Cheating is literally reading and writing to memory.

1 Like