Best way to secure a datastore from exploiters

I was making a datastore until I realised it could be abused by exploiters. I was wondering how I could protect it from being used. One way was putting
a script inside the button to check if I had enough currency then it would save the data. Another way was a local script would trigger a script sending the value and data it needs to save and the script, using communication, would check if they have the currency and it would save the data after. Although I don’t know if they are the best way to secure a datastore. I want to create a secure datastore so I can finally use it for my shop.

1 Like

give me a video, photo or the code you use to store data in the cloud. I need something else to help you.

You should put all of the data store functions inside of a server script inside of ServerScriptService. You should also put all of the currency, items, and inventory data inside of that script. You should also verify their current amount of owned currency and inventory items in that script. Putting them inside a local script is an inevitable disaster. The button should only send something like a billing request where it asks the server for a particular item. The server checks how much currency that player has, and if they don’t have enough, just don’t give them the item. If they do, add that item inside of the inventory data and subtract their currency with the price of that item. You should also only save data when the player exits the game and at a set time interval so your data store isn’t queued for updates. Here’s a script for example;

local dataRam = {
    ["2737739283"] = { --player id
        ["inventory"] = {["coconut"] = 28, ["fish launcher"] = 3},
        ["currency"] = 72774827
    }
}

Players.PlayerRemoving:Connect(function(player)
    storeData(player, dataRam[player.UserId])
end)
2 Likes

should I save the data when the player leaves or when it is bought.

Save the data when a player leaves, and load it when a player joins.

Additionally, you can have a table for all the players’ data on the server.

So create your table:

local serverdata = {}

Then you can have a subtable for each player on the server, that you can access using the player’s user id:

serverdata[plr.UserId]

So, if you wanted to modify the coins of a player, you would do:

serverdata[plr.UserId].Coins += 100

Now, to save memory, you can delete all of a player’s data from the table whenever that player leaves (after you saved their data of course):

serverdata[plr.UserId] = nil

This is done so that if a server is up for a long time, a lot of values will be contained in that table, most of which are irrelevant since the players that left the server don’t need their data in there anymore, so you save up memory by deleting their information from the table once they leave.

I hope I could help. Also let me know if anything doesn’t work I’m writing this on my phone :sweat_smile:

Here’s a simple example of a data saving system:

data = {}
data.service = game:GetService("DataStoreService")
data.events = game.ReplicatedStorage.Events.Data
data.ram = {}

data.stores = {}
data.stores.PlayerData = {
	store = data.service:GetDataStore("PlayerData"),
	structure = {
		dversion = 1.0,
		statistics = {
			survivals = 0,
			coins = 0,
		},
		inventory = {},
		badgeProgress = {},
		warnings = {},
		maxWarnings = 3
	},
	versionCheck = function(aver:number, bver:number) --returns true if version is in check
		return aver == bver
	end,
}

function data:speakChange(player, vdata)
	data.events.LISTEN:FireClient(player, vdata) --allows clients to listen to their data changes inside of the server's ram
end

function data:getRam(player:Player)
	local userId = player.UserId
	return data.ram[userId], userId
end

function data:setRam(player:Player, vdata)
	local userId = player.UserId
	data.ram[userId] = vdata
	data:speakChange(player, data.ram[userId])
end

function data:load(player:Player, store:{ ["store"]:DataStore, ["structure"]:{["dversion"]:number}, ["versionCheck"]:(number, number) -> ("aver", "bver") })
	local userId = tostring(player.UserId)
	local success, vdata = pcall(function()
		return store.store:GetAsync(userId)
	end)

	if success and vdata then
		if not store.versionCheck(vdata.dversion, store.structure.dversion) then
			vdata = store.structure
		end
	else
		--last resort (not recommended, you should revert to the previous version if possible, this is just an example)
		vdata = store.structure
	end

	return vdata
end

function data:save(player:Player, store:{ ["store"]:DataStore, ["structure"]:{["dversion"]:number}, ["versionCheck"]:(number, number) -> ("aver", "bver") })
	local vdata, userId = data:getRam(player)
	local success, errorMessage = pcall(function()
		store.store:SetAsync(userId, vdata)
	end)

	if not success then
		warn("Failed to save data for player " .. player.Name .. ": " .. errorMessage)
	end
end

function data:save_clearFromRam(player:Player, store)
	data:save(player, store)
	data:setRam(player, nil)
end

function data:load_toRam(player:Player, store)
	local d = data:load(player, store)
	data:setRam(player, d)
	return d
end

function data:modifyRam(player, path, value)
	local paths = string.split(path, ".") or string.split(path, "/") or string.split(path, " ")
	
	local d = data:getRam(player)
	local dx = d
	for i = 1, #paths - 1 do
		dx = dx[paths[i]]
		if not dx then warn("Data: Invalid path: " .. path) return end
	end
	local lastKey = paths[#paths]
	if dx[lastKey] == nil then warn("Data: Invalid path: " .. path) return end
	dx[lastKey] = value
	data:setRam(player, d)
	leaderstats:update(player)
end

data.events.GET.OnServerInvoke = function(player:Player, ...) --not recommended (this is just an example)
	local keys = {...}
	local dx = data:getRam(player)
	for i, v in pairs(keys) do
		if typeof(v) ~= "string" then warn("Data: Type mismatch: "..player.Name) return end
		local dy = dx[v]
		if not dy then
			warn("Data: Invalid key \""..v.."\": "..player.Name)
		else
			dx = dy
		end
	end
	return dx
end

_G.data = data

leaderstats = {} --example leaderstats changes
function leaderstats:setupPlayer(player:Player)
	local folder = Instance.new("Folder")
	folder.Name = "leaderstats"
	folder.Parent = player
	
	for i, v in pairs(data:getRam(player).statistics) do
		local s = Instance.new("IntValue")
		s.Name = i
		s.Value = v
		s.Parent = folder
	end
end
function leaderstats:update(player:Player)
	local statistics = player:FindFirstChild("leaderstats")
	if statistics then
		local d = data:getRam(player).statistics
		for _, v in pairs(statistics:GetChildren()) do
			if not v:IsA("IntValue") then continue end
			v.Value = d[v.Name]
		end
	else
		leaderstats:setupPlayer(player)
	end
end

_G.leaderstats = leaderstats

sorry if I confused you but I mean’t if I bought the item I needed. Would I save it to the data store immediately or do it when the game closes or player leaves or both.

can i ask you what is data ram

just do not trust the client
– krant19worlddv

2 Likes

Don’t save data immediately!
You should save data on PlayerRemoving

How would I know if the player bought the item when saving the data on remove?

i use folders under the player instance to store the player items , clients can not play with these because the server will no see the changes that the client made

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