One of my devs accidentally made a 1 million multiplier admin abuse event. And I wanted to make automatic rollback/revert for everyone who got the stats multiplied by the 1 million multiplier.
I hope someone can help me solve this problem. Thank you!
if you didnt have a snapshot before the admin abause, you cant
it works using roblox datastore manager feature by comparing it’s version and then revert the version before the player got the stat multiplied by million multiplier. but i wanna know how do i make it automatically?
explain your “automatically”, developers manually revert datastore to a specific snapshot when they know of a dupe glitch, etc
like by a script, upon they joined the server their data will be automatically reverted to previous version
we can’t do this manually, there’s lots of players has gotten it
If you’re trying to make it so you can detect or rollback this kind of thing going forward then you should look at data store Metadata. You can set a dictionary of values with each set or update. This could record the user who made the update and what operation was performed. The meta data is stored with each version so you can see why each version was generated.
I still don’t understand how it works, i really need an clear explanation
Not too sure what the api difference is between profile store and profile service is however I shall assume they are similar; In ProfileService you can query Player’s data versions with their profile key, min date and a max date, for example if you know when roughy the admin abuse occurred, when you load the player’s profile you can do a quick version query and compare the different data store times before the admin abuse and during the admin abuse, if the stats were multiplied by 1,000,000 then you can simply revert it back to the time before the admin abuse started!
local profileKey = `{UserId}_Data`
local maxDate = DateTime.fromUniversalTime(2025,12,02)
local minDate = DateTime.fromUniversalTime(2025,12,01) -- you can also specify hour and mins
local versionQuery = ProfileStore:ProfileVersionQuery(profileKey, Enum.SortDirection.Descending,nil,maxDate)
-- do other version query (might need to do versionQuery:NextAsync())
-- do comparisons here
-- then do this below
profile:ClearGlobalUpdates()
profile:OverwriteAsync()
--and then finally
Players:GetPlayerByUserId(UserId):Kick("Data Roll Back Success, Please Rejoin")
I assume profile store is very similar.
here is the profile service documentation:
Assuming the use of the multiplier is obvious then it should be easy to roll it back when you first load the value. After fetching the value check to see if it is greater than one million. If it is adjust it back to the proper value and store the new result.
local success,result = pcall(function()
local raw,keyinfo = datastore:GetAsync(key)
assert(typeof(raw) == "table", "Value must be table")
if raw.stat > 1000000 then
raw.stat /= 1000000
datastore:SetAsync(key, raw)
end
return true,raw
end)
If a set or update is going to happen automatically then you might not even need to store the value here since it will be handled when the player removes.
The versioning idea is to add metadata each time you add or update the data. This could help you keep track of why the value changed over time. But since a normal game update might have happened since the admin action it could be tricky to use it to back out of an admin change.
local options = Instance.new("DataStoreOptions")
options:SetMetaData({action="Action001"})
datastore:SetAsync(key, value, options)
Then you can fetch the metadata to see the last action that was performed on it.
local raw, keyinfo = datastore:GetAsync(key)
local meta = keyinfo:GetMetadata()
local action = meta.action -- Action001
hello there, thank you for helping. but there’s a problem
and this is what i did:
local minDate = DateTime.fromUniversalTime(2025,12,01, 18, 54)
local maxDate = DateTime.fromUniversalTime(2025,12,02, 19, 57)
local versionQuery = PlayerStore:VersionQuery(tostring(player.UserId), Enum.SortDirection.Descending, minDate, maxDate)
print("ver query")
print(versionQuery)
local versionInfo = versionQuery:NextAsync()
print("ver info")
print(versionInfo)
if not versionInfo then
warn("No rollback version found for user:", player.Name)
return
end
print("Rolling back to version:", versionInfo.Version)
i found a way how to do it, but i need to make it check once. how do i compare it?
local max_date = DateTime.fromUniversalTime(2025, 12, 2, 19, 50)
local query = PlayerStore:VersionQuery(
tostring(player.UserId), -- The same profile key that gets passed to :LoadProfileAsync()
Enum.SortDirection.Descending,
nil,
max_date
)
-- Get the first result in the query:
local found = query:NextAsync()
if found ~= nil then
found:SetAsync()
task.wait(1)
player:Kick("Data Roll Back Success, Please Rejoin!")
else
print(`No version to rollback to`)
end
Thank you for whoever trying helping me, i found a way to solve it. Thank you so much guys
local max_date = DateTime.fromUniversalTime(2025, 12, 2, 9)
local query = PlayerStore:VersionQuery(
tostring(player.UserId), -- The same profile key that gets passed to :LoadProfileAsync()
Enum.SortDirection.Descending,
nil,
max_date
)
-- Get the first result in the query:
local found = query:NextAsync()
if found ~= nil then
if not profile.Data.HasRolledBack then
found.Data.HasRolledBack = true
found:SetAsync()
print(found.Data)
task.wait(5)
player:Kick("Data Roll Back Success, Please Rejoin!")
else
print("No data to rollback.")
end
else
print(`No version to rollback to`)
end
no worries, glad I could help !
This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.
