As a Roblox developer, it is currently too hard to verify if an object can be saved or not.
The engine currently lacks a function to verify if data can be saved and has bad documentation on how to verify the safety of strings, which was my fault for providing improper fixes to the problem. The lack of any proper way to do this causes various issues and bugs that are incredibly difficult to deal with.
Robloxโs documentation is very unclear about what types data stores can save or not. If you were to dig and try saving every type, youโd find that some of Robloxโs data types can be saved while others canโt be saved, and the userdata that does save is just nil when using GetAsync
. The only places youโll find mentions of this are on the GlobalDataStore page in SetAsync and UpdateAsync and the security tactics page. On the GlobalDataStore page the documentation is also wrong claiming โvalues greater than 127 are used exclusively for encoding multi-byte codepoints, so a single byte greater than 127 will not be valid UTF-8 and the GlobalDataStore:SetAsync() attempt will failโ, which is wrong since characters like โรฏโ (195) are over 127 and will save completely fine.
Why is this problematic?
Roblox datastores have limitations on the data that can be stored; many data types cannot be saved; tables with specific indices and strings with invalid unicode will error. If instances were allowed to be saved, that would also not solve the issues, since when a player loads in the data, it may error and instead corrupt the playerโs data. However, in cases where an instance is passed, it can be reliably checked using typeof
. Strings and tables containing strings are much more problematic because you have to manually figure out what in strings can be saved and what should be allowed since checking if it is ascii is not enough or using utf8.len
is not enough and results in various bugs and bypasses.
Certain characters not saving is incredibly problematic when we donโt have a proper way to protect against this. Depending on the game the results an exploiter may get range from economy breaking to bypassing bans. If you were to have a loot box system in your game and an exploiter had a rare crate they could open they would be able to roll back their data constantly until they got the item they wanted, this could be repeated for other cases in games too. Alternatively if you had a system that banned players after a certain amount of infractions, and an exploiter decided to put a string that would prevent their data from being saved theyโd be completely negating a ban they would receive.
Currently, as a developer, you might not even be aware of this, and the documentation Roblox provides explaining how to deal with the issue uses bad advice by only allowing ascii, which was based on my own bad advice I showcased in my github gist. While my github gist helped bring attention to the problem, it never provided proper fixes to verifying if a string is safe or not, utf8.len
being bypassable, and my pattern only allowing ascii characters.
Youโll find that this check using string patterns based on my original [^\0-\127]
pattern seems good and serves as a good check; however, it becomes problematic when certain characters that will save now are rejected, such as รฏ
(naรฏve) and many non-English characters like Chinese characters and Japanese hiragana (pinyin isnโt safe either).
These checks exclude a lot of players that may be playing your game in countries that donโt use English, possibly leaving them confused when certain actions donโt work. The only proper way to verify if a string is safe as a developer is to manually try saving all characters, making a list of all the ones that work and all the ones that donโt, and developing a check based on that.
A list of all data types Roblox can save in data stores [January 19, 2023]
Axes -> errored
BrickColor -> errored
CFrame -> errored
CatalogSearchParams -> errored
Color3 -> errored
ColorSequence -> errored
ColorSequenceKeypoint -> errored
DateTime -> errored
DockWidgetPluginGuiInfo -> errored
Enum -> saved
EnumItem -> errored
Enums -> saved
Faces -> errored
FloatCurveKey -> errored
Font -> errored
Instance -> errored
NumberRange -> errored
NumberSequence -> errored
NumberSequenceKeypoint -> errored
OverlapParams -> saved
PathWaypoint -> errored
PhysicalProperties -> errored
Random -> errored
Ray -> errored
RaycastParams -> errored
Rect -> errored
Region3 -> errored
Region3int16 -> errored
TweenInfo -> errored
RotationCurveKey -> saved
UDim -> errored
UDim2 -> errored
Vector2 -> errored
Vector2int16 -> errored
Vector3 -> errored
Vector3int16 -> errored
userdata -> saved
number -> saved
string -> saved
function -> errored
table -> saved
boolean -> saved
How can this be remedied?
To summarize, Roblox should introduce a way to verify if a value can be saved. A function like DataStoreService:IsSaveable
would work effectively. The function should be able to accept a variable amount of arguments, so multiple values can be checked at once instead of calling individually.
local d = game:GetService("DataStoreService")
local f = function() end
local h = "Hello World!"
local s = "ๅนณ"
local n = "\237\190\140"
print(d:IsSaveable(f)) --> false
print(d:IsSaveable(h)) --> true
print(d:IsSaveable(s)) --> true
print(d:IsSaveable(n)) --> false
print(d:IsSaveable(h, s)) --> true
print(d:IsSaveable(h, n)) --> false
print(d:IsSaveable(f, h, s, n)) --> false
d:IsSaveable() --> error: missing argument
Roblox should bring more attention to this rather than having three paragraphs total explaining it that most developers will likely miss. A minor announcement warning developers of this would be useful to bring attention to this. Iโve talked with many developers of large games who told me they were completely unaware of this, and they themselves found it difficult to prevent these exploits other than just locking all the characters to ascii ones.