Files
UniversalSynSaveInstance/saveinstance.luau
2024-06-03 16:57:35 +02:00

2276 lines
78 KiB
Lua

--!native
-- https://discord.gg/wx4ThpAsmw
local function Find(String, Pattern)
return string.find(String, Pattern, nil, true)
end
local service = setmetatable({}, {
__index = function(self, Name)
local Service = game:GetService(Name) or settings():GetService(Name) or UserSettings():GetService(Name)
self[Name] = Service
return Service
end,
})
local EscapesPattern = "[&<>\"'\1-\9\11-\12\14-\31\127-\255]" -- * The safe way is to escape all five characters in text. However, the three characters " ' and > needn't be escaped in text
-- %z (\0 aka NULL) might not be needed as Roblox automatically converts it to space everywhere it seems like
-- Characters from: https://create.roblox.com/docs/en-us/ui/rich-text#escape-forms
-- TODO: EscapesPattern should be ordered from most common to least common characters for sake of speed
-- TODO: Might wanna use their numerical codes instead of named codes for reduced file size (Could be an Option)
-- TODO Maybe we should invert the pattern to only allow certain characters (future-proof)
local Escapes = {
["&"] = "&amp;", -- 38
["<"] = "&lt;", -- 60
[">"] = "&gt;", -- 62
['"'] = "&#34;", -- quot
["'"] = "&#39;", -- apos
}
for rangeStart, rangeEnd in string.gmatch(EscapesPattern, "(.)%-(.)") do
for charCode = string.byte(rangeStart), string.byte(rangeEnd) do
Escapes[string.char(charCode)] = "&#" .. charCode .. ";"
end
end
local globalcontainer
do
local Params = {
RepoURL = "https://raw.githubusercontent.com/luau/SomeHub/main/",
UMF = "UniversalMethodFinder",
}
local finder
finder, globalcontainer = loadstring(game:HttpGet(Params.RepoURL .. Params.UMF .. ".luau", true), Params.UMF)()
finder({
-- readbinarystring = 'string.find(...,"bin",nil,true)', -- ! Could match some unwanted stuff
base64encode = 'local a={...}local b=a[1]local function c(a,b)return string.find(a,b,nil,true)end;return c(b,"encode")and(c(b,"base64")or c(string.lower(tostring(a[2])),"base64"))',
decompile = '(string.find(...,"decomp",nil,true) and string.sub(...,#...) ~= "s") or string.find(...,"assembl",nil,true)',
gethiddenproperties = 'string.find(...,"get",nil,true) and string.find(...,"h",nil,true) and string.find(...,"prop",nil,true) and string.sub(...,#...) == "s"',
gethiddenproperty = 'string.find(...,"get",nil,true) and string.find(...,"h",nil,true) and string.find(...,"prop",nil,true) and string.sub(...,#...) ~= "s"',
gethui = 'string.find(...,"get",nil,true) and string.find(...,"h",nil,true) and string.find(...,"ui",nil,true)',
getnilinstances = 'string.find(...,"nil",nil,true)', -- ! Could match some unwanted stuff
getproperties = 'string.find(...,"get",nil,true) and string.find(...,"prop",nil,true) and string.sub(...,#...) == "s"',
getscriptbytecode = 'string.find(...,"get",nil,true) and string.find(...,"bytecode",nil,true) or string.find(...,"dump",nil,true) and string.find(...,"string",nil,true)',
getspecialinfo = 'string.find(...,"get",nil,true) and string.find(...,"spec",nil,true)',
hash = 'local a={...}local b=a[1]local function c(a,b)return string.find(a,b,nil,true)end;return c(b,"hash")and c(string.lower(tostring(a[2])),"crypt")',
protectgui = 'string.find(...,"protect",nil,true) and string.find(...,"ui",nil,true) and not string.find(...,"un",nil,true)',
-- request = 'string.find(...,"request",nil,true) and not string.find(...,"internal",nil,true)',
sethiddenproperty = 'string.find(...,"set",nil,true) and string.find(...,"h",nil,true) and string.find(...,"prop",nil,true) and string.sub(...,#...) ~= "s"',
writefile = 'string.find(...,"file",nil,true) and string.find(...,"write",nil,true)',
-- appendfile = 'string.find(...,"file",nil,true) and string.find(...,"append",nil,true)',
}, true, 10)
end
local gethiddenproperty = globalcontainer.gethiddenproperty
local sethiddenproperty = globalcontainer.sethiddenproperty
local writefile = globalcontainer.writefile
-- local appendfile = globalcontainer.appendfile
local getscriptbytecode = globalcontainer.getscriptbytecode
local base64encode = globalcontainer.base64encode
local hash = globalcontainer.hash
local sha384
if not globalcontainer.getspecialinfo then
globalcontainer.getspecialinfo = globalcontainer.gethiddenproperties
end
if not base64encode then
if not bit32.byteswap or not pcall(bit32.byteswap, 1) then -- Because Fluxus is missing byteswap
bit32 = table.clone(bit32)
local function tobit(num)
num %= (bit32.bxor(num, 32))
if 0x80000000 < num then
num -= bit32.bxor(num, 32)
end
return num
end
bit32.byteswap = function(num)
local BYTE_SIZE = 8
local MAX_BYTE_VALUE = 255
num %= bit32.bxor(2, 32)
local a = bit32.band(num, MAX_BYTE_VALUE)
num = bit32.rshift(num, BYTE_SIZE)
local b = bit32.band(num, MAX_BYTE_VALUE)
num = bit32.rshift(num, BYTE_SIZE)
local c = bit32.band(num, MAX_BYTE_VALUE)
num = bit32.rshift(num, BYTE_SIZE)
local d = bit32.band(num, MAX_BYTE_VALUE)
num = tobit(bit32.lshift(bit32.lshift(bit32.lshift(a, BYTE_SIZE) + b, BYTE_SIZE) + c, BYTE_SIZE) + d)
return num
end
table.freeze(bit32)
end
-- Credits @Reselim
local Base64_Encode_Buffer = loadstring(
game:HttpGet("https://raw.githubusercontent.com/Reselim/Base64/master/Base64.lua", true),
"Base64"
)().encode
base64encode = function(raw)
return raw == "" and raw or buffer.tostring(Base64_Encode_Buffer(buffer.fromstring(raw)))
end
end
if hash then
sha384 = function(data)
return hash(data, "sha384")
end
else
local FileName = "RequireOnlineModule"
sha384 = loadstring(
game:HttpGet("https://raw.githubusercontent.com/luau/SomeHub/main/" .. FileName .. ".luau", true),
FileName
)()(4544052033).sha384
end
local custom_decompiler, load_decompiler -- TODO Temporary
if getscriptbytecode then
-- Credits @w-a-e
local decompiler_source
if
pcall(function()
decompiler_source =
game:HttpGet("https://raw.githubusercontent.com/w-a-e/Advanced-Decompiler-V3/main/init.lua", true)
end)
then
load_decompiler = function(decomptimeout)
local CONSTANTS = [[
local ENABLED_REMARKS = {
NATIVE_REMARK = true,
INLINE_REMARK = true
}
local DECOMPILER_TIMEOUT = ]] .. decomptimeout .. [[
local READER_FLOAT_PRECISION = 7 -- up to 99
local SHOW_INSTRUCTION_LINES = false
local SHOW_REFERENCES = true
local SHOW_OPERATION_NAMES = false
local SHOW_MISC_OPERATIONS = false
local LIST_USED_GLOBALS = true
local RETURN_ELAPSED_TIME = false
]]
local _ENV = (getgenv or getrenv or getfenv)()
local old = _ENV.decompile
local f = loadstring(
string.gsub(
string.gsub(
decompiler_source,
"return %(x %% 2^32%) // %(2^disp%)",
"return math.floor((x %% 2^32) / (2^disp))",
1
), -- TODO Temporary fix for macsploit (// operator)
";;CONSTANTS HERE;;",
CONSTANTS
),
"Advanced-Decompiler-V3"
)
if not f then
return
end
f()
custom_decompiler = _ENV.decompile
_ENV.decompile = old
end
end
end
local SharedStrings = {}
local sharedstrings = setmetatable({
identifier = 1e15, -- 1 quadrillion, up to 9.(9) quadrillion, in theory this shouldn't ever run out and be enough for all sharedstrings ever imaginable
-- TODO: worst case, add fallback to str randomizer once numbers run out : )
}, {
__index = function(self, String)
local Identifier = base64encode(tostring(self.identifier)) -- tostring is only needed for built-in base64encode, Reselim's doesn't need it as buffers autoconvert
self.identifier += 1
self[String] = Identifier -- Todo: The value of the md5 attribute is a Base64-encoded key. <SharedString> type elements use this key to refer to the value of the string. The value is the text content, which is Base64-encoded. Historically, the key was the MD5 hash of the string value. However, this is not required; the key can be any value that will uniquely identify the shared string. Roblox currently uses BLAKE2b truncated to 16 bytes..
return Identifier
end,
})
local Descriptors
Descriptors = {
__APIPRECISION = function(raw, default)
if raw == 0 or raw % 1 == 0 then
return raw
end
local Extreme = Descriptors.__EXTREME(raw)
if Extreme then
return Extreme
end
local precision
if type(default) == "string" then -- TODO: This part isn't too necessary at all and affects speed
local dotIndex = Find(default, ".")
if dotIndex then
precision = #default - dotIndex
end
else
precision = default
end
if precision then
-- TODO: scientific notation formatting also takes place if value is a decimal (only counts if it starts with 0.) then values like 0.00008 will be formatted as 8.0000000000000006544e-05 ("%.19e"), it must have 5 or more consecutive (?) zeros for this, on other hand, if it doesn't start with 0. then e20+ format is applied in case it has 20 or more consecutive (?) zeros so 1e20 will be formatted as 1e+20 and upwards (1e+19 is not allowed, same as 1e-04 for decimals)
-- ? The good part is compression of value so less file size BUT at the potential cost of precision loss
return string.format("%." .. precision .. "f", raw)
end
return raw
end,
__BINARYSTRING = base64encode,
__BIT = function(...) -- * Credits to Friend (you know yourself)
local Value = 0
for Index, Bit in { ... } do
if Bit then
Value += 2 ^ (Index - 1)
end
end
return Value
end,
__CDATA = function(raw) -- ? Normally Roblox doesn't use CDATA unless the string has newline characters (\n); We rather CDATA everything for sake of speed
return "<![CDATA[" .. raw .. "]]>"
end,
__ENUM = function(raw)
return raw.Value, "token"
end,
__ENUMNAME = function(raw)
return raw.Name
end,
__EXTREME = function(raw)
local Extreme
if raw ~= raw then
Extreme = "NAN"
elseif raw == math.huge then
Extreme = "INF"
elseif raw == -math.huge then
Extreme = "-INF"
end
return Extreme
end,
__EXTREME_RANGE = function(raw)
return raw ~= raw and "0" or raw -- Normally we should return "-nan(ind)" instead of "0" but this adds more compatibility
end,
__PROTECTEDSTRING = function(raw) -- ? its purpose is to "protect" data from being treated as ordinary character data during processing;
return Find(raw, "]]>") and Descriptors.string(raw, true) or Descriptors.__CDATA(raw)
end,
__SEQUENCE = function(raw, ValueFormat) --The value is the text content, formatted as a space-separated list of floating point numbers.
-- tostring(raw) also works (but way slower rn)
local __EXTREME_RANGE = Descriptors.__EXTREME_RANGE
local Converted = ""
for _, Keypoint in raw.Keypoints do
Converted ..= Keypoint.Time .. " " .. (ValueFormat and ValueFormat(Keypoint) or __EXTREME_RANGE(
Keypoint.Value
) .. " " .. __EXTREME_RANGE(Keypoint.Envelope) .. " ")
end
return Converted
end,
__VECTOR = function(X, Y, Z) -- Each element is a <float>
local Value = "<X>" .. X .. "</X><Y>" .. Y .. "</Y>" -- There is no Vector without at least two Coordinates.. (Vector1, at least on Roblox)
if Z then
Value ..= "<Z>" .. Z .. "</Z>"
end
return Value
end,
--------------------------------------------------------------
--------------------------------------------------------------
--------------------------------------------------------------
Axes = function(raw)
--The text of this element is formatted as an integer between 0 and 7
return "<axes>" .. Descriptors.__BIT(raw.X, raw.Y, raw.Z) .. "</axes>"
end,
-- BinaryString = function(raw)
-- end
BrickColor = function(raw)
return raw.Number -- * Roblox encodes the tags as "int", but this is not required for Roblox to properly decode the type. For better compatibility, it is preferred that third-party implementations encode and decode "BrickColor" tags instead. Could also use "int" or "Color3uint8"
end,
CFrame = function(raw)
local X, Y, Z, R00, R01, R02, R10, R11, R12, R20, R21, R22 = raw:GetComponents()
return Descriptors.__VECTOR(X, Y, Z)
.. "<R00>"
.. R00
.. "</R00><R01>"
.. R01
.. "</R01><R02>"
.. R02
.. "</R02><R10>"
.. R10
.. "</R10><R11>"
.. R11
.. "</R11><R12>"
.. R12
.. "</R12><R20>"
.. R20
.. "</R20><R21>"
.. R21
.. "</R21><R22>"
.. R22
.. "</R22>",
"CoordinateFrame"
end,
Color3 = function(raw) -- Each element is a <float>
return "<R>" .. raw.R .. "</R><G>" .. raw.G .. "</G><B>" .. raw.B .. "</B>" -- ? It is recommended that Color3 is encoded with elements instead of text.
end,
Color3uint8 = function(raw)
-- https://github.com/rojo-rbx/rbx-dom/blob/master/docs/xml.md#color3uint8
return 0xFF000000
+ (math.floor(raw.R * 255) * 0x10000)
+ (math.floor(raw.G * 255) * 0x100)
+ math.floor(raw.B * 255) -- ? It is recommended that Color3uint8 is encoded with text instead of elements.
-- return bit32.bor(
-- bit32.bor(bit32.bor(bit32.lshift(0xFF, 24), bit32.lshift(0xFF * raw.R, 16)), bit32.lshift(0xFF * raw.G, 8)),
-- 0xFF * raw.B
-- )
-- return tonumber(string.format("0xFF%02X%02X%02X",raw.R*255,raw.G*255,raw.B*255))
end,
ColorSequence = function(raw)
--The value is the text content, formatted as a space-separated list of FLOATing point numbers.
return Descriptors.__SEQUENCE(raw, function(Keypoint)
local __EXTREME_RANGE = Descriptors.__EXTREME_RANGE
local Value = Keypoint.Value
return __EXTREME_RANGE(Value.R)
.. " "
.. __EXTREME_RANGE(Value.G)
.. " "
.. __EXTREME_RANGE(Value.B)
.. " 0 "
end)
end,
Content = function(raw)
return raw == "" and "<null></null>" or "<url>" .. Descriptors.string(raw, true) .. "</url>"
end,
CoordinateFrame = function(raw)
return "<CFrame>" .. Descriptors.CFrame(raw) .. "</CFrame>"
end,
-- DateTime = function(raw) end,-- TODO
Faces = function(raw)
-- The text of this element is formatted as an integer between 0 and 63
return "<faces>"
.. Descriptors.__BIT(raw.Right, raw.Top, raw.Back, raw.Left, raw.Bottom, raw.Front)
.. "</faces>"
end,
Font = function(raw)
return "<Family>"
.. Descriptors.Content(raw.Family)
.. "</Family><Weight>"
.. Descriptors.__ENUM(raw.Weight)
.. "</Weight><Style>"
.. Descriptors.__ENUMNAME(raw.Style) -- Weird but this field accepts .Name of enum instead..
.. "</Style>" --TODO (OPTIONAL ELEMENT): Figure out how to determine (Content) <CachedFaceId><url>rbxasset://fonts/GothamSSm-Medium.otf</url></CachedFaceId>
end,
NumberRange = function(raw) -- tostring(raw) also works
--The value is the text content, formatted as a space-separated list of floating point numbers.
local __EXTREME_RANGE = Descriptors.__EXTREME_RANGE
return __EXTREME_RANGE(raw.Min) .. " " .. __EXTREME_RANGE(raw.Max) --[[.. " "]] -- ! This might be required to bypass detections as thats how its formatted usually; __EXTREME_RANGE is not needed here but it fixes the issue where "nan 10" value would reset to "0 0"
end,
-- NumberSequence = Descriptors.__SEQUENCE,
PhysicalProperties = function(raw)
--[[Contains at least one CustomPhysics element, which is interpreted according to the bool type. If this value is true, then the tag also contains an element for each component of the PhysicalProperties:
Density
Friction
Elasticity
FrictionWeight
ElasticityWeight
The value of each component is represented by the text content formatted as a 32-bit floating point number (see float).]]
local CustomPhysics
if raw then
CustomPhysics = true
else
CustomPhysics = false
end
CustomPhysics = "<CustomPhysics>" .. Descriptors.bool(CustomPhysics) .. "</CustomPhysics>"
return raw
and CustomPhysics .. "<Density>" .. raw.Density .. "</Density><Friction>" .. raw.Friction .. "</Friction><Elasticity>" .. raw.Elasticity .. "</Elasticity><FrictionWeight>" .. raw.FrictionWeight .. "</FrictionWeight><ElasticityWeight>" .. raw.ElasticityWeight .. "</ElasticityWeight>"
or CustomPhysics
end,
-- ProtectedString = function(raw)
-- return tostring(raw), "ProtectedString"
-- end,
Ray = function(raw)
local vector3 = Descriptors.Vector3
return "<origin>" .. vector3(raw.Origin) .. "</origin><direction>" .. vector3(raw.Direction) .. "</direction>"
end,
Rect = function(raw)
local vector2 = Descriptors.Vector2
return "<min>" .. vector2(raw.Min) .. "</min><max>" .. vector2(raw.Max) .. "</max>", "Rect2D"
end,
-- Region3 = function(raw) --? Not sure yet
-- local vector3 = Descriptors.Vector3
-- local Position = raw.CFrame.Position
-- local Size = raw.Size
-- return "<min>"
-- .. vector3(Position - (Size * 0.5))
-- .. "</min><max>"
-- .. vector3(Position + (Size * 0.5))
-- .. "</max>"
-- end,
-- Region3int16 = function(raw) --? Not sure yet
-- local vector3int16 = Descriptors.Vector3int16
-- return "<min>" .. vector3int16(raw.Min) .. "</min><max>" .. vector3int16(raw.Max) .. "</max>"
-- end,
SharedString = function(raw)
raw = base64encode(raw)
local Identifier = sharedstrings[raw]
if SharedStrings[Identifier] == nil then
SharedStrings[Identifier] = raw
end
return Identifier
end,
UDim = function(raw)
--[[
S: Represents the Scale component. Interpreted as a <float>.
O: Represents the Offset component. Interpreted as an <int>.
]]
return "<S>" .. raw.Scale .. "</S><O>" .. raw.Offset .. "</O>"
end,
UDim2 = function(raw)
--[[
XS: Represents the X.Scale component. Interpreted as a <float>.
XO: Represents the X.Offset component. Interpreted as an <int>.
YS: Represents the Y.Scale component. Interpreted as a <float>.
YO: Represents the Y.Offset component. Interpreted as an <int>.
]]
local X, Y = raw.X, raw.Y
return "<XS>"
.. X.Scale
.. "</XS><XO>"
.. X.Offset
.. "</XO><YS>"
.. Y.Scale
.. "</YS><YO>"
.. Y.Offset
.. "</YO>"
end,
-- UniqueId = function(raw)
--[[
UniqueId properties might be random everytime Studio saves a place file
and don't have a use right now outside of packages, which SSI doesn't
account for anyway. They generate diff noise, so we shouldn't serialize
them until we have to.
]]
-- https://github.com/MaximumADHD/Roblox-Client-Tracker/blob/roblox/LuaPackages/Packages/_Index/ApolloClientTesting/ApolloClientTesting/utilities/common/makeUniqueId.lua#L62
-- return "" -- ? No idea if this even needs a Descriptor
-- end,
Vector2 = function(raw)
--[[
X: Represents the X component. Interpreted as a <float>.
Y: Represents the Y component. Interpreted as a <float>.
]]
return Descriptors.__VECTOR(raw.X, raw.Y)
end,
-- Vector2int16 = Descriptors.Vector2, -- except as <int>
Vector3 = function(raw)
--[[
X: Represents the X component. Interpreted as a <float>.
Y: Represents the Y component. Interpreted as a <float>.
Z: Represents the Z component. Interpreted as a <float>.
]]
return Descriptors.__VECTOR(raw.X, raw.Y, raw.Z)
end,
-- Vector3int16 = Descriptors.Vector3, -- except as <int>\
bool = tostring,
double = function(raw, default) -- Float64
return Descriptors.__APIPRECISION(raw, default or 17) --? A precision of at least 17 is required to properly represent a 64-bit floating point value, so this amount is recommended.
end, -- ? wouldn't float be better as an optimization
float = function(raw, default) -- Float32
return Descriptors.__APIPRECISION(raw, default or 9) -- ? A precision of at least 9 is required to properly represent a 32-bit floating point value, so this amount is recommended.
end,
int = function(raw) -- Int32
return Descriptors.__EXTREME(raw) or raw
end,
string = function(raw, skipcheck)
return not skipcheck and raw == "" and raw or string.gsub(raw, EscapesPattern, Escapes)
end,
}
for DescriptorName, RedirectName in
{
NumberSequence = "__SEQUENCE",
Vector2int16 = "Vector2",
Vector3int16 = "Vector3",
int64 = "int", -- Int64 (long)
}
do
Descriptors[DescriptorName] = Descriptors[RedirectName]
end
for _, originalfuncname in { "getproperties", "getspecialinfo" } do -- * Some executors only allow certain Classes for this method (like UnionOperation, MeshPart, Terrain), for example Electron, Codex
local originalfunc = globalcontainer[originalfuncname]
if originalfunc then
globalcontainer[originalfuncname] = function(instance)
local ok, result = pcall(originalfunc, instance)
return ok and result or {}
end
end
end
local getproperties = globalcontainer.getproperties
if getproperties then
if globalcontainer.getspecialinfo then
local old_getspecialinfo = globalcontainer.getspecialinfo
globalcontainer.getspecialinfo = function(instance)
local specialinfo = getproperties(instance)
for Property, Value in old_getspecialinfo(instance) do
specialinfo[Property] = Value
end
return specialinfo
end
else
globalcontainer.getspecialinfo = getproperties
end
end
local getspecialinfo = globalcontainer.getspecialinfo
local function ArrayToDictionary(Table, HybridMode, ValueOverride)
local tmp = {}
if HybridMode == "adjust" then
for Some1, Some2 in Table do
if type(Some1) == "number" then
tmp[Some2] = ValueOverride or true
elseif type(Some2) == "table" then
tmp[Some1] = ArrayToDictionary(Some2, "adjust") -- Some1 is Class, Some2 is Name
else
tmp[Some1] = Some2
end
end
else
for _, Key in Table do
if type(Key) == "string" then
tmp[Key] = true
end
end
end
return tmp
end
local ClassList
do
local function FetchAPI()
local API_Dump_Url =
"https://raw.githubusercontent.com/MaximumADHD/Roblox-Client-Tracker/roblox/Mini-API-Dump.json"
local API_Dump = game:HttpGet(API_Dump_Url, true)
local classList = {}
local function ReturnPropertyInfo(Member, PropertyName)
local MemberTags = Member.Tags
local Special
if MemberTags then
MemberTags = ArrayToDictionary(MemberTags)
Special = MemberTags.NotScriptable
end
local ValueType = Member.ValueType
local Property = {
Name = PropertyName,
Category = ValueType.Category,
Default = Member.Default,
-- Tags = MemberTags,
ValueType = ValueType.Name,
}
if Special then
Property.Special = true
end
return Property
end
for _, API_Class in service.HttpService:JSONDecode(API_Dump).Classes do
local Class = {}
local ClassName = API_Class.Name
local ClassTags = API_Class.Tags
if ClassTags then
ClassTags = ArrayToDictionary(ClassTags)
end
Class.Tags = ClassTags -- or {}
Class.Superclass = API_Class.Superclass
local ClassProperties = {}
-- ? Check 96ea8b2a755e55a78aedb55a7de7e83980e11077 commit - If a NotScriptableFix is needed that relies on another NotScriptable Property (which doesn't really make sense in the first place)
for _, Member in API_Class.Members do
if Member.MemberType == "Property" then
local PropertyName = Member.Name
local Serialization = Member.Serialization
if Serialization.CanSave and Serialization.CanLoad then -- If Roblox doesn't save it why should we; If Roblox doesn't load it we don't need to save it
--[[ -- ! CanSave replaces "Tags.Deprecated" check because there are some old properties which are deprecated yet have CanSave.
Example: Humanoid.Health is CanSave false due to Humanoid.Health_XML being CanSave true (obsolete properties basically) - in this case both of them will Load. (aka PropertyPatches)
CanSave being on same level as CanLoad also fixes potential issues with overlapping properties like Color, Color3 & Color3uint8 of BasePart, out of which only Color3uint8 should save
This also fixes everything in IgnoreClassProperties automatically without need to hardcode :)
A very simple fix for many problems that saveinstance scripts encounter!
--]]
ClassProperties[PropertyName] = ReturnPropertyInfo(Member, PropertyName)
end
end
end
Class.Properties = ClassProperties
classList[ClassName] = Class
end
-- classList.Instance.Properties.Parent = nil -- ? Not sure if this is a better option than filtering through properties to remove this
return classList
end
local ok, result = pcall(FetchAPI)
if ok then
ClassList = result
else
warn(result)
return
end
end
local inheritedproperties = setmetatable({}, {
__index = function(self, ClassName)
local proplist = {}
local layer = ClassList[ClassName]
while layer do
-- table.move(_list_0, 1, #_list_0, #proplist + 1, proplist)
for _, p in layer.Properties do
table.insert(proplist, table.clone(p))
end
layer = ClassList[layer.Superclass]
end
self[ClassName] = proplist
return proplist
end,
})
local classreplicas = setmetatable({}, {
__index = function(self, ClassName)
local Replica = Instance.new(ClassName) -- ! Might need to pcall this
self[ClassName] = Replica
return Replica
end,
})
local referents = setmetatable({
identifier = 0,
}, {
__index = function(self, instance)
local referent = self.identifier -- Todo: Roblox encodes all <Item> elements with a referent attribute. Each value is generated by starting with the prefix RBX, followed by a UUID version 4, with - characters removed, and all characters converted to uppercase. We probably need to do that too for sake of safety
self.identifier = referent + 1 -- Is faster than self.identifier += 1 for some reason
self[instance] = referent
return referent
end,
})
local globalenv = getgenv and getgenv() or _G or shared
local function CreateStatusText()
do
local Exists = globalenv.StatusText
if Exists then
globalenv.StatusText = nil
Exists:Destroy()
end
end
local StatusText
local StatusGui = Instance.new("ScreenGui")
globalenv.StatusText = StatusGui
StatusGui.DisplayOrder = 2_000_000_000
pcall(function() -- Compat with level 2
StatusGui.OnTopOfCoreBlur = true
end)
StatusText = Instance.new("TextLabel")
StatusText.Text = "Saving..."
StatusText.BackgroundTransparency = 1
StatusText.Font = Enum.Font.Code
StatusText.AnchorPoint = Vector2.new(1)
StatusText.Position = UDim2.new(1)
StatusText.Size = UDim2.new(0.3, 0, 0, 20)
StatusText.TextColor3 = Color3.new(1, 1, 1)
StatusText.TextScaled = true
StatusText.TextStrokeTransparency = 0.7
StatusText.TextXAlignment = Enum.TextXAlignment.Right
StatusText.TextYAlignment = Enum.TextYAlignment.Top
StatusText.Parent = StatusGui
local function randomString()
local length = math.random(10, 20)
local randomarray = table.create(length)
for i = 1, length do
randomarray[i] = string.char(math.random(32, 126))
end
return table.concat(randomarray)
end
if globalcontainer.gethui then
StatusGui.Name = randomString()
StatusGui.Parent = globalcontainer.gethui()
elseif globalcontainer.protectgui then
StatusGui.Name = randomString()
globalcontainer.protectgui(StatusGui)
StatusGui.Parent = service.CoreGui
else
local RobloxGui = service.CoreGui:FindFirstChild("RobloxGui")
if RobloxGui then
StatusGui.Parent = RobloxGui
else
StatusGui.Name = randomString()
StatusGui.Parent = service.CoreGui
end
end
-- end
return StatusText
end
--[=[
@class SynSaveInstance
Represents the options for saving instances with custom settings using the synsaveinstance function.
]=]
--- @interface CustomOptions table
--- * Structure of the main CustomOptions table.
--- * Note: Aliases take priority over parent option name.
--- @within SynSaveInstance
--- @field __DEBUG_MODE boolean -- Recommended to enable if you wish to help us improve our products and find bugs / issues with it! ___Default:___ false
--- @field ReadMe boolean --___Default:___ true
--- @field SafeMode boolean -- Kicks you before Saving, which prevents you from being detected in certain games. ___Default:___ true
--- @field ShowStatus boolean -- ___Default:___ true
--- @field mode string -- Change this to invalid mode like "custom" if you only want ExtraInstances. "optimized" mode is **NOT** supported with *@Object* option. ___Default:___ `"optimized"`
--- @field noscripts boolean -- ___Aliases:___ `Decompile`. ___Default:___ false
--- @field scriptcache boolean -- ___Default:___ true
--- @field decomptype string -- * "custom" - for built-in custom decompiler. ___Default:___ ""
--- @field timeout number -- If the decompilation run time exceeds this value it gets cancelled. Set to -1 to disable timeout (unreliable). ***Aliases***: `DecompileTimeout`. ___Default:___ 10
--- @field DecompileJobless boolean -- Includes already decompiled code in the output. No new scripts are decompiled. ___Default:___ false
--- @field SaveBytecodeIfDecompilerFails boolean -- Includes bytecode in the output if the decompiler fails. Useful if you wish to be able to decompile it yourself later. ___Default:___ false
--- .DecompileIgnore {Instance | Instance.ClassName | [Instance.ClassName] = {Instance.Name}} -- * Ignores match & it's descendants. Examples: "Chat", - Matches any instance with "Chat" ClassName, Players = {"MyPlayerName"} - Matches "Players" Class AND "MyPlayerName" Name ONLY, `workspace` - matches Instance by reference. ___Default:___ {Chat, TextChatService}
--- .IgnoreList {Instance | Instance.ClassName | [Instance.ClassName] = {Instance.Name}} -- Structure is similar to **@DecompileIgnore**. ___Default:___ {CoreGui, CorePackages}
--- .ExtraInstances {Instance} -- If used with any invalid mode (like "invalidmode") it will only save these instances. ___Default:___ {}
--- @field IgnoreProperties table -- Ignores properties by Name. ___Default:___ {}
--- @field SaveCacheInterval number -- The less the value the more often it saves, but that would mean less performance due to constantly saving. ___Default:___ 0x1600 * 2
--- @field FilePath string -- Must only contain the name of the file, no file extension. ___Default:___ false
--- @field Object string -- * If provided, saves as .rbxmx (Model file) instead. If Object is game, it will be saved as a .rbxl file. **MUST BE AN INSTANCE REFERENCE, FOR EXAMPLE - *game.Workspace***. `"optimized"` mode is **NOT** supported with this option. If IsModel is set to false then Object specified here will be saved as a place file. ___Default:___ false
--- @field IsModel boolean -- If Object is specified then sets to true automatically, unless you set it to false. ___Default:___ false
--- @field NilInstances boolean -- Save nil instances. ___Default:___ false
--- .NilInstancesFixes {[Instance.ClassName] = function} -- * This can cause some Classes to be fixed even though they might not need the fix (better be safe than sorry though). For example, Bones inherit from Attachment if we dont define them in the NilInstancesFixes then this will catch them anyways. **TO AVOID THIS BEHAVIOR USE THIS EXAMPLE:** {ClassName_That_Doesnt_Need_Fix = false}. ___Default:___ {Animator = function, AdPortal = function, BaseWrap = function, Attachment = function}
--- .NotScriptableFixes {[Instance.ClassName] = {<string>PropertyToFix = <string>PropertyFix, _Inheritors = {[Instance.ClassName] = {<string>PropertyToFix_Name = <string>PropertyFix_Name}}}} -- * Structure is similar to **@NilInstancesFixes**. This is useful for execs that lack gethiddenproperty. ___Default:___ *too much to list*
--- .IgnoreDefaults {[string] = boolean} -- Related to API Dump & IgnoreDefaultProperties. ___Default:___ {"__api_dump_class_not_creatable__", "__api_dump_no_string_value__", "__api_dump_skipped_class__",}
--- @field IgnoreDefaultProperties boolean -- Ignores default properties during saving. ___Default:___ true
--- @field IgnoreNotArchivable boolean -- Ignores the Archivable property and saves Non-Archivable instances. ___Default:___ true
--- @field IgnorePropertiesOfNotScriptsOnScriptsMode boolean -- Ignores property of every instance that is not a script in "scripts" mode. ___Default:___ false
--- @field IgnoreSpecialProperties boolean -- Ignores hidden/secret properties that are only accessible through `gethiddenproperty`. If your file is corrupted after saving, you can try turning this on. ___Default:___ false
--- @field IsolateLocalPlayer boolean -- Saves Children of LocalPlayer as separate folder and prevents any instance of ClassName Player with .Name identical to LocalPlayer.Name from saving. ___Default:___ false
--- @field IsolateStarterPlayer boolean -- If enabled, StarterPlayer will be cleared and the saved starter player will be placed into folders. ___Default:___ false
--- @field IsolateLocalPlayerCharacter boolean -- Saves Children of LocalPlayer.Character as separate folder and prevents any instance of ClassName Player with .Name identical to LocalPlayer.Name from saving. ___Default:___ false
--- @field RemovePlayerCharacters boolean -- Ignore player characters while saving. (Enables SaveNonCreatable automatically). ___Default:___ true
--- @field SaveNonCreatable boolean -- * Includes non-serializable instances as Folder objects (Name is misleading as this is mostly a fix for certain NilInstances and isn't always related to NotCreatable). ___Default:___ false
--- .NotCreatableFixes table<Instance.ClassName> -- * {"Player"} is the same as {Player = "Folder"}; Format like {SpawnLocation = "Part"} is only to be used when SpawnLocation inherits from "Part" AND "Part" is Creatable. ___Default:___ { "Player", "PlayerScripts", "PlayerGui" }
--- @field IsolatePlayers boolean -- * This option does save players, it's just they won't show up in Studio and can only be viewed through the place file code (in text editor). More info at https://github.com/luau/UniversalSynSaveInstance/issues/2. ___Default:___ false
--- @field AllowResettingProperties boolean -- * **RISKY:** Enables Resetting of properties for sake of checking their default value (Useful for cases when Instance is NotCreatable like services yet we need to get the default value ) then sets the property back to the original value, which might get detected by some games **WARNING: Sometimes Properties might not be able to be set to the original value due to circumstances**. ___Default:___ false
--- @field IgnoreSharedStrings boolean -- * **RISKY: FIXES CRASHES (TEMPORARY, TESTED ON ROEXEC ONLY). FEEL FREE TO DISABLE THIS TO SEE IF IT WORKS FOR YOU**. ___Default:___ true
--- @field SharedStringOverwrite boolean -- * **RISKY:** if the process is not finished aka crashed then none of the affected values will be available. SharedStrings can also be used for ValueTypes that aren't `SharedString`, this behavior is not documented anywhere but makes sense (Could create issues though, due to _potential_ ValueType mix-up, only works on certain types which are all base64 encoded so far). Reason: Allows for potential smaller file size (can also be bigger in some cases). ___Default:___ false
--- @interface OptionsAliases
--- @within SynSaveInstance
--- Aliases for the [SynSaveInstance.CustomOptions table].
--- @field FilePath string -- FileName
--- @field IgnoreDefaultProperties string -- IgnoreDefaultProps
--- @field SaveNonCreatable string -- SaveNotCreatable
--- @field IsolatePlayers string -- SavePlayers
--- @field scriptcache string -- DecompileJobless
--- @field timeout string -- DecompileTimeout
--- @field IgnoreNotArchivable string -- INVERSE IgnoreArchivable
--- @field RemovePlayerCharacters string -- INVERSE SavePlayerCharacters
--[=[
@function saveinstance
Saves instances with specified options.
TODO: CODE BLOCK EXAMPLES
@within SynSaveInstance
@yields
@param Parameter_1 variant<table, table<Instance>> -- Can either be [SynSaveInstance.CustomOptions table] or a filled with instances ({Instance}), (then it will be treated as ExtraInstances with an invalid mode and IsModel will be true).
@param Parameter_2 table -- [OPTIONAL] If present, then Parameter_2 will be assumed to be [SynSaveInstance.CustomOptions table]. And then if the Parameter_1 is an Instance, then it will be assumed to be [SynSaveInstance.CustomOptions table].Object. If Parameter_1 is a table filled with instances ({Instance}), then it will be assumed to be [SynSaveInstance.CustomOptions table].ExtraInstances and IsModel will be true). This exists for sake compatibility with `saveinstance(game, {})`
]=]
local function synsaveinstance(CustomOptions, CustomOptions2)
local totalstr = ""
local savebuffer, savebuffer_count = { '<roblox version="4">' }, 2
local StatusText
local OPTIONS = {
mode = "optimized",
noscripts = false,
scriptcache = true,
decomptype = "",
timeout = 10,
--* New:
__DEBUG_MODE = false,
-- Binary = false, -- true in syn newer versions (false in our case because no binary support yet), Description: Saves everything in Binary Mode (rbxl/rbxm).
--Callback = nil, -- Description: If set, the serialized data will be sent to the callback instead of to file.
--Clipboard = false, -- Description: If set to true, the serialized data will be set to the clipboard, which can be later pasted into studio easily. Useful for saving models.
-- MaxThreads = 3 -- Description: The number of decompilation threads that can run at once. More threads means it can decompile for scripts at a time.
-- DisableCompression = false, --Description: Disables compression in the binary output
DecompileJobless = false,
DecompileIgnore = { -- * Clean these up (merged Old Syn and New Syn)
service.Chat,
service.TextChatService,
},
SaveBytecodeIfDecompilerFails = false,
IgnoreProperties = { "ScriptGuid", "UniqueId", "HistoryId" },
IgnoreList = { service.CoreGui, service.CorePackages },
ExtraInstances = {},
NilInstances = false,
NilInstancesFixes = {},
IgnoreDefaults = {
"__api_dump_class_not_creatable__",
"__api_dump_no_string_value__",
"__api_dump_skipped_class__",
-- "__api_dump_write_only_property__" , -- ? Is this needed
},
SaveCacheInterval = 0x1600 * 2,
ShowStatus = true,
SafeMode = false,
ReadMe = true,
FilePath = false,
Object = false,
IsModel = false,
IgnoreDefaultProperties = true,
IgnoreNotArchivable = true,
IgnorePropertiesOfNotScriptsOnScriptsMode = false,
IgnoreSpecialProperties = false,
IsolateLocalPlayer = false,
IsolateLocalPlayerCharacter = false,
IsolatePlayers = false,
IsolateStarterPlayer = false,
RemovePlayerCharacters = true,
SaveNonCreatable = false,
NotCreatableFixes = { "Player", "PlayerScripts", "PlayerGui" },
-- ! Risky
AllowResettingProperties = false,
IgnoreSharedStrings = true,
SharedStringOverwrite = false,
OptionsAliases = { -- You can't really modify these as a user
FilePath = "FileName",
IgnoreDefaultProperties = "IgnoreDefaultProps",
SaveNonCreatable = "SaveNotCreatable",
IsolatePlayers = "SavePlayers",
scriptcache = "DecompileJobless",
timeout = "DecompileTimeout",
},
NotScriptableFixes = {
Players = { MaxPlayersInternal = "MaxPlayers", PreferredPlayersInternal = "PreferredPlayers" }, -- ? Only needed for execs that lack LocalUserSecurity (Level 2, 5, 9), even so, it's a pretty useless information as it can be viewed elsewhere
-- DebuggerBreakpoint = {line="Line"}, -- ? This shouldn't appear in live games (try to prove this wrong)
BallSocketConstraint = { MaxFrictionTorqueXml = "MaxFrictionTorque" },
BasePart = {
Color3uint8 = "Color",
MaterialVariantSerialized = "MaterialVariant",
size = "Size",
_Inheritors = {
TriangleMeshPart = {
FluidFidelityInternal = "FluidFidelity",
_Inheritors = {
MeshPart = { InitialSize = "MeshSize" },
PartOperation = { InitialSize = "MeshSize" },
},
},
FormFactorPart = { formFactorRaw = "FormFactor", _Inheritors = { Part = { shape = "Shape" } } },
TrussPart = { style = "Style" },
},
},
-- CustomEvent = {PersistedCurrentValue=function(instance) -- * Class is Deprecated and :SetValue doesn't seem to affect GetCurrentValue anymore
-- local Receiver = instance:GetAttachedReceivers()[1]
-- if Receiver then
-- return Receiver:GetCurrentValue()
-- else
-- error("No Receiver")
-- end
-- end},
DoubleConstrainedValue = { value = "Value" },
IntConstrainedValue = { value = "Value" },
Fire = { heat_xml = "Heat", size_xml = "Size" },
Humanoid = { Health_XML = "Health" },
MaterialService = { Use2022MaterialsXml = "Use2022Materials" },
Model = {
ScaleFactor = function(instance)
return instance:GetScale()
end,
WorldPivotData = "WorldPivot",
-- ModelMeshCFrame = "Pivot Offset", -- * Both are NotScriptable
-- _Inheritors = {
-- Workspace = { SignalBehavior2 = "SignalBehavior" }, -- * Both are NotScriptable so it doesn't make sense to keep
-- },
},
PackageLink = { PackageIdSerialize = "PackageId", VersionIdSerialize = "VersionNumber" },
StarterPlayer = { AvatarJointUpgrade_Serialized = "AvatarJointUpgrade" },
Smoke = { size_xml = "Size", opacity_xml = "Opacity", riseVelocity_xml = "RiseVelocity" },
Sound = {
xmlRead_MaxDistance_3 = "RollOffMaxDistance", -- * Also MaxDistance
},
-- ViewportFrame = { -- * Pointless because these reflect CurrentCamera's properties
-- CameraCFrame = function(instance) -- *
-- local CurrentCamera = instance.CurrentCamera
-- if CurrentCamera then
-- return CurrentCamera.CFrame
-- else
-- error("No CurrentCamera")
-- end
-- end,
-- -- CameraFieldOfView =
-- },
WeldConstraint = {
Part0Internal = "Part0",
Part1Internal = "Part1",
-- State = function(instance)
-- -- If untouched then default state is 3 (default true)
-- return instance.Enabled and 1 or 0
-- end,
},
}, -- For more info: https://github.com/luau/UniversalSynSaveInstance/blob/master/Tests/Potentially%20Missing%20Properties%20Tracker.luau
}
local function CheckAlias(key)
for Option, Alias in OPTIONS.OptionsAliases do
if key == Alias then
return Option
end
end
end
do
local function NilInstanceFixGeneral(Name, ClassName)
return function(instance, InstancePropertyOverrides)
local Exists = OPTIONS.NilInstancesFixes[Name]
local Fix
local DoesntExist = not Exists
if DoesntExist then
Fix = Instance.new(ClassName)
OPTIONS.NilInstancesFixes[Name] = Fix
-- Fix.Name = Name
InstancePropertyOverrides[Fix] = { __Children = { instance }, Properties = { Name = Name } }
else
Fix = Exists
end
table.insert(InstancePropertyOverrides[Fix].__Children, instance)
-- InstancePropertyOverrides[instance].Parent = AnimationController
if DoesntExist then
return Fix
end
end
end
OPTIONS.NilInstancesFixes.Animator = NilInstanceFixGeneral(
"Animator has to be placed under Humanoid or AnimationController",
"AnimationController"
)
-- TODO: Merge BaseWrap & Attachment & AdPortal fix (put all under MeshPart container)
-- TODO?:
-- DebuggerWatch DebuggerWatch must be a child of ScriptDebugger
-- PluginAction Parent of PluginAction must be Plugin or PluginMenu that created it!
OPTIONS.NilInstancesFixes.AdPortal = NilInstanceFixGeneral("AdPortal must be parented to a Part", "Part")
OPTIONS.NilInstancesFixes.BaseWrap =
NilInstanceFixGeneral("BaseWrap must be parented to a MeshPart", "MeshPart")
OPTIONS.NilInstancesFixes.Attachment =
NilInstanceFixGeneral("Attachments must be parented to a BasePart or another Attachment", "Part") -- * Bones inherit from Attachments
if CustomOptions2 and type(CustomOptions2) == "table" then
local tmp = CustomOptions
local Type = typeof(tmp)
CustomOptions = CustomOptions2
if Type == "Instance" then
CustomOptions.Object = tmp
elseif Type == "table" and typeof(tmp[1]) == "Instance" then
CustomOptions.ExtraInstances = tmp
OPTIONS.IsModel = true
end
end
local Type = typeof(CustomOptions)
if Type == "table" then
if typeof(CustomOptions[1]) == "Instance" then
OPTIONS.mode = "invalidmode"
OPTIONS.ExtraInstances = CustomOptions
OPTIONS.IsModel = true
CustomOptions = {}
else
for key, value in CustomOptions do
if OPTIONS[key] == nil then
local Option = CheckAlias(key)
if Option then
OPTIONS[Option] = value
end
else
OPTIONS[key] = value
end
end
local Decompile = CustomOptions.Decompile
if Decompile ~= nil then
OPTIONS.noscripts = not Decompile
end
local IgnoreArchivable = CustomOptions.IgnoreArchivable
if IgnoreArchivable ~= nil then
OPTIONS.IgnoreNotArchivable = not IgnoreArchivable
end
local SavePlayerCharacters = CustomOptions.SavePlayerCharacters
if SavePlayerCharacters ~= nil then
OPTIONS.RemovePlayerCharacters = not SavePlayerCharacters
end
local RemovePlayers = CustomOptions.RemovePlayers
if RemovePlayers ~= nil then
OPTIONS.IsolatePlayers = not RemovePlayers
end
end
elseif Type == "Instance" then
OPTIONS.mode = "invalidmode"
OPTIONS.Object = CustomOptions
CustomOptions = {}
else
CustomOptions = {}
end
end
local InstancePropertyOverrides = {}
local DecompileIgnore, IgnoreList, IgnoreProperties, NotCreatableFixes, IgnoreDefaults =
ArrayToDictionary(OPTIONS.DecompileIgnore, "adjust"),
ArrayToDictionary(OPTIONS.IgnoreList, "adjust"),
ArrayToDictionary(OPTIONS.IgnoreProperties),
ArrayToDictionary(OPTIONS.NotCreatableFixes, "adjust", "Folder"),
ArrayToDictionary(OPTIONS.IgnoreDefaults)
local __DEBUG_MODE = OPTIONS.__DEBUG_MODE
local FilePath = OPTIONS.FilePath
local SaveCacheInterval = OPTIONS.SaveCacheInterval
local ToSaveInstance = OPTIONS.Object
local IsModel = OPTIONS.IsModel
if ToSaveInstance and CustomOptions.IsModel == nil then
IsModel = true
end
local IgnoreDefaultProperties = OPTIONS.IgnoreDefaultProperties
local IgnoreNotArchivable = OPTIONS.IgnoreNotArchivable
local IgnorePropertiesOfNotScriptsOnScriptsMode = OPTIONS.IgnorePropertiesOfNotScriptsOnScriptsMode
local IgnoreSpecialProperties = OPTIONS.IgnoreSpecialProperties
local IsolateLocalPlayer = OPTIONS.IsolateLocalPlayer
local IsolateLocalPlayerCharacter = OPTIONS.IsolateLocalPlayerCharacter
local IsolateStarterPlayer = OPTIONS.IsolateStarterPlayer
local IsolatePlayers = OPTIONS.IsolatePlayers
local SaveNonCreatable = OPTIONS.SaveNonCreatable
local DecompileJobless = OPTIONS.DecompileJobless
local ScriptCache = OPTIONS.scriptcache
local Timeout = OPTIONS.timeout
local AllowResettingProperties = OPTIONS.AllowResettingProperties
local IgnoreSharedStrings = OPTIONS.IgnoreSharedStrings
local SharedStringOverwrite = OPTIONS.SharedStringOverwrite
local NotScriptableFixes = OPTIONS.NotScriptableFixes
local ldeccache = globalenv.scriptcache
local DecompileIgnoring, ToSaveList, ldecompile, placename, elapse_t, SaveNonCreatableWillBeEnabled, RecoveredScripts
if ScriptCache and not ldeccache then
ldeccache = {}
globalenv.scriptcache = ldeccache
end
if ToSaveInstance == game then
OPTIONS.mode = "full"
ToSaveInstance = nil
IsModel = nil
end
local function IsLuaSourceContainer(instance)
return instance:IsA("LuaSourceContainer")
end
do
local mode = string.lower(OPTIONS.mode)
local tmp = table.clone(OPTIONS.ExtraInstances)
local PlaceName = game.PlaceId
pcall(function()
PlaceName ..= " " .. service.MarketplaceService:GetProductInfo(PlaceName).Name
end)
local function SanitizeForFile(str)
return string.sub(string.gsub(string.gsub(string.gsub(str, "[^%w _]", ""), " +", " "), " +$", ""), 1, 240)
end
if IsModel then
if mode == "optimized" then -- ! NOT supported with Model file mode
mode = "full"
end
for _, key in
{
"IsolateLocalPlayer",
"IsolateLocalPlayerCharacter",
"IsolatePlayers",
"IsolateStarterPlayer",
"NilInstances",
}
do
if CustomOptions[key] == nil then
local Option = CheckAlias(key)
if CustomOptions[Option] == nil then
OPTIONS[key] = false
end
end
end
placename = (
FilePath or SanitizeForFile("model " .. PlaceName .. " " .. (ToSaveInstance or tmp[1]):GetFullName())
) .. ".rbxmx"
else
placename = (FilePath or SanitizeForFile("place " .. PlaceName)) .. ".rbxlx"
end
if mode ~= "scripts" then
IgnorePropertiesOfNotScriptsOnScriptsMode = nil
end
local TempRoot = ToSaveInstance or game
if mode == "full" then
local Children = TempRoot:GetChildren()
if 0 < #Children then
table.move(Children, 1, #Children, #tmp + 1, tmp)
end
elseif mode == "optimized" then -- ! Incompatible with .rbxmx (Model file) mode
-- if IsolatePlayers then
-- table.insert(_list_0, "Players")
-- end
for _, x in
{
"Workspace",
"Players",
"Lighting",
"MaterialService",
"ReplicatedFirst",
"ReplicatedStorage",
"ServerScriptService", -- ? Why
"ServerStorage", -- ? Why
"StarterGui",
"StarterPack",
"StarterPlayer",
"Teams",
"SoundService",
"TextChatService",
"Chat",
-- "InsertService",
"JointsService",
-- "LocalizationService",
-- "TestService",
-- "VoiceChatService",
}
do
table.insert(tmp, service[x])
end
elseif mode == "scripts" then
-- TODO: Only save paths that lead to scripts (nothing else)
-- Currently saves paths along with children of each tree
local unique = {}
for _, instance in TempRoot:GetDescendants() do
if IsLuaSourceContainer(instance) then
local Parent = instance.Parent
while Parent and Parent ~= TempRoot do
instance = instance.Parent
Parent = instance.Parent
end
if Parent then
unique[instance] = true
end
end
end
for instance in unique do
table.insert(tmp, instance)
end
end
ToSaveList = tmp
end
do
if load_decompiler then
load_decompiler(Timeout)
end
local Decompiler = OPTIONS.decomptype == "custom" and custom_decompiler
or globalcontainer.decompile
or custom_decompiler
-- if Decompiler == custom_decompiler then -- Cope
-- local key = "DecompileTimeout"
-- if CustomOptions[key] == nil then
-- local Option = CheckAlias(key)
-- if CustomOptions[Option] == nil then
-- Timeout = 1
-- end
-- end
-- end
local SaveBytecode
if OPTIONS.SaveBytecodeIfDecompilerFails then
SaveBytecode = function(DefaultOutput, Script)
local s, bytecode = pcall(getscriptbytecode, Script)
if s then
if bytecode and bytecode ~= "" then
return DefaultOutput .. "\n--Bytecode (Base64):\n-- " .. base64encode(bytecode)
end
end
return DefaultOutput
end
end
if OPTIONS.noscripts then
ldecompile = function(Script)
local output = "-- Decompiling is disabled"
if SaveBytecode then
return SaveBytecode(output, Script)
end
return output
end
elseif Decompiler then
local function DecompileHandler(Script)
if Timeout == -1 then
return pcall(Decompiler, Script)
end
local Thread = coroutine.running()
local Thread_Timeout, Cancelled
task.spawn(function(thread, scr)
local ok, result = pcall(Decompiler, scr)
if Cancelled then
return
end
if Thread_Timeout then
task.cancel(Thread_Timeout)
else
task.delay(0, function()
task.cancel(Thread_Timeout)
end)
end
while coroutine.status(thread) ~= "suspended" do
task.wait()
end
coroutine.resume(thread, ok, result)
end, Thread, Script)
Thread_Timeout = task.delay(Timeout, function(thread)
Cancelled = true -- TODO task.cancel
coroutine.resume(thread, nil, "Decompiler timed out")
end, Thread)
return coroutine.yield()
end
ldecompile = function(Script)
-- local name = scr.ClassName .. scr.Name
local hashed_bytecode
if ScriptCache and getscriptbytecode then
local s, bytecode = pcall(getscriptbytecode, Script) -- TODO This is awful because we already do this in Custom Decomp (when we are using it, that is)
local Cached
if s then
if not bytecode or bytecode == "" then
return "-- The Script is Empty"
end
hashed_bytecode = sha384(bytecode)
Cached = ldeccache[hashed_bytecode]
end
if Cached then
return Cached
elseif DecompileJobless then
return "-- Not found in already decompiled ScriptCache"
end
else
task.wait() -- TODO Maybe remove?
end
local ok, result = DecompileHandler(Script)
local output
if ok then
result = string.gsub(result, "\0", "\\0") -- TODO Temporary (decompiler issue)
output = result
else
output = "--[[ Failed to decompile\nReason:\n" .. (result or "") .. "\n]]"
if SaveBytecode then
output = SaveBytecode(output, Script)
end
end
if ScriptCache and hashed_bytecode then -- TODO there might(?) be an edgecase where it manages to decompile (built-in) even though getscriptbytecode failed, and the output won't get cached
ldeccache[hashed_bytecode] = output -- ? Should we cache even if it timed out?
end
return output
end
else
ldecompile = function(Script)
local output = "-- Decompiling is NOT supported on your executor"
if SaveBytecode then
return SaveBytecode(output, Script)
end
return output
end
end
end
local function getsafeproperty(instance, PropertyName)
return instance[PropertyName]
end
local function setsafeproperty(instance, PropertyName, Value)
instance[PropertyName] = Value
end
local function IsPropertyModified(instance, ProperyName)
return instance:IsPropertyModified(ProperyName)
end
local function ResetPropertyToDefault(instance, ProperyName)
instance:ResetPropertyToDefault(ProperyName)
end
local function SetProperty(instance, PropertyName, Value)
local ok = pcall(setsafeproperty, instance, PropertyName, Value)
if not ok then
ok = pcall(sethiddenproperty, instance, PropertyName, Value)
end
return ok
end
local function ReadProperty(Property, instance, PropertyName, specialProperties, Special)
local raw
local InstanceOverride = InstancePropertyOverrides[instance]
if InstanceOverride then
local PropertyOverride = InstanceOverride.Properties[PropertyName]
if PropertyOverride then
return PropertyOverride, specialProperties
end
end
local function FilterResult(Result) -- ? raw == nil thanks to SerializedDefaultAttributes; "can't get value" - "shap" Roexec; "Invalid value for enum " - "StreamingPauseMode" (old games probably) Roexec
return Result == nil
or Result == "can't get value"
or type(Result) == "string"
and (Find(Result, "Unable to get property " .. PropertyName) or Property.Category == "Enum" and Find(
Result,
"Invalid value for enum "
))
end
if Special then
if specialProperties == nil and getspecialinfo then
specialProperties = getspecialinfo(instance)
raw = specialProperties[PropertyName]
end
if raw == nil then
local ok, result = pcall(gethiddenproperty, instance, PropertyName)
if ok then
raw = result
end
end
if FilterResult(raw) then
-- * Skip next time we encounter this too perhaps
-- Property.Special = false
-- Property.CanRead = false
return "__BREAK", specialProperties -- ? We skip it because even if we use "" it will just reset to default in most cases, unless it's a string tag for example (same as not being defined)
end
else
local CanRead = Property.CanRead
if CanRead == nil then
local ok, result = pcall(getsafeproperty, instance, PropertyName)
if ok then
raw = result
else
if specialProperties == nil and getspecialinfo then
specialProperties = getspecialinfo(instance)
raw = specialProperties[PropertyName]
end
if raw == nil then
ok, result = pcall(gethiddenproperty, instance, PropertyName)
if ok then
raw = result
Property.Special = true
end
else
ok = true
Property.Special = true
end
end
Property.CanRead = ok
if not ok or FilterResult(raw) then
return "__BREAK", specialProperties
end
elseif CanRead then
raw = instance[PropertyName]
else -- * Skips because we've checked this property before
return "__BREAK", specialProperties
end
end
return raw, specialProperties
end
local function ReturnItem(ClassName, instance)
return '<Item class="' .. ClassName .. '" referent="' .. referents[instance] .. '"><Properties>' -- TODO: Ideally this shouldn't return <Properties> as well as the line below to close it IF IgnorePropertiesOfNotScriptsOnScriptsMode is Enabled OR If all properties are default (reduces file size by at least 1.4%)
end
local function ReturnProperty(Tag, PropertyName, Value)
return "<" .. Tag .. ' name="' .. PropertyName .. '">' .. Value .. "</" .. Tag .. ">"
end
local function ApiFormat(Value, Category, ValueType, Default)
if Category == "Enum" then
Value = Descriptors.__ENUMNAME(Value)
elseif Category == "Primitive" then
Value = Descriptors[ValueType](Value, Default)
end
return tostring(Value)
end
local function ReturnValueAndTag(raw, ValueType, Descriptor)
local value, tag = (Descriptor or Descriptors[ValueType])(raw)
return value, tag == nil and ValueType or tag
end
local function InheritsFix(Fixes, ClassName, instance)
local Fix = Fixes[ClassName]
if Fix then
return Fix
elseif Fix == nil then
for className, fix in Fixes do
if instance:IsA(className) then
return fix
end
end
end
end
local function getsizeformat()
local Size
local totalsize = #totalstr
for Index, BinaryPrefix in
{
"B",
"KB",
"MB",
"GB",
"TB",
}
do
if totalsize < 0x400 ^ Index then
Size = math.floor(totalsize / (0x400 ^ (Index - 1)) * 10) / 10 .. " " .. BinaryPrefix
break
end
end
return Size
end
local function savecache()
totalstr ..= table.concat(savebuffer)
writefile(placename, totalstr)
-- appendfile(placename, savestr) -- TODO: Sadly breaks evon (fixable) AND supposedly causes uneven amount of Tags (e.g. <Item> must be closed with </Item> but sometimes there's more of one than the other). While being under load, the function produces unexpected output?
-- totalsize += #savestr
table.clear(savebuffer)
savebuffer_count = 1
if StatusText then
StatusText.Text = "Saving.. Size: " .. getsizeformat()
end
task.wait() -- TODO Remove maybe?
end
local function savespecific(ClassName, Properties)
local Ref = Instance.new(ClassName)
local Item = ReturnItem(Ref.ClassName, Ref)
for PropertyName, PropertyValue in Properties do
local Class, value, tag
-- TODO: Improve all sort of overrides & exceptions in the code (code below is awful)
if "Source" == PropertyName then
tag = "ProtectedString"
value = Descriptors.__PROTECTEDSTRING(PropertyValue)
Class = "Script"
elseif "Name" == PropertyName then
Class = "Instance"
local ValueType = ClassList[Class].Properties[PropertyName].ValueType
value, tag = ReturnValueAndTag(PropertyValue, ValueType)
end
if Class then
Item ..= ReturnProperty(tag, PropertyName, value)
end
end
Item ..= "</Properties>"
return Item
end
local function savehierarchy(Hierarchy, Afterwards)
for _, instance in Hierarchy do
if IgnoreNotArchivable and not instance.Archivable then
continue
end
local SkipEntirely = IgnoreList[instance]
if SkipEntirely then
continue
end
local ClassName = instance.ClassName
local Class = ClassList[ClassName]
if not Class then
continue
end
local InstanceName = instance.Name
local OnIgnoredList = IgnoreList[ClassName]
if OnIgnoredList and (OnIgnoredList == true or OnIgnoredList[InstanceName]) then
continue
end
if not DecompileIgnoring then
DecompileIgnoring = DecompileIgnore[instance]
if DecompileIgnoring == nil then
local DecompileIgnored = DecompileIgnore[ClassName]
DecompileIgnoring = DecompileIgnored
and (DecompileIgnored == true or DecompileIgnored[InstanceName])
end
if DecompileIgnoring then
DecompileIgnoring = instance
end
end
local InstanceOverride
do
local Fix = NotCreatableFixes[ClassName]
if Fix then
if SaveNonCreatable then
if InstanceName ~= ClassName then
InstanceOverride = InstancePropertyOverrides[instance]
if not InstanceOverride then
InstanceOverride = { Properties = {} }
InstancePropertyOverrides[instance] = InstanceOverride
end
InstanceOverride.Properties.Name = "[" .. ClassName .. "] " .. InstanceName -- ! Assuming anything that has __Children will have .Properties
end
ClassName = Fix
else
continue -- They won't show up in Studio anyway (Enable IsolatePlayers if you wish to bypass this)
end
end
end
if not InstanceOverride then
InstanceOverride = InstancePropertyOverrides[instance]
end
local ChildrenOverride = InstanceOverride and InstanceOverride.__Children
if ChildrenOverride then
savebuffer[savebuffer_count] = savespecific(ClassName, InstanceOverride.Properties) -- ! Assuming anything that has __Children will have .Properties
savebuffer_count += 1
else
-- local Properties =
savebuffer[savebuffer_count] = ReturnItem(ClassName, instance) -- TODO: Ideally this shouldn't return <Properties> as well as the line below to close it IF IgnorePropertiesOfNotScriptsOnScriptsMode is ENABLED
savebuffer_count += 1
if not (IgnorePropertiesOfNotScriptsOnScriptsMode and not IsLuaSourceContainer(instance)) then
local specialProperties, Replica
for _, Property in inheritedproperties[ClassName] do
local PropertyName = Property.Name
if IgnoreProperties[PropertyName] then
continue
end
local Special = Property.Special
if IgnoreSpecialProperties and Special then
continue
end
local ValueType = Property.ValueType
if IgnoreSharedStrings and ValueType == "SharedString" then -- ? More info in Options
continue
end
local raw
raw, specialProperties =
ReadProperty(Property, instance, PropertyName, specialProperties, Special)
if raw == "__BREAK" then -- ! Assuming __BREAK is always returned when there's a failure to read a property
local PropertyFix
do
local Inheritors = NotScriptableFixes
while Inheritors do
local fix
for Superclass, Fixes in Inheritors do
if instance:IsA(Superclass) then
fix = Fixes
break
end
end
if fix then
PropertyFix = fix[PropertyName]
if PropertyFix then
break
end
else
break
end
Inheritors = fix._Inheritors
end
end
if PropertyFix then
local ok, result = pcall(
type(PropertyFix) == "function" and PropertyFix or getsafeproperty,
instance,
PropertyFix
)
if ok then
raw = result
else
continue
end
else
continue
end
end
if SharedStringOverwrite and ValueType == "BinaryString" then -- TODO: Convert this to table if more types are added
ValueType = "SharedString"
end
local Category = Property.Category
if
IgnoreDefaultProperties
and not (IsLuaSourceContainer(instance) and PropertyName == "Source")
then -- ? Could be not just "Source" in the future
local ok, IsModified = pcall(IsPropertyModified, instance, PropertyName) -- ? Not yet enabled lol (580)
if ok and not IsModified then
continue
end
local Default = Property.Default
if IgnoreDefaults[Default] then
local ClassTags = ClassList[ClassName].Tags
local NotCreatable = ClassTags and ClassTags.NotCreatable -- __api_dump_class_not_creatable__ also indicates this
local Reset
if NotCreatable then -- TODO: This whole block should only run if Replica doesn't exist yet, except ResetPropertyToDefault because it's needed for just about every property of NotCreatable objects (in order to check default if undefined in API Dump)
if AllowResettingProperties then
Reset = pcall(ResetPropertyToDefault, instance, PropertyName)
if Reset and not Replica then
Replica = instance
end
end
elseif not Replica then
Replica = classreplicas[ClassName]
end
if Replica and not (NotCreatable and not Reset) then
Default = ReadProperty(Property, Replica, PropertyName, specialProperties, Special)
-- * Improve this along with specialProperties (merge or maybe store the method to Property.Special), get this property at any cost
if Reset and not SetProperty(Replica, PropertyName, raw) and __DEBUG_MODE then -- It has been reset
warn(
"FAILED TO SET BACK TO ORIGINAL VALUE (OPEN A GITHUB ISSUE): ",
ValueType,
ClassName,
PropertyName
)
end
Default = ApiFormat(Default, Category, ValueType)
Property.Default = Default
end
elseif Default == "default" and ValueType == "PhysicalProperties" then
Default = "nil"
Property.Default = Default
end
if ApiFormat(raw, Category, ValueType, Default) == Default then -- ! PhysicalProperties, Font - Font.fromName, CFrame, BrickColor (and Enum to some extent) aren't being defaulted properly in the api dump, meaning an issue must be created.. (They're not being tostringed or fail to do so)
-- TODO: tostring(Vector3.new(0/0,math.huge,-math.huge)) returns lowercase Extremes - "nan, inf, -inf" meanwhile for everything else (in xml & dump defaults they seem to be uppercase), this can cause issues later on when a Vector3 Property comes out, default of which will include either of these of Extreme values (Needs to be watched)
continue
end
end
-- Serialization start
local tag, value
if Category == "Class" then
tag = "Ref"
if raw then
if SaveNonCreatableWillBeEnabled then
local Fix = NotCreatableFixes[raw.ClassName]
if
Fix
and (
PropertyName == "PlayerToHideFrom"
or ValueType ~= "Instance" and ValueType ~= Fix
)
then
continue
end
end
value = referents[raw]
else
value = "null"
end
elseif Category == "Enum" then -- ! We do this order (Enums before Descriptors) specifically because Font Enum might get a Font Descriptor despite having Enum Category, unlike Font DataType which that Descriptor is meant for
value, tag = Descriptors.__ENUM(raw)
else
local Descriptor = Descriptors[ValueType]
if Descriptor then
value, tag = ReturnValueAndTag(raw, ValueType, Descriptor)
elseif "BinaryString" == ValueType then -- TODO: Try fitting this inside Descriptors
tag = ValueType
value = Descriptors.__BINARYSTRING(raw)
if -- ? Roblox doesn't CDATA anything else other than these as far as we know (try to prove this wrong)
PropertyName == "SmoothGrid"
or PropertyName == "MaterialColors"
or PropertyName == "PhysicsGrid"
then
value = Descriptors.__CDATA(value)
end
elseif "ProtectedString" == ValueType then -- TODO: Try fitting this inside Descriptors
tag = ValueType
if PropertyName == "Source" then
if DecompileIgnoring then -- ? Should this really prevent extraction of the original source if present ?
value = "-- Ignored"
else
local should_decompile = true
local LinkedSourceUrl = instance.LinkedSource -- ! Assuming every Class that has ProtectedString Source property also has a LinkedSource property
local LinkedSourceNotEmpty = LinkedSourceUrl ~= ""
if LinkedSourceNotEmpty then
local LinkedSource = string.match(LinkedSourceUrl, "%w+$") -- TODO: No sure if this pattern matches possible cases. Example is: 'rbxassetid://0&hash=cd73dd2fe5e5013137231c227da3167e'
if LinkedSource then
local Cached = ldeccache[LinkedSource]
if Cached then
value = Cached
should_decompile = nil
elseif DecompileJobless then
value = "-- Not found in already decompiled ScriptCache"
should_decompile = nil
end
local Source
local ok = pcall(function()
Source = game:HttpGet(
"https://assetdelivery.roproxy.com/v1/asset/?"
.. (string.find(LinkedSource, "%a") and "hash" or "id")
.. "="
.. LinkedSource
)
end)
if ok then
ldeccache[LinkedSource] = Source
value = Source
local Path = instance:GetFullName()
if RecoveredScripts then
table.insert(RecoveredScripts, Path)
else
RecoveredScripts = { Path }
end
should_decompile = nil
end
else --if __DEBUG_MODE then -- * We print this anyway because very important
warn(
"FAILED TO EXTRACT ORIGINAL SCRIPT SOURCE (OPEN A GITHUB ISSUE): ",
instance:GetFullName(),
LinkedSourceUrl
)
end
end
if should_decompile then
local IsLocalScript = instance:IsA("LocalScript")
if
IsLocalScript and instance.RunContext == Enum.RunContext.Server
or not IsLocalScript
and instance:IsA("Script")
and instance.RunContext ~= Enum.RunContext.Client
then
value = "-- Server Scripts can NOT be decompiled" --TODO: Could be not just server scripts in the future
else
value = ldecompile(instance)
end
end
value = "-- Saved by UniversalSynSaveInstance https://discord.gg/wx4ThpAsmw\n\n"
.. (LinkedSourceNotEmpty and "-- Original Source: " .. LinkedSourceUrl .. " (use https://assetdelivery.roproxy.com/v1/asset/?id=ASSETID)\n\n" or "")
.. value
end
end
value = Descriptors.__PROTECTEDSTRING(value)
-- elseif "UniqueId" == ValueType or "SecurityCapabilities" == ValueType then -- ? Not sure yet
-- tag, value = ValueType, raw
else
--OptionalCoordinateFrame and so on, we make it dynamic
local startIDX, endIDX = Find(ValueType, "Optional")
if startIDX == 1 then
-- Extract the string after "Optional"
Descriptor = Descriptors[string.sub(ValueType, endIDX + 1)]
if Descriptor then
if raw ~= nil then
value, tag = ReturnValueAndTag(raw, ValueType, Descriptor)
else
value, tag = "", ValueType
end
end
end
end
end
if tag then
savebuffer[savebuffer_count] = ReturnProperty(tag, PropertyName, value)
savebuffer_count += 1
elseif __DEBUG_MODE then
warn("UNSUPPORTED TYPE (OPEN A GITHUB ISSUE): ", ValueType, ClassName, PropertyName)
end
end
end
savebuffer[savebuffer_count] = "</Properties>"
savebuffer_count += 1
if SaveCacheInterval < savebuffer_count then
savecache()
end
end
if SkipEntirely ~= false then -- ? We save instance without it's descendants in this case (== false)
local Children = ChildrenOverride or Afterwards or instance:GetChildren()
if #Children ~= 0 then
savehierarchy(Children)
end
end
if DecompileIgnoring and DecompileIgnoring == instance then
DecompileIgnoring = nil
end
savebuffer[savebuffer_count] = "</Item>"
savebuffer_count += 1
end
end
local function saveextra(Name, Hierarchy, CustomClassName, Source)
savebuffer[savebuffer_count] = savespecific((CustomClassName or "Folder"), { Name = Name, Source = Source })
savebuffer_count += 1
if Hierarchy then
savehierarchy(Hierarchy)
end
savebuffer[savebuffer_count] = "</Item>"
savebuffer_count += 1
end
local function savegame()
-- writefile(placename, "")
if IsModel then
savebuffer[savebuffer_count] = '<Meta name="ExplicitAutoJoints">true</Meta>'
savebuffer_count += 1
end
--[[
-- ? Roblox encodes the following additional attributes. These are not required. Moreover, any defined schemas are ignored, and not required for a file to be valid: xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.roblox.com/roblox.xsd"
Also http can be converted to https but not sure if Roblox would decide to detect that
-- ? <External>null</External><External>nil</External> - <External> is a legacy concept that is no longer used.
]]
-- TODO Find a better solution for this
SaveNonCreatableWillBeEnabled = SaveNonCreatable
or (IsolateLocalPlayer or IsolateLocalPlayerCharacter) and IsolateLocalPlayer
or IsolatePlayers
or OPTIONS.NilInstances and globalcontainer.getnilinstances -- ! Make sure this accurately reflects everything below
if ToSaveInstance then
savehierarchy({ ToSaveInstance }, ToSaveList)
else
savehierarchy(ToSaveList)
end
if IsolateLocalPlayer or IsolateLocalPlayerCharacter then
local Players = service.Players
local LocalPlayer = Players.LocalPlayer
if IsolateLocalPlayer then
SaveNonCreatable = true
saveextra("LocalPlayer", LocalPlayer:GetChildren())
end
if IsolateLocalPlayerCharacter then
local LocalPlayerCharacter = LocalPlayer.Character
if LocalPlayerCharacter then
saveextra("LocalPlayer Character", LocalPlayerCharacter:GetChildren())
end
end
end
if IsolateStarterPlayer then
-- SaveNonCreatable = true -- TODO: Enable if StarterPlayerScripts or StarterCharacterScripts stop showing up in isolated folder in Studio
saveextra("StarterPlayer", service.StarterPlayer:GetChildren())
end
if IsolatePlayers then
SaveNonCreatable = true
saveextra("Players", service.Players:GetChildren())
end
if OPTIONS.NilInstances and globalcontainer.getnilinstances then
local nilinstances = {}
local NilInstancesFixes = OPTIONS.NilInstancesFixes
for _, instance in globalcontainer.getnilinstances() do
if instance == game then
instance = nil
-- break
else
local ClassName = instance.ClassName
local Fix = InheritsFix(NilInstancesFixes, ClassName, instance)
if Fix then
instance = Fix(instance, InstancePropertyOverrides)
-- continue
end
local Class = ClassList[ClassName]
if Class then
local ClassTags = Class.Tags
if ClassTags and ClassTags.Service then -- For CSGDictionaryService, NonReplicatedCSGDictionaryService, LogService, ProximityPromptService, TestService & more
-- instance.Parent = game
instance = nil
-- continue
end
end
end
if instance then
table.insert(nilinstances, instance)
end
end
SaveNonCreatable = true
saveextra("Nil Instances", nilinstances)
end
if OPTIONS.ReadMe then
local exec_name = identifyexecutor or getexecutorname or whatexecutor
saveextra(
"README",
nil,
"Script",
"--[[\n"
.. (RecoveredScripts and "\t\tIMPORTANT: Original Source of these Scripts was Recovered: " .. service.HttpService:JSONEncode(
RecoveredScripts
) .. "\n" or "")
.. [[
Thank you for using UniversalSynSaveInstance.
If you didn't save in Binary - we recommended to save the game right away to take advantage of the binary format & to preserve values of certain properties if you used IgnoreDefaultProperties setting (as they might change in the future).
You can do that by going to FILE -> Save to File As -> Make sure File Name ends with .rbxl -> Save
If your player cannot spawn into the game, please move the scripts in StarterPlayer elsewhere & Set CharacterAutoLoads to true on Players service.
If the chat system does not work, please use the explorer and delete everything inside the TextChatService/Chat service(s).
Or run `game:GetService("Chat"):ClearAllChildren()`
If Union and MeshPart collisions don't work, run the script below in the Studio Command Bar:
local C = game:GetService("CoreGui")
local D = Enum.CollisionFidelity.Default
for _, v in game:GetDescendants() do
if v:IsA("TriangleMeshPart") and not v:IsDescendantOf(C) then
v.CollisionFidelity = D
end
end
If you can't move the Camera, run the scripts in the Studio Command Bar:
workspace.CurrentCamera.CameraType = Enum.CameraType.Fixed
This file was generated with the following settings:
]]
.. service.HttpService:JSONEncode(OPTIONS)
.. "\n\n\t\tElapsed time: "
.. os.clock() - elapse_t
.. " PlaceId: "
.. game.PlaceId
.. " Executor: "
.. (exec_name and table.concat({ exec_name() }, " ") or "Unknown")
.. "\n]]"
)
end
do
local tmp = { "<SharedStrings>" }
for Identifier, Value in SharedStrings do
table.insert(tmp, '<SharedString md5="' .. Identifier .. '">' .. Value .. "</SharedString>")
end
if 1 < #tmp then -- TODO: This sucks so much because we try to iterate a table just to check this (check above)
savebuffer[savebuffer_count] = table.concat(tmp)
savebuffer[savebuffer_count] = "</SharedStrings>"
savebuffer_count += 2 -- ? Is this fine
end
end
savebuffer[savebuffer_count] = "</roblox>"
savebuffer_count += 1
savecache()
table.clear(SharedStrings)
end
local Connections
do
local Players = service.Players
local LocalPlayer = Players.LocalPlayer
if IgnoreList.Model ~= true then
Connections = {}
local function IgnoreCharacter(Player)
table.insert(
Connections,
Player.CharacterAdded:Connect(function(Character)
IgnoreList[Character] = true
end)
)
local Character = Player.Character
if Character then
IgnoreList[Character] = true
end
end
if OPTIONS.RemovePlayerCharacters then
table.insert(
Connections,
Players.PlayerAdded:Connect(function(Player)
IgnoreCharacter(Player)
end)
)
for _, Player in Players:GetPlayers() do
IgnoreCharacter(Player)
end
else
IgnoreNotArchivable = false -- TODO Bad solution (Characters are NotArchivable); Also make sure the next solution is compatible with IsolateLocalPlayerCharacter
if IsolateLocalPlayerCharacter then
IgnoreCharacter(LocalPlayer)
end
end
end
if IsolateLocalPlayer and IgnoreList.Player ~= true then
IgnoreList[LocalPlayer] = true
end
end
if IsolateStarterPlayer then
IgnoreList.StarterPlayer = false
end
if IsolatePlayers then
IgnoreList.Players = false
end
if OPTIONS.ShowStatus then
StatusText = CreateStatusText()
end
do
if OPTIONS.SafeMode then
service.Players.LocalPlayer:Kick("\nSaving in Progress..\nPlease do NOT leave")
task.delay(5, service.GuiService.ClearError, service.GuiService)
end
elapse_t = os.clock()
local ok, err = xpcall(savegame, function(err)
return debug.traceback(err)
end)
for _, Connection in Connections do
Connection:Disconnect()
end
if StatusText then
task.spawn(function()
elapse_t = os.clock() - elapse_t
local Log10 = math.log10(elapse_t)
local ExtraTime = 10
if ok then
StatusText.Text = string.format("Saved! Time %.3f seconds; Size %s", elapse_t, getsizeformat())
StatusText.TextColor3 = Color3.new(0, 1)
task.wait(Log10 * 2 + ExtraTime)
else
StatusText.Text = "Failed! Check F9 console for more info"
StatusText.TextColor3 = Color3.new(1)
warn("Error found while saving:")
warn(err)
task.wait(Log10 + ExtraTime)
end
StatusText:Destroy()
end)
end
end
end
return synsaveinstance