From 08c4273ea598ca73d086b5090983c8c64ef12693 Mon Sep 17 00:00:00 2001
From: htt-py <95628489+phoriah@users.noreply.github.com>
Date: Sun, 7 Apr 2024 04:32:13 +0200
Subject: [PATCH] Fix NilInstance Attachments; Add SavePlayers
A lot of other Options added.
Basically re-wrote a lot of code.
---
README.md | 4 +-
TODO/PropertyPatches.luau | 1 +
Tests/NilInstanceErrorFinder.luau | 158 ++++
saveinstance.lua | 2 +
saveinstance.luau | 1397 ++++++++++++++++-------------
5 files changed, 953 insertions(+), 609 deletions(-)
create mode 100644 Tests/NilInstanceErrorFinder.luau
diff --git a/README.md b/README.md
index 3e71adb..e949cf0 100644
--- a/README.md
+++ b/README.md
@@ -116,10 +116,10 @@ https://discord.com/invite/wx4ThpAsmw **/** https://discord.gg/wx4ThpAsmw
* [x] RemovePlayerCharacters
* [x] SavePlayers
* [x] ShowStatus
- * [ ] Add Drawing Library support for ShowStatus
+ - [ ] Add Drawing Library support for ShowStatus
* ~~[-] IsolatePlayerGui~~ Use IsolateLocalPlayer instead
* [ ] Callback
- * [ ] Clipboard
+ * [ ] Clipboard/CopyToClipboard
* [ ] Binary (rbxl/rbxm)
- [ ] Support for as many Executors as possible (🤢🤮)
- [x] ~~Use getspecialinfo fallback function carefully as it's hardcoded~~ Useless because there's no way to tell if the Property Values of those instances are default or not
diff --git a/TODO/PropertyPatches.luau b/TODO/PropertyPatches.luau
index 77f2fdc..fdf6c28 100644
--- a/TODO/PropertyPatches.luau
+++ b/TODO/PropertyPatches.luau
@@ -2,6 +2,7 @@
-- ! Source: https://github.com/MaximumADHD/Roblox-File-Format/blob/main/Plugins/GenerateApiDump/PropertyPatches.lua
-- ! Extras: https://github.com/rojo-rbx/rbx-dom/tree/master/patches
+-- ! Extras: https://github.com/Dekkonot/rbx-instance-serializer/blob/23f772f6f78af879a21faa9fea3e6c4f93d1cdee/src/API.lua#L19
export type GetSet = string | {
Get: string,
diff --git a/Tests/NilInstanceErrorFinder.luau b/Tests/NilInstanceErrorFinder.luau
new file mode 100644
index 0000000..bb993b8
--- /dev/null
+++ b/Tests/NilInstanceErrorFinder.luau
@@ -0,0 +1,158 @@
+local ClassList
+local GlobalSettings, GlobalBasicSettings = settings(), UserSettings()
+local service = setmetatable({}, {
+ __index = function(Self, Name)
+ local Service = game:GetService(Name) or GlobalSettings:GetService(Name) or GlobalBasicSettings:GetService(Name)
+ Self[Name] = Service
+ return Service
+ end,
+})
+service.HttpService.HttpEnabled = true
+local function ArrayToDictionary(Table, HybridMode)
+ local tmp = table.create(#Table)
+
+ if HybridMode == "adjust" then
+ for Some1, Some2 in Table do
+ if type(Some1) == "number" then
+ tmp[Some2] = 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
+ tmp[Key] = true
+ end
+ end
+
+ return tmp
+end
+
+local function Find(String, Pattern)
+ return string.find(String, Pattern, nil, true)
+end
+
+do
+ -- TODO: More @ https://github.com/Dekkonot/rbx-instance-serializer/blob/23f772f6f78af879a21faa9fea3e6c4f93d1cdee/src/API.lua#L19
+
+ local function FetchAPI()
+ local API_Dump_Url =
+ "https://raw.githubusercontent.com/MaximumADHD/Roblox-Client-Tracker/roblox/Mini-API-Dump.json"
+ local API_Dump = service.HttpService:GetAsync(API_Dump_Url, true)
+ local API_Classes = service.HttpService:JSONDecode(API_Dump).Classes
+
+ local classList = {}
+
+ for _index_0 = 1, #API_Classes do
+ local API_Class = API_Classes[_index_0]
+ local ClassMembers = API_Class.Members
+
+ local Class = {}
+
+ local ClassName = API_Class.Name
+
+ local ClassTags = API_Class.Tags
+
+ if ClassTags then
+ ClassTags = ArrayToDictionary(ClassTags)
+ end
+
+ -- ClassInfo.Name = ClassName
+ Class.Tags = ClassTags -- or {}
+ Class.Superclass = API_Class.Superclass
+
+ local ClassProperties = {}
+
+ for _index_1 = 1, #ClassMembers do
+ local Member = ClassMembers[_index_1]
+ if Member.MemberType == "Property" then
+ local PropertyName = Member.Name
+
+ -- ? We avoid this as some Instances like services may have this property locked and thus make file unable to open and it's not even used by Roblox anyways as Parent-Child relationship is done by embedding/nesting
+
+ local Serialization = Member.Serialization
+
+ if Serialization.CanLoad then
+ local Ignored = false
+ if not (Ignored and Ignored[PropertyName]) then
+ local Allowed = true
+
+ local MemberTags = Member.Tags
+
+ local Special
+
+ if MemberTags then
+ MemberTags = ArrayToDictionary(MemberTags)
+
+ Special = MemberTags.NotScriptable
+
+ if MemberTags.Deprecated then
+ Allowed = nil
+ end
+ end
+ if Allowed then
+ 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
+
+ ClassProperties[PropertyName] = Property
+ end
+ end
+ 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 throguh properties to remove this
+
+ return classList
+ end
+
+ local ok, result = pcall(FetchAPI)
+ if ok then
+ ClassList = result
+ else
+ warn(result)
+ return
+ end
+end
+local F = Instance.new("Folder")
+for Class in ClassList do
+ local o, r = pcall(Instance.new, Class)
+ local x = r
+ if not o then
+ r = game:FindFirstChild(Class, true)
+ if not r then
+ r = game:FindFirstChildWhichIsA(Class, true)
+ end
+ end
+ if r then
+ o, r = pcall(function(_, _2)
+ _.Parent = _2
+ end, r, F)
+ if not o then
+ if Find(r, "locked") or Find(r, "Cannot change") or Find(r, "Invalid parent for Service") then
+ continue
+ end
+ warn(Class, r)
+ end
+ elseif not (Find(x, "Unable to create an Instance") or Find(x, "The current thread cannot create")) then
+ print(Class, x)
+ end
+end
diff --git a/saveinstance.lua b/saveinstance.lua
index b54ee9d..5540d0b 100644
--- a/saveinstance.lua
+++ b/saveinstance.lua
@@ -1408,7 +1408,9 @@ local IgnoreSharedStrings = OPTIONS.IgnoreSharedStrings
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]]")
diff --git a/saveinstance.luau b/saveinstance.luau
index 91bf116..05cd56a 100644
--- a/saveinstance.luau
+++ b/saveinstance.luau
@@ -2,7 +2,6 @@
local Params = {
RepoURL = "https://raw.githubusercontent.com/luau/SomeHub/main/",
UMF = "UniversalMethodFinder",
- ROM = "RequireOnlineModule",
}
local finder, globalcontainer = loadstring(game:HttpGet(Params.RepoURL .. Params.UMF .. ".luau", true), Params.UMF)()
@@ -42,14 +41,17 @@ local service = setmetatable({}, {
end,
})
-local EscapesPattern = "[%z\1-\8\11-\12\14-\31\127-\191\194-\244<>\"'&]"
--- Order from: https://create.roblox.com/docs/en-us/ui/rich-text#escape-forms
+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)
local Escapes = {
- ["<"] = "<",
- [">"] = ">",
- ['"'] = """,
- ["'"] = "'",
- ["&"] = "&",
+ ["&"] = "&", -- 38
+ ["<"] = "<", -- 60
+ [">"] = ">", -- 62
+ ['"'] = """, -- 34
+ ["'"] = "'", -- 39
}
for rangeStart, rangeEnd in string.gmatch(EscapesPattern, "(.)%-(.)") do
@@ -160,7 +162,7 @@ Descriptors = {
return Value
end,
- __CDATA = function(raw)
+ __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 ""
end,
__ENUM = function(raw)
@@ -181,8 +183,8 @@ Descriptors = {
return Extreme
end,
- __PROTECTEDSTRING = function(raw)
- return Find(raw, "]]>") and Descriptors.string(raw) or Descriptors.__CDATA(raw) -- ? its purpose is to "protect" data from being treated as ordinary character data during processing
+ __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) or Descriptors.__CDATA(raw)
end,
__VECTOR = function(X, Y, Z)
local Value = "" .. X .. "" .. Y .. "" -- There is no Vector without at least two Coordinates.. (Vector1, at least on Roblox)
@@ -466,120 +468,15 @@ end
local getspecialinfo = globalcontainer.getspecialinfo
-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 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 true == CanRead then
- raw = instance[PropertyName]
- elseif false == CanRead then -- * Skips because we've checked this property before
- return "__BREAK", specialProperties
- end
- end
- return raw, specialProperties
-end
-
-local ldeccache = {}
-
local function ArrayToDictionary(Table, HybridMode)
local tmp = table.create(#Table)
- if HybridMode == "table" then
- for Some1, Some2 in Table do
- if type(Some1) == "number" then
- tmp[Some2] = true
- else
- tmp[Some1] = ArrayToDictionary(Some2, "table") -- Some1 is Class, Some2 is Name
- end
- end
- elseif HybridMode == "bool" then
+ if HybridMode == "adjust" then
for Some1, Some2 in Table do
if type(Some1) == "number" then
tmp[Some2] = true
+ elseif type(Some2) == "table" then
+ tmp[Some1] = ArrayToDictionary(Some2, "adjust") -- Some1 is Class, Some2 is Name
else
tmp[Some1] = Some2
end
@@ -592,99 +489,107 @@ local function ArrayToDictionary(Table, HybridMode)
return tmp
end
-local ClassPropertiesBlacklist =
- { GuiObject = { "Transparency" }, Instance = { "Parent" }, BasePart = { "BrickColor" }, Attachment = {"WorldCFrame"} } -- GuiObject.Transparency is almost always 1 meaning everything will be transparent; Instance.Parent is useless in xml (no idea about binary); BasePart.BrickColor hurts other Color3 properties; Attachment.WorldCFrame breaks Attachment.CFrame (and is hidden anyway)
-for Class, Properties in ClassPropertiesBlacklist do
- ClassPropertiesBlacklist[Class] = ArrayToDictionary(Properties)
-end
-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 API_Classes = service.HttpService:JSONDecode(API_Dump).Classes
+local ClassList
+do
+ -- TODO: More @ https://github.com/Dekkonot/rbx-instance-serializer/blob/23f772f6f78af879a21faa9fea3e6c4f93d1cdee/src/API.lua#L19
+ local IgnoreClassProperties = {
+ GuiObject = { "Transparency" },
+ Instance = { "Parent" }, -- ClassName isn't included because CanLoad & CanSave are false on it
+ BasePart = { "BrickColor" },
+ Attachment = { "WorldCFrame" },
+ } -- GuiObject.Transparency is almost always 1 meaning everything will be transparent; Instance.Parent is useless in xml (no idea about binary); BasePart.BrickColor hurts other Color3 properties; Attachment.WorldCFrame breaks Attachment.CFrame (and is hidden anyway)
- local ClassList = {}
+ for Class, Properties in IgnoreClassProperties do
+ IgnoreClassProperties[Class] = ArrayToDictionary(Properties)
+ end
+ 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 API_Classes = service.HttpService:JSONDecode(API_Dump).Classes
- for _index_0 = 1, #API_Classes do
- local API_Class = API_Classes[_index_0]
- local ClassMembers = API_Class.Members
+ local classList = {}
- local Class = {}
+ for _index_0 = 1, #API_Classes do
+ local API_Class = API_Classes[_index_0]
+ local ClassMembers = API_Class.Members
- local ClassName = API_Class.Name
+ local Class = {}
- local ClassTags = API_Class.Tags
+ local ClassName = API_Class.Name
- if ClassTags then
- ClassTags = ArrayToDictionary(ClassTags)
- end
+ local ClassTags = API_Class.Tags
- -- ClassInfo.Name = ClassName
- Class.Tags = ClassTags -- or {}
- Class.Superclass = API_Class.Superclass
+ if ClassTags then
+ ClassTags = ArrayToDictionary(ClassTags)
+ end
- local ClassProperties = {}
+ -- ClassInfo.Name = ClassName
+ Class.Tags = ClassTags -- or {}
+ Class.Superclass = API_Class.Superclass
- for _index_1 = 1, #ClassMembers do
- local Member = ClassMembers[_index_1]
- if Member.MemberType == "Property" then
- local PropertyName = Member.Name
+ local ClassProperties = {}
- -- ? We avoid this as some Instances like services may have this property locked and thus make file unable to open and it's not even used by Roblox anyways as Parent-Child relationship is done by embedding/nesting
+ for _index_1 = 1, #ClassMembers do
+ local Member = ClassMembers[_index_1]
+ if Member.MemberType == "Property" then
+ local PropertyName = Member.Name
- local Serialization = Member.Serialization
+ -- ? We avoid this as some Instances like services may have this property locked and thus make file unable to open and it's not even used by Roblox anyways as Parent-Child relationship is done by embedding/nesting
- if Serialization.CanLoad then
- local Blacklisted = ClassPropertiesBlacklist[ClassName]
- if not (Blacklisted and Blacklisted[PropertyName]) then
- local Allowed = true
+ local Serialization = Member.Serialization
- local MemberTags = Member.Tags
+ if Serialization.CanLoad then
+ local Ignored = IgnoreClassProperties[ClassName]
+ if not (Ignored and Ignored[PropertyName]) then
+ local Allowed = true
- local Special
+ local MemberTags = Member.Tags
- if MemberTags then
- MemberTags = ArrayToDictionary(MemberTags)
+ local Special
- Special = MemberTags.NotScriptable
+ if MemberTags then
+ MemberTags = ArrayToDictionary(MemberTags)
- if MemberTags.Deprecated then
- Allowed = nil
+ Special = MemberTags.NotScriptable
+
+ if MemberTags.Deprecated then
+ Allowed = nil
+ end
end
- end
- if Allowed then
- local ValueType = Member.ValueType
+ if Allowed then
+ local ValueType = Member.ValueType
- local Property = {
- Name = PropertyName,
- Category = ValueType.Category,
- Default = Member.Default,
- -- Tags = MemberTags,
- ValueType = ValueType.Name,
- }
+ local Property = {
+ Name = PropertyName,
+ Category = ValueType.Category,
+ Default = Member.Default,
+ -- Tags = MemberTags,
+ ValueType = ValueType.Name,
+ }
- if Special then
- Property.Special = true
+ if Special then
+ Property.Special = true
+ end
+
+ ClassProperties[PropertyName] = Property
end
-
- ClassProperties[PropertyName] = Property
end
end
end
end
+
+ Class.Properties = ClassProperties
+
+ classList[ClassName] = Class
end
- Class.Properties = ClassProperties
+ -- classList.Instance.Properties.Parent = nil -- ? Not sure if this is a better option than filtering throguh properties to remove this
- ClassList[ClassName] = Class
+ return classList
end
- -- ClassList.Instance.Properties.Parent = nil -- ? Not sure if this is a better option than filtering throguh properties to remove this
-
- return ClassList
-end
-local ClassList
-do
local ok, result = pcall(FetchAPI)
if ok then
ClassList = result
@@ -748,77 +653,55 @@ local referents = setmetatable({
end,
})
-local function ReturnItem(ClassName, instance)
- return '- ' -- TODO: Ideally this shouldn't return as well as the line below to close it IF IgnorePropertiesOfNotScriptsOnScriptsMode is ENABLED
-end
-local function ReturnProperty(Tag, PropertyName, Value)
- return "<" .. Tag .. ' name="' .. PropertyName .. '">' .. Value .. "" .. Tag .. ">"
-end
+local globalenv = getgenv and getgenv() or _G
-local function ApiFormatify(Value, Category, ValueType, Default)
- if Category == "Enum" then
- Value = Descriptors.__ENUMNAME(Value)
- elseif Category == "Primitive" then
- Value = Descriptors[ValueType](Value, Default)
+local StatusGui = globalenv.StatusGui
+if not StatusGui then
+ StatusGui = Instance.new("ScreenGui")
+ globalenv.StatusGui = StatusGui
+
+ StatusGui.DisplayOrder = 2_000_000_000
+ StatusGui.OnTopOfCoreBlur = true
+
+ local function randomString()
+ local randomarray = {}
+ for i = 1, math.random(10, 20) do
+ randomarray[i] = string.char(math.random(32, 126))
+ end
+ return table.concat(randomarray)
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 BlacklistedDefaults = {
- __api_dump_class_not_creatable__ = true,
- __api_dump_no_string_value__ = true,
- __api_dump_skipped_class__ = true,
- -- __api_dump_write_only_property__ = true, -- ? Is this needed
-}
-
-local StatusGui = Instance.new("ScreenGui")
-StatusGui.DisplayOrder = 2_000_000_000
-StatusGui.OnTopOfCoreBlur = true
-
-local function randomString()
- local randomarray = {}
- for i = 1, math.random(10, 20) 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 and not (is_sirhurt_closure or (syn and DrawingImmediate)) 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
+ if globalcontainer.gethui then
StatusGui.Name = randomString()
+ StatusGui.Parent = globalcontainer.gethui()
+ elseif globalcontainer.protectgui and not (is_sirhurt_closure or (syn and DrawingImmediate)) 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
+ local StatusText = Instance.new("TextLabel")
+ globalenv.StatusText = StatusText
+ 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.Text = "Starting..."
+ StatusText.TextColor3 = Color3.new(1, 1, 1)
+ StatusText.TextScaled = true
+ StatusText.TextStrokeTransparency = 0.7
+ StatusText.TextXAlignment = Enum.TextXAlignment.Right
+ StatusText.TextYAlignment = Enum.TextYAlignment.Top
end
-local StatusText = Instance.new("TextLabel")
-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.Text = "Starting..."
-StatusText.TextColor3 = Color3.new(1, 1, 1)
-StatusText.TextScaled = true
-StatusText.TextStrokeTransparency = 0.7
-StatusText.TextXAlignment = Enum.TextXAlignment.Right
-StatusText.TextYAlignment = Enum.TextYAlignment.Top
-
-local ScriptsClasses = ArrayToDictionary({ "LocalScript", "ModuleScript", Script = false }, "bool")
local function synsaveinstance(CustomOptions)
table.clear(SharedStrings)
@@ -826,7 +709,8 @@ local function synsaveinstance(CustomOptions)
local savebuffer = {}
local StatusTextClone
- local OPTIONS = {
+ local OPTIONS
+ OPTIONS = {
mode = "optimized", -- Change this to invalid mode like "custom" if you only want extrainstances; -- ! "optimized" mode is NOT supported with OPTIONS.Object option
noscripts = false,
scriptcache = true,
@@ -834,36 +718,47 @@ local function synsaveinstance(CustomOptions)
timeout = 30, -- Description: If the decompilation run time exceeds this value it gets cancelled.
--* New:
__DEBUG_MODE = false, -- Recommended to enable if you wish to help us improve our products and find bugs / issues with it!
+
--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.
- DecompileIgnore = { -- * Clean these up (merged Old Syn and New Syn)
- "Chat",
- "TextChatService",
- }, -- Scripts inside of these ClassNames will be saved but not decompiled
+
--[[ Explanation of structure for DecompileIgnore
- {
- "Chat", - This affects any descendants of instance with "Chat" ClassName
- Players = {"MyPlayerName"} - - This affects any descendants of instance with "Players" .Class AND "MyPlayerName" .Name ONLY
+ {
+ "Chat", - This ignores any instance with "Chat" ClassName
+ Players = {"MyPlayerName"} - This ignores any descendants of instance with "Players" Class AND "MyPlayerName" Name ONLY
+ workspace, - This ignores only the this specific instance & it's descendants (Workspace in our case)
}
]]
- PropertiesBlacklist = {},
- InstancesBlacklist = { "CoreGui", "CorePackages" },
- --[[ Explanation of structure for InstancesBlacklist
- {
- "CoreGui", - This affects any descendants of instance with "Chat" ClassName
- Players = {"MyPlayerName"} - - This affects any descendants of instance with "Players" .Class AND "MyPlayerName" .Name ONLY
+ DecompileIgnore = { -- * Clean these up (merged Old Syn and New Syn)
+ service.Chat,
+ service.TextChatService,
+ }, -- Scripts inside of these ClassNames will be saved but not decompiled
+
+ IgnoreProperties = {},
+ --[[ Explanation of structure for IgnoreList
+ {
+ "Chat", - This ignores any instance with "Chat" ClassName
+ Players = {"MyPlayerName"} - This ignores any descendants of instance with "Players" Class AND "MyPlayerName" Name ONLY
+ workspace, - This ignores only the this specific instance & it's descendants (Workspace in our case)
+ StarterPlayer = false, - This saves StarterPlayer without it's descendants
}
]]
+ IgnoreList = { service.CoreGui, service.CorePackages },
+
ExtraInstances = {},
NilInstances = false, -- Description: Save nil instances.
NilInstancesFixes = {
- Animator = function(instance)
- local AnimationController = Instance.new("AnimationController")
- AnimationController.Name = "Animator has to be placed under Humanoid or AnimationController"
- instance:Clone().Parent = AnimationController
- return AnimationController
- end,
+
+ -- Service = function(instance) end, -- ? Have yet to encounter a case where an Instance with Service Tag is deleted
},
+ ScriptsClasses = { "LocalScript", "ModuleScript", Script = false }, -- Please keep this updated; "= false" means it won't be decompiled
+ IgnoreDefaults = {
+ __api_dump_class_not_creatable__ = true,
+ __api_dump_no_string_value__ = true,
+ __api_dump_skipped_class__ = true,
+ -- __api_dump_write_only_property__ = true, -- ? Is this needed
+ },
+
ShowStatus = true,
FilePath = false, -- does not need to contain a file extension, only the name of the file.
Object = false, -- 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 like game.Workspace for example; "optimized" mode is NOT supported with this option
@@ -878,6 +773,9 @@ local function synsaveinstance(CustomOptions)
IsolateLocalPlayer = false, -- Saves Children of LocalPlayer as separate folder and prevents any instance of ClassName Player with .Name identical to LocalPlayer.Name from saving
IsolateLocalPlayerCharacter = false, -- Saves Children of LocalPlayer.Character as separate folder and prevents any instance of ClassName Player with .Name identical to LocalPlayer.Name from saving
-- 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, --Description: Includes already decompiled code in the output. No new scripts are decompiled.
+ SaveNonCreatable = false, --Description: 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)
RemovePlayerCharacters = true, -- Description: Ignore player characters while saving.
SavePlayers = false, -- 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
SaveCacheInterval = 0x1600, -- The less the more often it saves, but that would mean less performance due to constantly saving
@@ -888,68 +786,134 @@ local function synsaveinstance(CustomOptions)
AllowResettingProperties = false, -- 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
IgnoreSharedStrings = true, -- ! FIXES CRASHES (TEMPORARY, TESTED ON ROEXEC ONLY); FEEL FREE TO DISABLE THIS TO SEE IF IT WORKS FOR YOU
SharedStringOverwrite = false, -- ! 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)
+
+ OptionsAliases = {
+ FilePath = "FileName",
+ IgnoreDefaultProperties = "IgnoreDefaultProps",
+ SavePlayers = "IsolatePlayers",
+ scriptcache = "DecompileJobless",
+ timeout = "DecompileTimeout",
+ },
}
+ do
+ local function NilInstanceFixGeneral(Name, ClassName)
+ return function(instance, InstancePropertyOverrides)
+ local Exists = OPTIONS.NilInstancesFixes[Name]
- if type(CustomOptions) == "table" then
- for key, value in CustomOptions do
- if OPTIONS[key] ~= nil then
- OPTIONS[key] = value
- end
- end
- local Decompile = CustomOptions.Decompile
- if Decompile ~= nil then
- OPTIONS.noscripts = not Decompile
- end
- local DecompileTimeout = CustomOptions.DecompileTimeout
- if DecompileTimeout ~= nil then
- OPTIONS.timeout = DecompileTimeout
- end
- local IgnoreDefaultProps = CustomOptions.IgnoreDefaultProps
- if IgnoreDefaultProps ~= nil then
- OPTIONS.IgnoreDefaultProperties = IgnoreDefaultProps
- end
- else
- CustomOptions = {}
- end
- local Timeout = OPTIONS.timeout
- local ldecompile
+ local Fix
+ Exists = not Exists
- if OPTIONS.noscripts then
- ldecompile = function()
- return "-- Decompiling is disabled"
- end
- elseif decompile then
- ldecompile = function(Script)
- -- local name = scr.ClassName .. scr.Name
- do
- if OPTIONS.scriptcache then
- local Cached = ldeccache[Script]
- if Cached then
- return Cached
- end
+ if Exists then
+ Fix = Instance.new(ClassName)
+ OPTIONS.NilInstancesFixes[Name] = Fix
+ -- Fix.Name = Name
+
+ InstancePropertyOverrides[Fix] = { __Children = { instance }, Properties = { Name = Name } }
else
- rwait()
+ Fix = Exists
+ end
+
+ table.insert(InstancePropertyOverrides[Fix].__Children, instance)
+ -- InstancePropertyOverrides[instance].Parent = AnimationController
+ if Exists then
+ return Fix
end
end
- local ok, result = pcall(decompile, Script, Timeout, Timeout) -- ! This might break on Syn due to second param being bool or string (deprecated tho)
- ldeccache[Script] = result
- return ok and result or "--[[Failed to decompile\nReason:\n" .. (result or "") .. "\n]]"
end
- else
- ldecompile = function()
- return "-- Decompiling is NOT supported on your executor"
+
+ 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
+
+ local Type = typeof(CustomOptions)
+ if Type == "table" then
+ for key, value in CustomOptions do
+ if OPTIONS[key] == nil then
+ for Option, Alias in OPTIONS.OptionsAliases do
+ if key == Alias then
+ OPTIONS[Option] = value
+ break
+ end
+ 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
+ elseif Type == "Instance" and CustomOptions ~= game then
+ CustomOptions = { FilePath = CustomOptions }
+ else
+ CustomOptions = {}
end
end
+
+ local InstancePropertyOverrides = {}
+
+ local DecompileIgnore, IgnoreList, IgnoreProperties, ScriptsClasses =
+ ArrayToDictionary(OPTIONS.DecompileIgnore, "adjust"),
+ ArrayToDictionary(OPTIONS.IgnoreList, "adjust"),
+ ArrayToDictionary(OPTIONS.IgnoreProperties, "adjust"),
+ ArrayToDictionary(OPTIONS.ScriptsClasses, "adjust")
+
+ local __DEBUG_MODE = OPTIONS.__DEBUG_MODE
+
+ local FilePath = OPTIONS.FilePath
+ local SaveCacheInterval = OPTIONS.SaveCacheInterval
local ToSaveInstance = OPTIONS.Object
+ local IgnoreDefaultProperties = OPTIONS.IgnoreDefaultProperties
+ local IgnoreDefaults = OPTIONS.IgnoreDefaults
+ 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 SavePlayers = OPTIONS.SavePlayers
+
+ 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 ldeccache = globalenv.scriptcache
+
+ local DecompileIgnoring, ToSaveList, ldecompile, placename
+
if ToSaveInstance == game then
ToSaveInstance = nil
end
- local FilePath = OPTIONS.FilePath
- local IgnorePropertiesOfNotScriptsOnScriptsMode = OPTIONS.IgnorePropertiesOfNotScriptsOnScriptsMode
- local placename
- local ToSaveList
do
local mode = string.lower(OPTIONS.mode)
local tmp = table.clone(OPTIONS.ExtraInstances)
@@ -959,18 +923,19 @@ local function synsaveinstance(CustomOptions)
if mode == "optimized" then -- ! NOT supported with Model file mode
mode = "full"
end
-
+
for _, key in
{
"IsolateLocalPlayerCharacter",
"IsolateStarterPlayer",
+ "SavePlayers",
"IsolateLocalPlayer",
"NilInstances",
}
do
if not CustomOptions[key] then
- OPTIONS[key] = false
-end
+ OPTIONS[key] = false
+ end
end
placename = (FilePath or "model" .. PlaceId .. "_" .. ToSaveInstance:GetDebugId(0)) .. ".rbxmx" -- * GetDebugId is only unique per instance within same game session, after rejoining it might be different
@@ -993,10 +958,11 @@ end
"JointsService",
"Lighting",
"MaterialService",
+ "Players",
"ReplicatedFirst",
"ReplicatedStorage",
- "ServerStorage", -- ? Why
"ServerScriptService", -- ? Why
+ "ServerStorage", -- ? Why
"SoundService",
"StarterGui",
"StarterPack",
@@ -1005,9 +971,9 @@ end
"TextChatService",
"Workspace",
}
- if OPTIONS.SavePlayers then
- table.insert(_list_0, "Players")
- end
+ -- if SavePlayers then
+ -- table.insert(_list_0, "Players")
+ -- end
for _index_0 = 1, #_list_0 do
local x = _list_0[_index_0]
table.insert(tmp, service[x])
@@ -1036,24 +1002,167 @@ end
ToSaveList = tmp
end
- local DecompileIgnore, InstancesBlacklist, PropertiesBlacklist =
- ArrayToDictionary(OPTIONS.DecompileIgnore, "table"),
- ArrayToDictionary(OPTIONS.InstancesBlacklist, "table"),
- ArrayToDictionary(OPTIONS.PropertiesBlacklist, "table")
+ if ScriptCache and not ldeccache then
+ ldeccache = {}
+ globalenv.scriptcache = ldeccache
+ end
- local __DEBUG_MODE = OPTIONS.__DEBUG_MODE
- local AllowResettingProperties = OPTIONS.AllowResettingProperties
- local IgnoreDefaultProperties = OPTIONS.IgnoreDefaultProperties
- local IgnoreNotArchivable = OPTIONS.IgnoreNotArchivable
- local IgnoreSpecialProperties = OPTIONS.IgnoreSpecialProperties
- local IsolateLocalPlayer = OPTIONS.IsolateLocalPlayer
+ if OPTIONS.noscripts then
+ ldecompile = function()
+ return "-- Decompiling is disabled"
+ end
+ elseif decompile then
+ ldecompile = function(Script)
+ -- local name = scr.ClassName .. scr.Name
+ do
+ if ScriptCache then
+ local Cached = ldeccache[Script]
+ if Cached then
+ return Cached
+ elseif DecompileJobless then
+ return "-- Not found in already decompiled ScriptCache"
+ end
+ else
+ rwait()
+ end
+ end
+ local ok, result = pcall(decompile, Script, Timeout, Timeout) -- ! This might break on Syn due to second param being bool or string (deprecated tho)
+ ldeccache[Script] = result
+ return ok and result or "--[[Failed to decompile\nReason:\n" .. (result or "") .. "\n]]"
+ end
+ else
+ ldecompile = function()
+ return "-- Decompiling is NOT supported on your executor"
+ end
+ end
- local IsolateLocalPlayerCharacter = OPTIONS.IsolateLocalPlayerCharacter
- local IsolateStarterPlayer = OPTIONS.IsolateStarterPlayer
- local SaveCacheInterval = OPTIONS.SaveCacheInterval
+ local function getsafeproperty(instance, PropertyName)
+ return instance[PropertyName]
+ end
+ local function setsafeproperty(instance, PropertyName, Value)
+ instance[PropertyName] = Value
+ end
- local SharedStringOverwrite = OPTIONS.SharedStringOverwrite
- local IgnoreSharedStrings = OPTIONS.IgnoreSharedStrings
+ 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 true == CanRead then
+ raw = instance[PropertyName]
+ elseif false == CanRead then -- * Skips because we've checked this property before
+ return "__BREAK", specialProperties
+ end
+ end
+
+ return raw, specialProperties
+ end
+
+ local function ReturnItem(ClassName, instance)
+ return '
- ' -- TODO: Ideally this shouldn't return as well as the line below to close it IF IgnorePropertiesOfNotScriptsOnScriptsMode is ENABLED
+ end
+ local function ReturnProperty(Tag, PropertyName, Value)
+ return "<" .. Tag .. ' name="' .. PropertyName .. '">' .. Value .. "" .. Tag .. ">"
+ end
+
+ local function ApiFormatify(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 getsizeformat()
local Size
@@ -1084,200 +1193,267 @@ end
savebuffer = {}
rwait()
end
- local DecompileIgnoring
+
+ 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 ..= ""
+ return Item
+ end
+
local function savehierarchy(Hierarchy, Afterwards)
if SaveCacheInterval < #savebuffer then
savecache()
end
+
for _index_0 = 1, #Hierarchy do
local instance = Hierarchy[_index_0]
+ if IgnoreNotArchivable and not instance.Archivable then
+ continue
+ end
+ local SkipEntirely = IgnoreList[instance]
+ if SkipEntirely then
+ continue
+ end
+
local ClassName = instance.ClassName
+ if not ClassList[ClassName] then
+ continue
+ end
+
local InstanceName = instance.Name
- local Blacklisted = InstancesBlacklist[ClassName]
- if
- instance.RobloxLocked
- or not ClassList[ClassName]
- or IgnoreNotArchivable and not instance.Archivable
- or Blacklisted and (Blacklisted == true or Blacklisted[InstanceName])
- then
+ local OnIgnoredList = IgnoreList[ClassName]
+ if OnIgnoredList and (OnIgnoredList == true or OnIgnoredList[InstanceName]) then
continue
end
if not DecompileIgnoring then
- local DecompileIgnored = DecompileIgnore[ClassName]
- DecompileIgnoring = DecompileIgnored
- and (DecompileIgnored == true or DecompileIgnored[InstanceName])
- and instance
- end
+ DecompileIgnoring = DecompileIgnore[instance]
- local Properties = inheritedproperties[ClassName]
- savebuffer[#savebuffer + 1] = ReturnItem(ClassName, instance) -- TODO: Ideally this shouldn't return as well as the line below to close it IF IgnorePropertiesOfNotScriptsOnScriptsMode is ENABLED
- local Ignored
- if IgnorePropertiesOfNotScriptsOnScriptsMode and ScriptsClasses[ClassName] == nil then
- Ignored = true
- end
- if not Ignored then
- local specialProperties, Replica
- for _index_1 = 1, #Properties do
- local Property = Properties[_index_1]
- local PropertyName = Property.Name
+ if DecompileIgnoring == nil then
+ local DecompileIgnored = DecompileIgnore[ClassName]
+ DecompileIgnoring = DecompileIgnored
+ and (DecompileIgnored == true or DecompileIgnored[InstanceName])
+ end
- if PropertiesBlacklist[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
- continue
- 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 PropertyName ~= "Source" then -- ? Source is special, might need to be changed to check for LuaSourceContainer IsA instead
- 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 BlacklistedDefaults[Default] then
- local ClassTags = ClassList[ClassName].Tags
-
- local NotCreatable = ClassTags and ClassTags.NotCreatable
-
- 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 = ApiFormatify(Default, Category, ValueType)
- Property.Default = Default
- -- if Property.Special then
- -- end
- end
- elseif Default == "default" and ValueType == "PhysicalProperties" then
- Default = "nil"
- Property.Default = Default
- end
-
- if ApiFormatify(raw, Category, ValueType, Default) == Default then -- ! PhysicalProperties, Font, 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)
- -- print("Default not serializing", PropertyName)
-
- continue
- end
- end
-
- local tag, value
- if Category == "Class" then
- tag = "Ref"
- if raw then
- 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 (feel free to prove us 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 ScriptsClasses[ClassName] == false then
- value = "-- Server scripts can NOT be decompiled" --TODO: Could be not just server scripts in the future
- else
- if DecompileIgnoring then
- value = "-- Ignored"
- else
- value = ldecompile(instance)
- end
- end
- end
-
- value = Descriptors.__PROTECTEDSTRING(value)
- 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 + 1] = ReturnProperty(tag, PropertyName, value)
- elseif __DEBUG_MODE then
- warn("UNSUPPORTED TYPE (OPEN A GITHUB ISSUE): ", ValueType, ClassName, PropertyName)
- end
+ if DecompileIgnoring then
+ DecompileIgnoring = instance
end
end
- savebuffer[#savebuffer + 1] = ""
- local Children = Afterwards or instance:GetChildren()
- if #Children ~= 0 then
- savehierarchy(Children)
+ local InstanceOverride
+ if ClassName == "Player" or ClassName == "PlayerScripts" or ClassName == "PlayerGui" 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 = "Folder"
+ else
+ continue -- They won't show up in Studio anyway (Enable SavePlayers if you wish to bypass this)
+ end
+ end
+ if not InstanceOverride then
+ InstanceOverride = InstancePropertyOverrides[instance]
+ end
+ local ChildrenOverride = InstanceOverride and InstanceOverride.__Children
+
+ if ChildrenOverride then
+ savebuffer[#savebuffer + 1] = savespecific(ClassName, InstanceOverride.Properties) -- ! Assuming anything that has __Children will have .Properties
+ else
+ local Properties = inheritedproperties[ClassName]
+ savebuffer[#savebuffer + 1] = ReturnItem(ClassName, instance) -- TODO: Ideally this shouldn't return as well as the line below to close it IF IgnorePropertiesOfNotScriptsOnScriptsMode is ENABLED
+
+ if not (IgnorePropertiesOfNotScriptsOnScriptsMode and ScriptsClasses[ClassName] == nil) then
+ local specialProperties, Replica
+ for _index_1 = 1, #Properties do
+ local Property = Properties[_index_1]
+ 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
+ continue
+ 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 PropertyName ~= "Source" then -- ? Source is special, might need to be changed to check for LuaSourceContainer IsA instead
+ 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
+
+ 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 = ApiFormatify(Default, Category, ValueType)
+ Property.Default = Default
+ -- if Property.Special then
+ -- end
+ end
+ elseif Default == "default" and ValueType == "PhysicalProperties" then
+ Default = "nil"
+ Property.Default = Default
+ end
+
+ if ApiFormatify(raw, Category, ValueType, Default) == Default then -- ! PhysicalProperties, Font, 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)
+ -- print("Default not serializing", PropertyName)
+
+ continue
+ end
+ end
+
+ local tag, value
+ if Category == "Class" then
+ tag = "Ref"
+ if raw then
+ 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 (feel free to prove us 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 ScriptsClasses[ClassName] == false then
+ value = "-- Server scripts can NOT be decompiled" --TODO: Could be not just server scripts in the future
+ else
+ if DecompileIgnoring then
+ value = "-- Ignored"
+ else
+ value = ldecompile(instance)
+ end
+ end
+ end
+
+ value = Descriptors.__PROTECTEDSTRING(value)
+ 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 + 1] = ReturnProperty(tag, PropertyName, value)
+ elseif __DEBUG_MODE then
+ warn("UNSUPPORTED TYPE (OPEN A GITHUB ISSUE): ", ValueType, ClassName, PropertyName)
+ end
+ end
+ end
+ savebuffer[#savebuffer + 1] = ""
+ 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
@@ -1287,22 +1463,9 @@ end
savebuffer[#savebuffer + 1] = "
"
end
end
+
local function saveextra(Name, Hierarchy, CustomClassName, Source)
- local Ref = Instance.new(CustomClassName or "Folder")
-
- local PropertyName = "Name"
- local ValueType = ClassList.Instance.Properties[PropertyName].ValueType
- local value, tag = ReturnValueAndTag(Name, ValueType)
-
- local Buffer = ReturnItem(Ref.ClassName, Ref) .. ReturnProperty(tag, PropertyName, value)
-
- if Source then
- Buffer ..= ReturnProperty("ProtectedString", "Source", Descriptors.__PROTECTEDSTRING(Source))
- end
-
- Buffer ..= ""
-
- table.insert(savebuffer, Buffer)
+ table.insert(savebuffer, savespecific((CustomClassName or "Folder"), { Name = Name, Source = Source }))
if Hierarchy then
savehierarchy(Hierarchy)
end
@@ -1310,41 +1473,6 @@ end
end
local function savegame()
- local nilinstances
- if OPTIONS.NilInstances and globalcontainer.getnilinstances then
- local tmp = {}
-
- local NilInstancesFixes = OPTIONS.NilInstancesFixes
-
- for _, instance in globalcontainer.getnilinstances() do
- if instance == game then
- instance = nil
- -- break
- else
- local ClassName = instance.ClassName
-
- local Fix = NilInstancesFixes[ClassName]
- if Fix then -- *
- instance = Fix(instance)
- -- continue
- end
-
- local Class = ClassList[ClassName]
- if Class then
- local ClassTags = Class.Tags
- if ClassTags and ClassTags.Service then
- instance.Parent = game
- instance = nil
- -- continue
- end
- end
- end
- if instance then
- table.insert(tmp, instance)
- end
- end
- nilinstances = tmp
- end
local Starter = ''
if ToSaveInstance then
Starter ..= 'true'
@@ -1365,6 +1493,7 @@ end
local Players = service.Players
local LocalPlayer = Players.LocalPlayer
if IsolateLocalPlayer then
+ SaveNonCreatable = true
saveextra("LocalPlayer", LocalPlayer:GetChildren())
end
if IsolateLocalPlayerCharacter then
@@ -1374,13 +1503,62 @@ end
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 nilinstances then
+ if SavePlayers 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 = NilInstancesFixes[ClassName]
+ if not Fix then -- ! This can cause some Classes to be fixed even though they might not need the fix (better be safe than sorry though); Like Bones inherit from Attachment if we dont define them in the NilInstancesFixes then this will catch them anyways
+ for className, fix in NilInstancesFixes do
+ if instance:IsA(className) then
+ Fix = fix
+ break
+ end
+ end
+ end
+ if Fix then -- *
+ instance = Fix(instance, InstancePropertyOverrides)
+ -- continue
+ end
+
+ -- ? Have yet to encounter a case where an Instance with Service Tag is deleted
+ -- local Class = ClassList[ClassName]
+ -- if Class then
+ -- local ClassTags = Class.Tags
+ -- if ClassTags and ClassTags.Service then
+ -- 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
saveextra("README", nil, "Script", "--[[\n" .. [[
Thank you for using SynSaveInstance Revival.
@@ -1401,11 +1579,14 @@ end
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]]")
end
+
local tmp = { "" }
for Identifier, Value in SharedStrings do
tmp[#tmp + 1] = '' .. Value .. ""
@@ -1425,47 +1606,47 @@ end
if Exists then
Exists:Destroy()
end
- StatusTextClone = StatusText:Clone()
+ StatusTextClone = globalenv.StatusText:Clone()
StatusTextClone.Parent = StatusGui
end
- if not OPTIONS.SavePlayers and not InstancesBlacklist.Players then
- InstancesBlacklist.Players = {}
- end
- if OPTIONS.RemovePlayerCharacters then
- local Players = service.Players
- local T = InstancesBlacklist.Model
- if T ~= true then
- if not T then
- T = {}
- end
- for _, Player in Players:GetPlayers() do
- T[Player.Name] = true
- end
- InstancesBlacklist.Model = T
- end
- end
- if IsolateStarterPlayer then
- InstancesBlacklist.StarterPlayer = true
- end
- if IsolateLocalPlayer or IsolateLocalPlayerCharacter then
+ do
local Players = service.Players
local LocalPlayer = Players.LocalPlayer
- local LocalPlayerName = LocalPlayer.Name
- if IsolateLocalPlayer and InstancesBlacklist.Player ~= true then
- if not InstancesBlacklist.Player then
- InstancesBlacklist.Player = {}
- end
- InstancesBlacklist.Player[LocalPlayerName] = true
- end
- if IsolateLocalPlayerCharacter and InstancesBlacklist.Model ~= true then
- if not InstancesBlacklist.Model then
- InstancesBlacklist.Model = {}
- end
+ local function IgnoreCharacter(Player)
+ Player.CharacterAdded:Connect(function(Character)
+ IgnoreList[Character] = true
+ end)
- InstancesBlacklist.Model[LocalPlayerName] = true
+ local Character = Player.Character
+ if Character then
+ IgnoreList[Character] = true
+ end
end
+
+ if IgnoreList.Model ~= true then
+ if OPTIONS.RemovePlayerCharacters then
+ for _, Player in Players:GetPlayers() do
+ IgnoreCharacter(Player)
+ end
+ else
+ 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 SavePlayers then
+ IgnoreList.Players = false
end
do
@@ -1478,18 +1659,20 @@ end
local Log10 = math.log10(elapse_t)
if StatusTextClone then
- local ExtraTime = 10
- if ok then
- StatusTextClone.Text = string.format("Saved! Time %.2f seconds; Size %s", elapse_t, getsizeformat())
- task.wait(Log10 * 2 + ExtraTime)
- else
- StatusTextClone.Text = "Failed! Check F9 console for more info"
- warn("Error found while saving")
- warn("Information about error:")
- warn(err)
- task.wait(Log10 + ExtraTime)
- end
- StatusTextClone:Destroy()
+ task.spawn(function()
+ local ExtraTime = 10
+ if ok then
+ StatusTextClone.Text = string.format("Saved! Time %.2f seconds; Size %s", elapse_t, getsizeformat())
+ task.wait(Log10 * 2 + ExtraTime)
+ else
+ StatusTextClone.Text = "Failed! Check F9 console for more info"
+ warn("Error found while saving")
+ warn("Information about error:")
+ warn(err)
+ task.wait(Log10 + ExtraTime)
+ end
+ StatusTextClone:Destroy()
+ end)
end
end
end