NoTextForSpeech

saveinstance

Dec 7th, 2025 (edited)
61
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 134.13 KB | None | 0 0
  1. --!native
  2. --!optimize 2
  3. --!divine-intellect
  4. -- https://discord.gg/wx4ThpAsmw
  5.  
  6. local konstantdecompiler = loadstring(game:HttpGet('https://raw.githubusercontent.com/verysigmapro/UniversalSynSaveInstance-With-Save-Terrain/refs/heads/main/konstantdecompiler.lua'))()
  7.  
  8. local game = game
  9. local pcall = pcall
  10. local workspace = workspace
  11. local setmetatable = setmetatable
  12. local bit32 = bit32
  13. local buffer = buffer
  14. local Instance = Instance
  15. local assert = assert
  16. local table = table
  17. local string = string
  18.  
  19. local function string_find(s, pattern)
  20. return string.find(s, pattern, nil, true)
  21. end
  22.  
  23. local function ArrayToDict(t, hydridMode, valueOverride, typeStrict)
  24. local tmp = {}
  25.  
  26. if hydridMode then
  27. for any1, any2 in t do
  28. if type(any1) == "number" then
  29. tmp[any2] = valueOverride or true
  30. elseif type(any2) == "table" then
  31. tmp[any1] = ArrayToDict(any2, hydridMode) -- any1 is Class, any2 is Name
  32. else
  33. tmp[any1] = any2
  34. end
  35. end
  36. else
  37. for _, key in t do
  38. if not typeStrict or typeStrict and type(key) == typeStrict then
  39. tmp[key] = true
  40. end
  41. end
  42. end
  43.  
  44. return tmp
  45. end
  46.  
  47. local global_container
  48. do
  49. local filename = "UniversalMethodFinder"
  50.  
  51. local finder
  52. finder, global_container = loadstring(
  53. game:HttpGet("https://raw.githubusercontent.com/luau/SomeHub/main/" .. filename .. ".luau", true),
  54. filename
  55. )()
  56.  
  57. finder({
  58. -- readbinarystring = 'string.find(...,"bin",nil,true)', -- ! Could match some unwanted stuff (getbinaryindex)
  59. -- request = 'string.find(...,"request",nil,true) and not string.find(...,"internal",nil,true)',
  60. 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"))',
  61. -- cloneref = 'string.find(...,"clone",nil,true) and string.find(...,"ref",nil,true)',
  62. -- decompile = '(string.find(...,"decomp",nil,true) and string.sub(...,#...) ~= "s")',
  63. gethiddenproperty = 'string.find(...,"get",nil,true) and string.find(...,"h",nil,true) and string.find(...,"prop",nil,true) and string.sub(...,#...) ~= "s"',
  64. gethui = 'string.find(...,"get",nil,true) and string.find(...,"h",nil,true) and string.find(...,"ui",nil,true)',
  65. -- getcon = 'string.find(...,"get",nil,true) and (string.find(...,"conn",nil,true) or string.find(...,"sig",nil,true)) and string.sub(...,#(...))=="s"',
  66. getnilinstances = 'string.find(...,"nil",nil,true) and string.find(...,"get",nil,true) and string.sub(...,#...) == "s"', -- ! Could match some unwanted stuff
  67. getscriptbytecode = 'string.find(...,"get",nil,true) and string.find(...,"bytecode",nil,true)', -- or string.find(...,"dump",nil,true) and string.find(...,"string",nil,true) due to Fluxus (dumpstring returns a function)
  68. -- 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")',
  69. protectgui = 'string.find(...,"protect",nil,true) and string.find(...,"ui",nil,true) and not string.find(...,"un",nil,true)',
  70. setthreadidentity = 'string.find(...,"identity",nil,true) and string.find(...,"set",nil,true)',
  71. }, true, 10)
  72. end
  73.  
  74. local identify_executor = identifyexecutor or getexecutorname or whatexecutor
  75.  
  76. local EXECUTOR_NAME = identify_executor and identify_executor() or ""
  77.  
  78. -- local cloneref = global_container.cloneref
  79. local gethiddenproperty = global_container.gethiddenproperty
  80.  
  81. -- These should be universal enough
  82. local appendfile = appendfile
  83. local isfile = isfile
  84. local readfile = readfile
  85. local writefile = writefile
  86.  
  87. local getscriptbytecode = global_container.getscriptbytecode -- * A lot of assumptions are made based on whether this function is defined or not. So in certain edge cases, like if the executor defines "decompile" or "getscripthash" function yet doesn't define this function there might be loss of functionality of the saveinstance. Although that would be very rare and weird
  88. local base64encode = global_container.base64encode
  89.  
  90. local service = setmetatable({}, {
  91. __index = function(self, serviceName)
  92. local o, s = pcall(Instance.new, serviceName)
  93. local Service = o and s
  94. or game:GetService(serviceName)
  95. or settings():GetService(serviceName)
  96. or UserSettings():GetService(serviceName)
  97.  
  98. -- if cloneref then
  99. -- Service = cloneref(Service)
  100. -- end
  101. if Service then
  102. self[serviceName] = Service
  103. end
  104. return Service
  105. end,
  106. })
  107.  
  108. local gethiddenproperty_fallback
  109. do -- * Load Region of Déjà Vu
  110. local UGCValidationService -- = service.UGCValidationService
  111.  
  112. gethiddenproperty_fallback = function(instance, propertyName)
  113. if not UGCValidationService then
  114. UGCValidationService = service.UGCValidationService
  115. end
  116. return UGCValidationService:GetPropertyValue(instance, propertyName) -- TODO Sadly there's no way to tell whether value is actually nil or the function just couldn't read it (always returns nil for "Class" category properties)
  117. -- TODO `category ~= "Class"` causes WeldConstraint Part1Internal to be read as nil and not get unfiltered. Currently, there are no properties of category "Class" that match the following: NotScriptable, can be read with gethiddenproperty_fallback accurately (it always outputs nil for "Class" category, making that check useless anyway) & don't have a NotScriptableFix.
  118. end
  119. if gethiddenproperty then
  120. local o, r = pcall(gethiddenproperty, workspace, "StreamOutBehavior")
  121. if not o or r ~= nil and typeof(r) ~= "EnumItem" then -- * Tests if gethiddenproperty is broken
  122. gethiddenproperty = nil
  123. else
  124. o, r = pcall(gethiddenproperty, Instance.new("AnimationRigData", Instance.new("Folder")), "parent") -- * Tests how it reacts to property overlap (shadowing) due to AnimationRigData.parent; expected BinaryString
  125.  
  126. if o and r ~= nil and type(r) ~= "string" then
  127. gethiddenproperty = nil
  128. end
  129. end
  130. end
  131. local function benchmark(funcs, ...)
  132. local ranking = table.create(2)
  133. for i, f in funcs do
  134. local start = os.clock()
  135. for _ = 1, 50 do
  136. f(...)
  137. end
  138. ranking[i] = { t = os.clock() - start, f = f }
  139. end
  140. table.sort(ranking, function(a, b)
  141. return a.t < b.t
  142. end)
  143. return ranking[1].f
  144. end
  145.  
  146. local test_str = string.rep("\1\0\0\0\1\2\3\4\5\6\7", 50)
  147.  
  148. do
  149. if not bit32.byteswap or not pcall(bit32.byteswap, 1) then -- Because Fluxus is missing byteswap
  150. bit32 = table.clone(bit32)
  151.  
  152. local function tobit(num)
  153. num %= (bit32.bxor(num, 32))
  154. if 0x80000000 < num then
  155. num -= bit32.bxor(num, 32)
  156. end
  157. return num
  158. end
  159.  
  160. bit32.byteswap = function(num)
  161. local BYTE_SIZE = 8
  162. local MAX_BYTE_VALUE = 255
  163.  
  164. num %= bit32.bxor(2, 32)
  165.  
  166. local a = bit32.band(num, MAX_BYTE_VALUE)
  167. num = bit32.rshift(num, BYTE_SIZE)
  168.  
  169. local b = bit32.band(num, MAX_BYTE_VALUE)
  170. num = bit32.rshift(num, BYTE_SIZE)
  171.  
  172. local c = bit32.band(num, MAX_BYTE_VALUE)
  173. num = bit32.rshift(num, BYTE_SIZE)
  174.  
  175. local d = bit32.band(num, MAX_BYTE_VALUE)
  176. num = tobit(bit32.lshift(bit32.lshift(bit32.lshift(a, BYTE_SIZE) + b, BYTE_SIZE) + c, BYTE_SIZE) + d)
  177. return num
  178. end
  179.  
  180. table.freeze(bit32)
  181. end
  182.  
  183. -- Credits @daily3014 & @XoifaiI
  184. local rbxcrypt_base64encode
  185. pcall(function()
  186. local b64_enc_buf = loadstring(
  187. game:HttpGet(
  188. "https://raw.githubusercontent.com/verysigmapro/UniversalSynSaveInstance-With-Save-Terrain/refs/heads/main/daily3014_Base64.luau",
  189. true
  190. ),
  191. "Base64"
  192. )().Encode
  193. rbxcrypt_base64encode = function(raw)
  194. return buffer.tostring(b64_enc_buf(buffer.fromstring(raw)))
  195. end
  196. end)
  197.  
  198. local EncodingService = game:GetService("EncodingService")
  199. local EncodingService_base64encode = function(raw)
  200. return buffer.tostring(EncodingService:Base64Encode(buffer.fromstring(raw)))
  201. end
  202.  
  203. -- * Tests if base64encode exists and works properly then benchmark it
  204. if base64encode and base64encode("\1\0\0\0\1") == "AQAAAAE=" then
  205. if rbxcrypt_base64encode then
  206. base64encode =
  207. benchmark({ base64encode, rbxcrypt_base64encode, EncodingService_base64encode }, test_str)
  208. end
  209. else
  210. base64encode = rbxcrypt_base64encode
  211. end
  212.  
  213. assert(base64encode, "base64encode not found")
  214. end
  215. end
  216.  
  217. local SharedStrings = {}
  218. local SharedString_identifiers = setmetatable({
  219. 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
  220. -- TODO: worst case, add fallback to str randomizer once numbers run out : )
  221. }, {
  222. __index = function(self, str)
  223. local Identifier = base64encode(tostring(self.identifier)) -- tostring is only needed for built-in base64encode, Luau base64 implementations don't need it as buffers autoconvert
  224. self.identifier += 1
  225.  
  226. self[str] = Identifier -- ? 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..
  227. return Identifier
  228. end,
  229. })
  230.  
  231. local inherited_properties = {}
  232. local default_instances = {}
  233. local referents, ref_size = {}, 0 -- ? 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.
  234.  
  235. local function GetRef(instance)
  236. local ref = referents[instance]
  237. if not ref then
  238. ref = ref_size
  239. referents[instance] = ref
  240. ref_size += 1
  241. end
  242. return ref
  243. end
  244.  
  245. local function index(self, index_name)
  246. return self[index_name]
  247. end
  248.  
  249. local version = version
  250. if not version then
  251. version = function()
  252. return "UNKNOWN"
  253. end
  254. end
  255.  
  256. local CLIENT_VERSION = tonumber(string.split(version(), ".")[2]) or 9e9 -- Velocity temp fix
  257.  
  258. local attr_Type_IDs = {
  259. string = 0x02,
  260. boolean = 0x03,
  261. int32 = 0x04,
  262. -- float = 0x05, -- float32
  263. number = 0x06, -- float64 (double)
  264. -- Array = 0x07,
  265. -- Dictionary = 0x08,
  266. UDim = 0x09,
  267. UDim2 = 0x0A,
  268. Ray = 0x0B,
  269. Faces = 0x0C,
  270. Axes = 0x0D,
  271. BrickColor = 0x0E,
  272. Color3 = 0x0F,
  273. Vector2 = 0x10,
  274. Vector3 = 0x11,
  275. Vector2int16 = 0x12,
  276. Vector3int16 = 0x13,
  277. CFrame = 0x14,
  278. EnumItem = 0x15,
  279. NumberSequence = 0x17,
  280. NumberSequenceKeypoint = 0x18,
  281. ColorSequence = 0x19,
  282. ColorSequenceKeypoint = 0x1A,
  283. NumberRange = 0x1B,
  284. Rect = 0x1C,
  285. PhysicalProperties = 0x1D,
  286. Region3 = 0x1F,
  287. Region3int16 = 0x20,
  288. Font = 0x21,
  289. SecurityCapabilities = 0x22,
  290. Path2DControlPoint = 0x23,
  291. }
  292. local CFrame_Rotation_IDs = {
  293. ["\0\0\128\63\0\0\0\0\0\0\0\0\0\0\0\0\0\0\128\63\0\0\0\0\0\0\0\0\0\0\0\0\0\0\128\63"] = 0x02,
  294. ["\0\0\128\63\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\128\191\0\0\0\0\0\0\128\63\0\0\0\0"] = 0x03,
  295. ["\0\0\128\63\0\0\0\0\0\0\0\0\0\0\0\0\0\0\128\191\0\0\0\0\0\0\0\0\0\0\0\0\0\0\128\191"] = 0x05,
  296. ["\0\0\128\63\0\0\0\0\0\0\0\128\0\0\0\0\0\0\0\0\0\0\128\63\0\0\0\0\0\0\128\191\0\0\0\0"] = 0x06,
  297. ["\0\0\0\0\0\0\128\63\0\0\0\0\0\0\128\63\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\128\191"] = 0x07,
  298. ["\0\0\0\0\0\0\0\0\0\0\128\63\0\0\128\63\0\0\0\0\0\0\0\0\0\0\0\0\0\0\128\63\0\0\0\0"] = 0x09,
  299. ["\0\0\0\0\0\0\128\191\0\0\0\0\0\0\128\63\0\0\0\0\0\0\0\128\0\0\0\0\0\0\0\0\0\0\128\63"] = 0x0a,
  300. ["\0\0\0\0\0\0\0\0\0\0\128\191\0\0\128\63\0\0\0\0\0\0\0\0\0\0\0\0\0\0\128\191\0\0\0\0"] = 0x0c,
  301. ["\0\0\0\0\0\0\128\63\0\0\0\0\0\0\0\0\0\0\0\0\0\0\128\63\0\0\128\63\0\0\0\0\0\0\0\0"] = 0x0d,
  302. ["\0\0\0\0\0\0\0\0\0\0\128\191\0\0\0\0\0\0\128\63\0\0\0\0\0\0\128\63\0\0\0\0\0\0\0\0"] = 0x0e,
  303. ["\0\0\0\0\0\0\128\191\0\0\0\0\0\0\0\0\0\0\0\0\0\0\128\191\0\0\128\63\0\0\0\0\0\0\0\0"] = 0x10,
  304. ["\0\0\0\0\0\0\0\0\0\0\128\63\0\0\0\0\0\0\128\191\0\0\0\0\0\0\128\63\0\0\0\0\0\0\0\128"] = 0x11,
  305. ["\0\0\128\191\0\0\0\0\0\0\0\0\0\0\0\0\0\0\128\63\0\0\0\0\0\0\0\0\0\0\0\0\0\0\128\191"] = 0x14,
  306. ["\0\0\128\191\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\128\63\0\0\0\0\0\0\128\63\0\0\0\128"] = 0x15,
  307. ["\0\0\128\191\0\0\0\0\0\0\0\0\0\0\0\0\0\0\128\191\0\0\0\0\0\0\0\0\0\0\0\0\0\0\128\63"] = 0x17,
  308. ["\0\0\128\191\0\0\0\0\0\0\0\128\0\0\0\0\0\0\0\0\0\0\128\191\0\0\0\0\0\0\128\191\0\0\0\128"] = 0x18,
  309. ["\0\0\0\0\0\0\128\63\0\0\0\128\0\0\128\191\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\128\63"] = 0x19,
  310. ["\0\0\0\0\0\0\0\0\0\0\128\191\0\0\128\191\0\0\0\0\0\0\0\0\0\0\0\0\0\0\128\63\0\0\0\0"] = 0x1b,
  311. ["\0\0\0\0\0\0\128\191\0\0\0\128\0\0\128\191\0\0\0\0\0\0\0\128\0\0\0\0\0\0\0\0\0\0\128\191"] = 0x1c,
  312. ["\0\0\0\0\0\0\0\0\0\0\128\63\0\0\128\191\0\0\0\0\0\0\0\0\0\0\0\0\0\0\128\191\0\0\0\0"] = 0x1e,
  313. ["\0\0\0\0\0\0\128\63\0\0\0\0\0\0\0\0\0\0\0\0\0\0\128\191\0\0\128\191\0\0\0\0\0\0\0\0"] = 0x1f,
  314. ["\0\0\0\0\0\0\0\0\0\0\128\63\0\0\0\0\0\0\128\63\0\0\0\128\0\0\128\191\0\0\0\0\0\0\0\0"] = 0x20,
  315. ["\0\0\0\0\0\0\128\191\0\0\0\0\0\0\0\0\0\0\0\0\0\0\128\63\0\0\128\191\0\0\0\0\0\0\0\0"] = 0x22,
  316. ["\0\0\0\0\0\0\0\0\0\0\128\191\0\0\0\0\0\0\128\191\0\0\0\128\0\0\128\191\0\0\0\0\0\0\0\128"] = 0x23,
  317. }
  318. local BASE_CAPABILITIES
  319. pcall(function()
  320. BASE_CAPABILITIES = SecurityCapabilities.new()
  321. end)
  322. local CAPABILITY_BITS = {
  323. Plugin = 2 ^ 0, ---------------- 0
  324. LocalUser = 2 ^ 1, ------------- 1
  325. WritePlayer = 2 ^ 2, ----------- 2
  326. RobloxScript = 2 ^ 3, ---------- 3
  327. RobloxEngine = 2 ^ 4, ---------- 4
  328. NotAccessible = 2 ^ 5, --------- 5
  329. RunClientScript = 2 ^ 8, ------- 8
  330. RunServerScript = 2 ^ 9, ------- 9
  331. Unknown = 2 ^ 10, -------------- 10 (0xa)
  332. AccessOutsideWrite = 2 ^ 11, --- 11 (0xb)
  333. Unassigned = 2 ^ 15, ----------- 15 (0xf)
  334. AssetRequire = 2 ^ 16, --------- 16 (0x10)
  335. LoadString = 2 ^ 17, ----------- 17 (0x11)
  336. ScriptGlobals = 2 ^ 18, -------- 18 (0x12)
  337. CreateInstances = 2 ^ 19, ------ 19 (0x13)
  338. Basic = 2 ^ 20, ---------------- 20 (0x14)
  339. Audio = 2 ^ 21, ---------------- 21 (0x15)
  340. DataStore = 2 ^ 22, ------------ 22 (0x16)
  341. Network = 2 ^ 23, -------------- 23 (0x17)
  342. Physics = 2 ^ 24, -------------- 24 (0x18)
  343. UI = 2 ^ 25, ------------------- 25 (0x19)
  344. CSG = 2 ^ 26, ------------------ 26 (0x1a)
  345. Chat = 2 ^ 27, ----------------- 27 (0x1b)
  346. Animation = 2 ^ 28, ------------ 28 (0x1c)
  347. Avatar = 2 ^ 29, --------------- 29 (0x1d)
  348. Input = 2 ^ 30, ---------------- 30 (0x1e)
  349. Environment = 2 ^ 31, ---------- 31 (0x1f)
  350. RemoteEvent = 2 ^ 32, ---------- 32 (0x20)
  351. LegacySound = 2 ^ 33, ---------- 33 (0x21)
  352. Players = 2 ^ 34, -------------- 34 (0x22)
  353. CapabilityControl = 2 ^ 35, ---- 35 (0x23)
  354. RemoteCommand = 2 ^ 59, -------- 59 (0x3b)
  355. InternalTest = 2 ^ 60, --------- 60 (0x3c), Related to TestingGameScript
  356. PluginOrOpenCloud = 2 ^ 61, ---- 61 (0x3d)
  357. Assistant = 2 ^ 62, ------------ 62 (0x3e)
  358. -- Restricted = 2 ^ 63, -------- for negative (highest bit for i64)
  359. }
  360.  
  361. local function __COUNT_CAPABILITY_BITS(raw)
  362. -- TODO tostring & string.split aren't ideal but this is the only way until the feature is out of the experimental phase (SecurityCapabilities.Contains exists but the Enums that it accepts lacks some hidden bits) - NotAccessible, Unknown, Restricted
  363. -- ! Seems like both tostring & .Contains ignore high / internal bits (anything above CapabilityControl): RemoteCommand, InternalTest, PluginOrOpenCloud, Assistant. They're present when created & saved by Studio but can't be read through current means
  364.  
  365. local result = 0
  366. for _, flag in string.split(tostring(raw), " | ") do
  367. local bit = CAPABILITY_BITS[flag]
  368. if bit then
  369. result += bit
  370. end
  371. end
  372. return result
  373. end
  374.  
  375. local function __COUNT_BITS(...) -- * Credits to Friend (you know yourself)
  376. local Value = 0
  377.  
  378. for i, bit in { ... } do
  379. if bit then
  380. Value += 2 ^ (i - 1)
  381. end
  382. end
  383.  
  384. return Value
  385. end
  386.  
  387. local Binary_Descriptors
  388. Binary_Descriptors = {
  389. __PACK_MULTIPLE = function(descriptor, value1, value2, value3)
  390. local buf1, size1 = descriptor(value1)
  391. local buf2, size2 = descriptor(value2)
  392.  
  393. local len = size1 + size2
  394. local buf3, size3
  395.  
  396. if value3 ~= nil then
  397. buf3, size3 = descriptor(value3)
  398. len += size3
  399. end
  400.  
  401. local b = buffer.create(len)
  402.  
  403. buffer.copy(b, 0, buf1)
  404. buffer.copy(b, size1, buf2)
  405.  
  406. if value3 ~= nil then
  407. buffer.copy(b, size1 + size2, buf3)
  408. end
  409.  
  410. return b, len
  411. end,
  412. __construct_Sequence = function(keypoint_handler, keypointSize)
  413. return function(raw)
  414. local Keypoints = raw.Keypoints
  415. local Keypoints_n = #Keypoints
  416.  
  417. local len = 4 + keypointSize * Keypoints_n
  418. local b = buffer.create(len)
  419.  
  420. buffer.writeu32(b, 0, Keypoints_n)
  421.  
  422. local offset = 4
  423. for _, keypoint in Keypoints do
  424. keypoint_handler(keypoint, b, offset)
  425. offset += keypointSize
  426. end
  427.  
  428. return b, len
  429. end
  430. end,
  431. __writei64 = function(b, offset, raw)
  432. local low = bit32.band(raw, 0xFFFFFFFF)
  433. local high = (raw - low) / 0x100000000
  434.  
  435. buffer.writei32(b, offset, low)
  436. buffer.writei32(b, offset + 4, high)
  437. end,
  438. __PACK_F32 = nil,
  439. __PACK_I16 = nil,
  440. __construct__PACKER = function(float)
  441. local writeFunc = float and buffer.writef32 or buffer.writei16
  442. local elementSize = float and 4 or 2
  443.  
  444. -- local zbuf, nozbuf = buffer.create(elementSize * 3), buffer.create(elementSize * 2)
  445.  
  446. return function(X, Y, Z)
  447. local len = Z and (elementSize * 3) or (elementSize * 2)
  448. local b = buffer.create(len)
  449.  
  450. writeFunc(b, 0, X)
  451. writeFunc(b, elementSize, Y)
  452. if Z then
  453. writeFunc(b, elementSize * 2, Z)
  454. end
  455.  
  456. return b, len
  457. end
  458. end,
  459. --------------------------------------------------------------
  460. --------------------------------------------------------------
  461. --------------------------------------------------------------
  462. ["string"] = function(raw)
  463. local raw_len = #raw
  464. local len = 4 + raw_len
  465.  
  466. local b = buffer.create(len)
  467.  
  468. buffer.writeu32(b, 0, raw_len)
  469. buffer.writestring(b, 4, raw)
  470.  
  471. return b, len
  472. end,
  473. ["boolean"] = function(raw)
  474. local b = buffer.create(1)
  475.  
  476. buffer.writeu8(b, 0, raw and 1 or 0)
  477.  
  478. return b, 1
  479. end,
  480. ["number"] = function(raw) -- double
  481. local b = buffer.create(8)
  482.  
  483. buffer.writef64(b, 0, raw)
  484.  
  485. return b, 8
  486. end,
  487. ["UDim"] = function(raw)
  488. local b = buffer.create(8)
  489.  
  490. buffer.writef32(b, 0, raw.Scale)
  491. buffer.writei32(b, 4, raw.Offset)
  492.  
  493. return b, 8
  494. end,
  495. ["UDim2"] = function(raw)
  496. return Binary_Descriptors.__PACK_MULTIPLE(Binary_Descriptors["UDim"], raw.X, raw.Y)
  497. end,
  498. ["Ray"] = function(raw)
  499. return Binary_Descriptors.__PACK_MULTIPLE(Binary_Descriptors["Vector3"], raw.Origin, raw.Direction)
  500. end,
  501. ["Faces"] = function(raw)
  502. local b = buffer.create(4)
  503.  
  504. buffer.writeu32(b, 0, __COUNT_BITS(raw.Right, raw.Top, raw.Back, raw.Left, raw.Bottom, raw.Front))
  505.  
  506. return b, 4
  507. end,
  508. ["Axes"] = function(raw)
  509. local b = buffer.create(4)
  510.  
  511. buffer.writeu32(b, 0, __COUNT_BITS(raw.X, raw.Y, raw.Z))
  512.  
  513. return b, 4
  514. end,
  515. ["BrickColor"] = function(raw)
  516. local b = buffer.create(4)
  517.  
  518. buffer.writeu32(b, 0, raw.Number)
  519.  
  520. return b, 4
  521. end,
  522. ["Color3"] = function(raw)
  523. return Binary_Descriptors.__PACK_F32(raw.R, raw.G, raw.B)
  524. end,
  525. ["Vector2"] = function(raw)
  526. return Binary_Descriptors.__PACK_F32(raw.X, raw.Y)
  527. end,
  528. ["Vector3"] = function(raw)
  529. return Binary_Descriptors.__PACK_F32(raw.X, raw.Y, raw.Z)
  530. end,
  531. ["Vector2int16"] = function(raw)
  532. return Binary_Descriptors.__PACK_I16(raw.X, raw.Y)
  533. end,
  534. ["Vector3int16"] = function(raw)
  535. return Binary_Descriptors.__PACK_I16(raw.X, raw.Y, raw.Z)
  536. end,
  537. ["CFrame"] = function(raw)
  538. local X, Y, Z, R00, R01, R02, R10, R11, R12, R20, R21, R22 = raw:GetComponents()
  539. local rotation_ID
  540. do
  541. local b = buffer.create(36)
  542.  
  543. buffer.writef32(b, 0, R00)
  544. buffer.writef32(b, 4, R01)
  545. buffer.writef32(b, 8, R02)
  546. buffer.writef32(b, 12, R10)
  547. buffer.writef32(b, 16, R11)
  548. buffer.writef32(b, 20, R12)
  549. buffer.writef32(b, 24, R20)
  550. buffer.writef32(b, 28, R21)
  551. buffer.writef32(b, 32, R22)
  552.  
  553. rotation_ID = CFrame_Rotation_IDs[buffer.tostring(b)]
  554. end
  555.  
  556. local len = rotation_ID and 13 or 49
  557. local b = buffer.create(len)
  558.  
  559. local __PACK_F32 = Binary_Descriptors.__PACK_F32
  560. local position = __PACK_F32(X, Y, Z)
  561. buffer.copy(b, 0, position)
  562. -- buffer.writef32(b, 0, X)
  563. -- buffer.writef32(b, 4, Y)
  564. -- buffer.writef32(b, 8, Z)
  565.  
  566. if rotation_ID then
  567. buffer.writeu8(b, 12, rotation_ID)
  568. else
  569. buffer.writeu8(b, 12, 0x0)
  570.  
  571. local xBasis = __PACK_F32(R00, R01, R02)
  572. buffer.copy(b, 13, xBasis)
  573. local yBasis = __PACK_F32(R10, R11, R12)
  574. buffer.copy(b, 13 + 12, yBasis)
  575. local zBasis = __PACK_F32(R20, R21, R22)
  576. buffer.copy(b, 13 + 24, zBasis)
  577.  
  578. -- buffer.writef32(b, 13, R00)
  579. -- buffer.writef32(b, 17, R01)
  580. -- buffer.writef32(b, 21, R02)
  581.  
  582. -- buffer.writef32(b, 25, R10)
  583. -- buffer.writef32(b, 29, R11)
  584. -- buffer.writef32(b, 33, R12)
  585.  
  586. -- buffer.writef32(b, 37, R20)
  587. -- buffer.writef32(b, 41, R21)
  588. -- buffer.writef32(b, 45, R22)
  589. end
  590.  
  591. return b, len
  592. end,
  593. ["EnumItem"] = function(raw)
  594. local b_Name, Name_size = Binary_Descriptors["string"](tostring(raw.EnumType))
  595.  
  596. local len = Name_size + 4
  597. local b = buffer.create(len)
  598.  
  599. buffer.copy(b, 0, b_Name)
  600. buffer.writeu32(b, Name_size, raw.Value)
  601.  
  602. return b, len
  603. end,
  604. ["NumberSequence"] = nil,
  605. ["NumberSequenceKeypoint"] = function(keypoint, b, offset)
  606. if not b then
  607. return Binary_Descriptors.__PACK_F32(keypoint.Envelope, keypoint.Time, keypoint.Value)
  608. end
  609.  
  610. buffer.writef32(b, offset, keypoint.Envelope)
  611. offset += 4
  612. buffer.writef32(b, offset, keypoint.Time)
  613. offset += 4
  614. buffer.writef32(b, offset, keypoint.Value)
  615. end,
  616. ["ColorSequence"] = nil,
  617. ["ColorSequenceKeypoint"] = function(keypoint, b, offset)
  618. local Value = Binary_Descriptors["Color3"](keypoint.Value)
  619.  
  620. if not b then
  621. b = buffer.create(20)
  622. offset = 0
  623. end
  624.  
  625. buffer.writef32(b, offset, 0)
  626. offset += 4
  627. buffer.writef32(b, offset, keypoint.Time)
  628. offset += 4
  629. buffer.copy(b, offset, Value)
  630.  
  631. return b, 20
  632. end,
  633. ["NumberRange"] = function(raw)
  634. return Binary_Descriptors.__PACK_F32(raw.Min, raw.Max)
  635. end,
  636. ["Rect"] = function(raw)
  637. return Binary_Descriptors.__PACK_MULTIPLE(Binary_Descriptors["Vector2"], raw.Min, raw.Max)
  638. end,
  639. ["PhysicalProperties"] = function(raw) -- ? Not sure yet (https://github.com/RobloxAPI/spec/blob/master/properties/drafts/AttributesSerializeFull.md#physicalproperties)
  640. local len = 1
  641. if raw then
  642. len += 24
  643. end
  644. local b = buffer.create(len)
  645.  
  646. buffer.writeu8(b, 0, raw and 3 or 0) -- 3 means it has set CustomPhysicalProperties & has set AcousticAbsorption
  647.  
  648. if raw then
  649. buffer.writef32(b, 1, raw.Density)
  650. buffer.writef32(b, 5, raw.Friction)
  651. buffer.writef32(b, 9, raw.Elasticity)
  652. buffer.writef32(b, 13, raw.FrictionWeight)
  653. buffer.writef32(b, 17, raw.ElasticityWeight)
  654. buffer.writef32(b, 21, raw.AcousticAbsorption)
  655. end
  656.  
  657. return b, len
  658. end,
  659. ["Region3"] = function(raw)
  660. local Translation = raw.CFrame.Position
  661. local HalfSize = raw.Size * 0.5
  662.  
  663. return Binary_Descriptors.__PACK_MULTIPLE(
  664. Binary_Descriptors["Vector3"],
  665. Translation - HalfSize, -- /App/util/Region3.cpp#L38
  666. Translation + HalfSize -- /App/util/Region3.cpp#L42
  667. )
  668. end,
  669. ["Region3int16"] = function(raw)
  670. return Binary_Descriptors.__PACK_MULTIPLE(Binary_Descriptors["Vector3int16"], raw.Min, raw.Max)
  671. end,
  672. ["Font"] = 636 < CLIENT_VERSION and function(raw)
  673. local string__descriptor = Binary_Descriptors["string"]
  674.  
  675. local b_Family, Family_size = string__descriptor(raw.Family)
  676. local b_CachedFaceId, CachedFaceId_size = string__descriptor("")
  677.  
  678. local len = 3 + Family_size + CachedFaceId_size
  679. local b = buffer.create(len)
  680.  
  681. local ok_w, weight = pcall(index, raw, "Weight")
  682. local ok_s, style = pcall(index, raw, "Style")
  683.  
  684. buffer.writeu16(b, 0, ok_w and weight.Value or 0)
  685. buffer.writeu8(b, 2, ok_s and style.Value or 0)
  686.  
  687. buffer.copy(b, 3, b_Family)
  688. buffer.copy(b, 3 + Family_size, b_CachedFaceId)
  689.  
  690. return b, len
  691. end or function(raw)
  692. local string__descriptor = Binary_Descriptors["string"]
  693.  
  694. local b_Family, Family_size = string__descriptor(raw.Family)
  695. local b_CachedFaceId, CachedFaceId_size = string__descriptor("")
  696.  
  697. local len = 3 + Family_size + CachedFaceId_size
  698. local b = buffer.create(len)
  699.  
  700. local FontString = tostring(raw)
  701.  
  702. local EmptyWeight = string_find(FontString, "Weight = ,")
  703. local EmptyStyle = string_find(FontString, "Style = }")
  704.  
  705. buffer.writeu16(b, 0, EmptyWeight and 0 or raw.Weight.Value)
  706. buffer.writeu8(b, 2, EmptyStyle and 0 or raw.Style.Value)
  707.  
  708. buffer.copy(b, 3, b_Family)
  709. buffer.copy(b, 3 + Family_size, b_CachedFaceId)
  710.  
  711. return b, len
  712. end,
  713. ["SecurityCapabilities"] = function(raw)
  714. local b = buffer.create(8)
  715.  
  716. if raw == BASE_CAPABILITIES then
  717. return b, 8
  718. end
  719.  
  720. Binary_Descriptors.__writei64(b, 0, __COUNT_CAPABILITY_BITS(raw))
  721.  
  722. return b, 8
  723. end,
  724. ["Path2DControlPoint"] = function(raw)
  725. return Binary_Descriptors.__PACK_MULTIPLE(
  726. Binary_Descriptors["UDim2"],
  727. raw.Position,
  728. raw.LeftTangent,
  729. raw.RightTangent
  730. )
  731. end,
  732. }
  733.  
  734. do -- Sequences
  735. Binary_Descriptors["NumberSequence"] =
  736. Binary_Descriptors.__construct_Sequence(Binary_Descriptors["NumberSequenceKeypoint"], 12)
  737.  
  738. Binary_Descriptors["ColorSequence"] =
  739. Binary_Descriptors.__construct_Sequence(Binary_Descriptors["ColorSequenceKeypoint"], 20)
  740. end
  741.  
  742. do -- Vectors
  743. Binary_Descriptors.__PACK_F32 = Binary_Descriptors.__construct__PACKER(true)
  744.  
  745. Binary_Descriptors.__PACK_I16 = Binary_Descriptors.__construct__PACKER()
  746. end
  747.  
  748. local ESCAPES_PATTERN = "[&<>\"'\0\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
  749. -- %z (\0 aka NULL) might not be needed as Roblox automatically converts it to space everywhere it seems like
  750. -- Characters from: https://create.roblox.com/docs/en-us/ui/rich-text#escape-forms
  751. -- * EscapesPattern should be ordered from most common to least common characters for sake of speed
  752. -- * Might wanna use their numerical codes instead of named codes for reduced file size (Could be an Option)
  753. -- TODO Maybe we should invert the pattern to only allow certain characters (future-proof)
  754. local ESCAPES = {
  755. ["&"] = "&amp;", -- 38
  756. ["<"] = "&lt;", -- 60
  757. [">"] = "&gt;", -- 62
  758. ['"'] = "&#34;", -- quot
  759. ["'"] = "&#39;", -- apos
  760. ["\0"] = "",
  761. }
  762.  
  763. for rangeStart, rangeEnd in string.gmatch(ESCAPES_PATTERN, "(.)%-(.)") do
  764. for charCode = string.byte(rangeStart), string.byte(rangeEnd) do
  765. ESCAPES[string.char(charCode)] = "&#" .. charCode .. ";"
  766. end
  767. end
  768.  
  769. local XML_Descriptors
  770. XML_Descriptors = {
  771. __CDATA = function(raw) -- ? Normally Roblox doesn't use CDATA unless the string has newline characters (\n); We rather CDATA everything for sake of speed
  772. return "<![CDATA[" .. raw .. "]]>"
  773. end,
  774. __NORMALIZE_NUMBER = function(raw)
  775. if raw ~= raw then
  776. return "NAN"
  777. elseif raw == math.huge then
  778. return "INF"
  779. elseif raw == -math.huge then
  780. return "-INF"
  781. end
  782.  
  783. return raw
  784. end,
  785. __NORMALIZE_RANGE = function(raw)
  786. return raw ~= raw and "0" or raw -- Normally we should return "-nan(ind)" instead of "0" but this adds more compatibility
  787. end,
  788. __MINMAX = function(min, max, descriptor)
  789. return "<min>" .. descriptor(min) .. "</min><max>" .. descriptor(max) .. "</max>"
  790. end,
  791. __PROTECTEDSTRING = function(raw) -- ? its purpose is to "protect" data from being treated as ordinary character data during processing;
  792. return string_find(raw, "]]>") and string.gsub(raw, ESCAPES_PATTERN, ESCAPES) or XML_Descriptors.__CDATA(raw)
  793. end,
  794. __construct_Sequence = function(keypoint_handler)
  795. -- The value is the text content, formatted as a space-separated list of floating point numbers.
  796. -- tostring(raw) also works (but way slower rn)
  797. -- ? Trailing whitespace after Envelope is needed for lune compatibility
  798. return function(raw)
  799. local sequence = ""
  800.  
  801. for _, keypoint in raw.Keypoints do
  802. sequence ..= keypoint_handler(keypoint)
  803. end
  804.  
  805. return sequence
  806. end
  807. end,
  808. __VECTOR = function(X, Y, Z) -- Each element is a <float>
  809. local Value = "<X>" .. X .. "</X><Y>" .. Y .. "</Y>" -- There is no Vector without at least two Coordinates.. (Vector1, at least on Roblox)
  810.  
  811. if Z then
  812. Value ..= "<Z>" .. Z .. "</Z>"
  813. end
  814.  
  815. return Value
  816. end,
  817. --------------------------------------------------------------
  818. --------------------------------------------------------------
  819. --------------------------------------------------------------
  820. Axes = function(raw)
  821. -- The text of this element is formatted as an integer between 0 and 7
  822.  
  823. return "<axes>" .. __COUNT_BITS(raw.X, raw.Y, raw.Z) .. "</axes>"
  824. end,
  825.  
  826. -- ! Assuming all base64 encoded strings won't have newlines
  827.  
  828. BinaryString = function(raw)
  829. return raw == "" and "" or base64encode(raw)
  830. end,
  831.  
  832. BrickColor = function(raw)
  833. 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"
  834. end,
  835. CFrame = function(raw)
  836. local X, Y, Z, R00, R01, R02, R10, R11, R12, R20, R21, R22 = raw:GetComponents()
  837. return XML_Descriptors.__VECTOR(X, Y, Z)
  838. .. "<R00>"
  839. .. R00
  840. .. "</R00><R01>"
  841. .. R01
  842. .. "</R01><R02>"
  843. .. R02
  844. .. "</R02><R10>"
  845. .. R10
  846. .. "</R10><R11>"
  847. .. R11
  848. .. "</R11><R12>"
  849. .. R12
  850. .. "</R12><R20>"
  851. .. R20
  852. .. "</R20><R21>"
  853. .. R21
  854. .. "</R21><R22>"
  855. .. R22
  856. .. "</R22>",
  857. "CoordinateFrame"
  858. end,
  859. -- CFrameQuat = function(raw) -- ? This will probably never release as it's not even used anywhere naturally, but there are hints it does exist as a DataType
  860. -- local X, Y, Z, R00, R01, R02, R10, R11, R12, R20, R21, R22 = raw:GetComponents()
  861. -- local trace = R00 + R11 + R22
  862. -- local S, QW, QX, QY, QZ
  863.  
  864. -- if trace > 0 then
  865. -- S = math.sqrt(1 + trace) * 2
  866. -- QW = 0.25 * S
  867. -- QX = (R21 - R12) / S
  868. -- QY = (R02 - R20) / S
  869. -- QZ = (R10 - R01) / S
  870. -- elseif (R00 > R11) and (R00 > R22) then
  871. -- S = math.sqrt(1 + R00 - R11 - R22) * 2
  872. -- QW = (R21 - R12) / S
  873. -- QX = 0.25 * S
  874. -- QY = (R01 + R10) / S
  875. -- QZ = (R02 + R20) / S
  876. -- elseif R11 > R22 then
  877. -- S = math.sqrt(1 + R11 - R00 - R22) * 2
  878. -- QW = (R02 - R20) / S
  879. -- QX = (R01 + R10) / S
  880. -- QY = 0.25 * S
  881. -- QZ = (R12 + R21) / S
  882. -- else
  883. -- S = math.sqrt(1 + R22 - R00 - R11) * 2
  884. -- QW = (R10 - R01) / S
  885. -- QX = (R02 + R20) / S
  886. -- QY = (R12 + R21) / S
  887. -- QZ = 0.25 * S
  888. -- end
  889.  
  890. -- return XML_Descriptors.__VECTOR(X, Y, Z)
  891. -- .. "<QX>"
  892. -- .. QX
  893. -- .. "</QX><QY>"
  894. -- .. QY
  895. -- .. "</QY><QZ>"
  896. -- .. QZ
  897. -- .. "</QZ><QW>"
  898. -- .. QW
  899. -- .. "</QW>"
  900. -- end,
  901. Color3 = function(raw) -- Each element is a <float>
  902. 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.
  903. end,
  904. Color3uint8 = function(raw)
  905. -- https://github.com/rojo-rbx/rbx-dom/blob/master/docs/xml.md#color3uint8
  906. -- ? It is recommended that Color3uint8 is encoded with text instead of elements.
  907.  
  908. return 0xFF000000
  909. + (math.floor(raw.R * 255) * 0x10000)
  910. + (math.floor(raw.G * 255) * 0x100)
  911. + math.floor(raw.B * 255)
  912. -- return bit32.bor(
  913. -- bit32.bor(bit32.bor(bit32.lshift(0xFF, 24), bit32.lshift(0xFF * raw.R, 16)), bit32.lshift(0xFF * raw.G, 8)),
  914. -- 0xFF * raw.B
  915. -- )
  916.  
  917. -- return tonumber(string.format("0xFF%02X%02X%02X",raw.R*255,raw.G*255,raw.B*255))
  918. end,
  919. ColorSequence = nil,
  920. ColorSequenceKeypoint = function(keypoint)
  921. local __NORMALIZE_RANGE = XML_Descriptors.__NORMALIZE_RANGE
  922.  
  923. local color3 = keypoint.Value
  924.  
  925. return __NORMALIZE_RANGE(keypoint.Time)
  926. .. " "
  927. .. __NORMALIZE_RANGE(color3.R)
  928. .. " "
  929. .. __NORMALIZE_RANGE(color3.G)
  930. .. " "
  931. .. __NORMALIZE_RANGE(color3.B)
  932. .. " 0 "
  933. end,
  934. Content = function(raw) -- TODO Not sure about Object & Opaque, run tests when possible
  935. local SourceType = raw.SourceType
  936. return SourceType == Enum.ContentSourceType.None and "<null></null>"
  937. or SourceType == Enum.ContentSourceType.Uri and "<uri>" .. XML_Descriptors.string(raw.Uri) .. "</uri>"
  938. or SourceType == Enum.ContentSourceType.Object and "<Ref>" .. GetRef(raw.Object) .. "</Ref>"
  939. or SourceType == Enum.ContentSourceType.Opaque and "<Ref>" .. GetRef(raw.Opaque) .. "</Ref>"
  940. end,
  941. ContentId = function(raw)
  942. return raw == "" and "<null></null>" or "<url>" .. XML_Descriptors.string(raw) .. "</url>", "Content" -- TODO Remove "Content" str once Roblox fully releases Content DataType
  943. end,
  944. CoordinateFrame = function(raw)
  945. return "<CFrame>" .. XML_Descriptors.CFrame(raw) .. "</CFrame>"
  946. end,
  947. -- DateTime = function(raw) return raw.UnixTimestampMillis end, -- ? Not sure
  948. EnumItem = function(raw)
  949. return raw.Value, "token"
  950. end,
  951. Faces = function(raw)
  952. -- The text of this element is formatted as an integer between 0 and 63
  953. return "<faces>" .. __COUNT_BITS(raw.Right, raw.Top, raw.Back, raw.Left, raw.Bottom, raw.Front) .. "</faces>"
  954. end,
  955. Font = 636 < CLIENT_VERSION
  956. and function(raw)
  957. -- TODO (OPTIONAL ELEMENT): Figure out how to determine (ContentId) <CachedFaceId><url>rbxasset://fonts/GothamSSm-Medium.otf</url></CachedFaceId>
  958. --[[
  959. ? game:GetService("TextService"):GetFontMemoryData()
  960. ? rbxasset://fonts/families/{Enum.Font.BuilderSans.Name}.json
  961. ]]
  962.  
  963. local ok_w, weight = pcall(index, raw, "Weight")
  964. local ok_s, style = pcall(index, raw, "Style")
  965.  
  966. return "<Family>"
  967. .. XML_Descriptors.ContentId(raw.Family)
  968. .. "</Family><Weight>"
  969. .. (ok_w and XML_Descriptors.EnumItem(weight) or "")
  970. .. "</Weight><Style>"
  971. .. (ok_s and style.Name or "") -- Weird but this field accepts .Name of enum instead..
  972. .. "</Style>"
  973. end
  974. or function(raw)
  975. local FontString = tostring(raw) -- TODO: Temporary fix
  976.  
  977. local EmptyWeight = string_find(FontString, "Weight = ,")
  978. local EmptyStyle = string_find(FontString, "Style = }")
  979.  
  980. return "<Family>"
  981. .. XML_Descriptors.ContentId(raw.Family)
  982. .. "</Family><Weight>"
  983. .. (EmptyWeight and "" or XML_Descriptors.EnumItem(raw.Weight))
  984. .. "</Weight><Style>"
  985. .. (EmptyStyle and "" or raw.Style.Name) -- Weird but this field accepts .Name of enum instead..
  986. .. "</Style>"
  987. end,
  988. NetAssetRef = nil,
  989. NumberRange = function(raw) -- tostring(raw) also works
  990. -- The value is the text content, formatted as a space-separated list of floating point numbers.
  991. local __NORMALIZE_RANGE = XML_Descriptors.__NORMALIZE_RANGE
  992.  
  993. return __NORMALIZE_RANGE(raw.Min) .. " " .. __NORMALIZE_RANGE(raw.Max) --[[.. " "]] -- ! This might be required for compatibility; __NORMALIZE_RANGE is not needed here but it fixes the issue where "nan 10" value would reset to "0 0"
  994. end,
  995. NumberSequence = nil,
  996. NumberSequenceKeypoint = function(keypoint)
  997. local __NORMALIZE_RANGE = XML_Descriptors.__NORMALIZE_RANGE
  998.  
  999. return __NORMALIZE_RANGE(keypoint.Time)
  1000. .. " "
  1001. .. __NORMALIZE_RANGE(keypoint.Value)
  1002. .. " "
  1003. .. __NORMALIZE_RANGE(keypoint.Envelope)
  1004. .. " "
  1005. end,
  1006. -- Path2DControlPoint = function(raw) -- ? Not sure
  1007. -- local udim2 = XML_Descriptors.UDim2
  1008. -- return "<Position>"
  1009. -- .. udim2(raw.Position)
  1010. -- .. "</Position>"
  1011. -- .. "<LeftTangent>"
  1012. -- .. udim2(raw.LeftTangent)
  1013. -- .. "</LeftTangent>"
  1014. -- .. "<RightTangent>"
  1015. -- .. udim2(raw.RightTangent)
  1016. -- .. "</RightTangent>"
  1017. -- end,
  1018.  
  1019. PhysicalProperties = function(raw)
  1020. --[[
  1021. 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:
  1022.  
  1023. Density
  1024. Friction
  1025. Elasticity
  1026. FrictionWeight
  1027. ElasticityWeight
  1028. AcousticAbsorption
  1029.  
  1030. The value of each component is represented by the text content formatted as a 32-bit floating point number (see float)
  1031. ]]
  1032.  
  1033. local CustomPhysics = "<CustomPhysics>" .. XML_Descriptors.bool(raw and true or false) .. "</CustomPhysics>"
  1034.  
  1035. return raw
  1036. and CustomPhysics .. "<Density>" .. raw.Density .. "</Density><Friction>" .. raw.Friction .. "</Friction><Elasticity>" .. raw.Elasticity .. "</Elasticity><FrictionWeight>" .. raw.FrictionWeight .. "</FrictionWeight><ElasticityWeight>" .. raw.ElasticityWeight .. "</ElasticityWeight><AcousticAbsorption>" .. raw.AcousticAbsorption .. "</AcousticAbsorption>"
  1037. or CustomPhysics
  1038. end,
  1039. -- ProtectedString = function(raw) return tostring(raw), "ProtectedString" end,
  1040. Ray = function(raw)
  1041. local vector3 = XML_Descriptors.Vector3
  1042.  
  1043. return "<origin>" .. vector3(raw.Origin) .. "</origin><direction>" .. vector3(raw.Direction) .. "</direction>"
  1044. end,
  1045. Rect = function(raw)
  1046. return XML_Descriptors.__MINMAX(raw.Min, raw.Max, XML_Descriptors.Vector2), "Rect2D"
  1047. end,
  1048. Region3 = function(raw) -- ? Not sure about xml format yet, the math is correct though (/Network/Replicator.cpp#L1306)
  1049. local Translation = raw.CFrame.Position
  1050. local HalfSize = raw.Size * 0.5
  1051.  
  1052. return XML_Descriptors.__MINMAX(
  1053. Translation - HalfSize, -- /App/util/Region3.cpp#L38
  1054. Translation + HalfSize, -- /App/util/Region3.cpp#L42
  1055. XML_Descriptors.Vector3
  1056. )
  1057. end,
  1058. Region3int16 = function(raw) -- ? Not sure yet (/App/v8tree/EnumProperty.cpp#L346)
  1059. return XML_Descriptors.__MINMAX(raw.Min, raw.Max, XML_Descriptors.Vector3int16)
  1060. end,
  1061. SharedString = function(raw)
  1062. raw = raw == "" and "" or base64encode(raw)
  1063.  
  1064. local Identifier = SharedString_identifiers[raw]
  1065.  
  1066. if SharedStrings[Identifier] == nil then
  1067. SharedStrings[Identifier] = raw
  1068. end
  1069.  
  1070. return Identifier
  1071. end,
  1072. SecurityCapabilities = function(raw)
  1073. if raw == BASE_CAPABILITIES then
  1074. return 0
  1075. end
  1076.  
  1077. return __COUNT_CAPABILITY_BITS(raw)
  1078. end,
  1079. -- SystemAddress = function(raw) return raw end, -- PeerId? systemAddress as a string in the format "IP|Port", "|" being portDelineator, should not be '.', ':', '%', '-', '/', a number, or a-f -- ? Not sure
  1080. -- TweenInfo = function(raw) -- ? Not sure
  1081. -- local __NORMALIZE_NUMBER = XML_Descriptors.__NORMALIZE_NUMBER
  1082. -- local EnumItem = XML_Descriptors.EnumItem
  1083.  
  1084. -- return "<Time>"
  1085. -- .. __NORMALIZE_NUMBER(raw.Time)
  1086. -- .. "</Time><DelayTime>"
  1087. -- .. __NORMALIZE_NUMBER(raw.DelayTime)
  1088. -- .. "</DelayTime><RepeatCount>"
  1089. -- .. __NORMALIZE_NUMBER(raw.RepeatCount)
  1090. -- .. "</RepeatCount><Reverses>"
  1091. -- .. XML_Descriptors.bool(raw.Reverses)
  1092. -- .. "</Reverses><EasingDirection>"
  1093. -- .. enum(raw.EasingDirection)
  1094. -- .. "</EasingDirection><EasingStyle>"
  1095. -- .. enum(raw.EasingStyle)
  1096. -- .. "</EasingStyle>"
  1097. -- end,
  1098. UDim = function(raw)
  1099. --[[
  1100. S: Represents the Scale component. Interpreted as a <float>.
  1101. O: Represents the Offset component. Interpreted as an <int>.
  1102. ]]
  1103.  
  1104. return "<S>" .. raw.Scale .. "</S><O>" .. raw.Offset .. "</O>"
  1105. end,
  1106. UDim2 = function(raw)
  1107. --[[
  1108. XS: Represents the X.Scale component. Interpreted as a <float>.
  1109. XO: Represents the X.Offset component. Interpreted as an <int>.
  1110. YS: Represents the Y.Scale component. Interpreted as a <float>.
  1111. YO: Represents the Y.Offset component. Interpreted as an <int>.
  1112. ]]
  1113.  
  1114. local X, Y = raw.X, raw.Y
  1115.  
  1116. return "<XS>"
  1117. .. X.Scale
  1118. .. "</XS><XO>"
  1119. .. X.Offset
  1120. .. "</XO><YS>"
  1121. .. Y.Scale
  1122. .. "</YS><YO>"
  1123. .. Y.Offset
  1124. .. "</YO>"
  1125. end,
  1126.  
  1127. -- UniqueId = function(raw) -- ? Not sure -- ? No idea if this even needs a Descriptor
  1128. -- --[[
  1129. -- UniqueId properties might be random everytime Studio saves a place file
  1130. -- and don't have a use right now outside of packages, which SSI doesn't
  1131. -- account for anyway. They generate diff noise, so we shouldn't serialize
  1132. -- them until we have to.
  1133. -- ]]
  1134. -- -- https://github.com/MaximumADHD/Roblox-Client-Tracker/blob/master/LuaPackages/Packages/_Index/ApolloClient/ApolloClient/utilities/common/makeUniqueId.lua#L68
  1135. -- return raw -- seems to be string type by default
  1136. -- end,
  1137.  
  1138. Vector2 = function(raw)
  1139. --[[
  1140. X: Represents the X component. Interpreted as a <float>.
  1141. Y: Represents the Y component. Interpreted as a <float>.
  1142. ]]
  1143. return XML_Descriptors.__VECTOR(raw.X, raw.Y)
  1144. end,
  1145. Vector2int16 = nil,
  1146. -- Vector2int16 = Descriptors.Vector2, -- except as <int>
  1147. Vector3 = function(raw)
  1148. --[[
  1149. X: Represents the X component. Interpreted as a <float>.
  1150. Y: Represents the Y component. Interpreted as a <float>.
  1151. Z: Represents the Z component. Interpreted as a <float>.
  1152. ]]
  1153. return XML_Descriptors.__VECTOR(raw.X, raw.Y, raw.Z)
  1154. end,
  1155. Vector3int16 = nil,
  1156. -- Vector3int16 = Descriptors.Vector3, -- except as <int>\
  1157. bool = function(raw)
  1158. return raw and "true" or "false"
  1159. end,
  1160. double = nil, -- Float64
  1161. float = nil, -- Float32
  1162. int = nil, -- Int32
  1163. int64 = nil, -- Int64 (long)
  1164. string = function(raw)
  1165. return (raw == nil or raw == "") and ""
  1166. or string_find(raw, "]]>") and string.gsub(raw, ESCAPES_PATTERN, ESCAPES)
  1167. or XML_Descriptors.__CDATA(string.gsub(raw, "\0", ""))
  1168. end,
  1169.  
  1170. --------------------------------------------------------------
  1171. -----------%localappdata%/Roblox/GlobalSettings_13.xml--------
  1172. -----------------------settings().Studio----------------------
  1173. --------------------------------------------------------------
  1174. -- QDir = function(raw) -- ? Not sure
  1175. -- return raw
  1176. -- end,
  1177. -- QFont = function(raw) -- ? Not sure
  1178. -- return raw
  1179. -- end,
  1180. }
  1181.  
  1182. do -- Sequences
  1183. XML_Descriptors.NumberSequence = XML_Descriptors.__construct_Sequence(XML_Descriptors.NumberSequenceKeypoint)
  1184.  
  1185. XML_Descriptors.ColorSequence = XML_Descriptors.__construct_Sequence(XML_Descriptors.ColorSequenceKeypoint)
  1186. end
  1187.  
  1188. for descriptorName, redirectName in
  1189. {
  1190. NetAssetRef = "SharedString",
  1191. Vector2int16 = "Vector2",
  1192. Vector3int16 = "Vector3",
  1193. double = "__NORMALIZE_NUMBER",
  1194. float = "__NORMALIZE_NUMBER",
  1195. int = "__NORMALIZE_NUMBER",
  1196. int64 = "__NORMALIZE_NUMBER",
  1197. }
  1198. do
  1199. XML_Descriptors[descriptorName] = XML_Descriptors[redirectName]
  1200. end
  1201.  
  1202. if CLIENT_VERSION < math.huge then -- math.huge because unknown yet
  1203. -- ! For sake of compatibility with older clients, Roblox currently does this too but it WILL probably change in the future so keep track of that. ONCE ROBLOX MOVES AWAY FROM THIS, MAKE SURE TO KEEP SUPPORT FOR VERY OLD CLIENTS; 645 <= CLIENT Ver < ??? (ver when roblox moves away), THIS IS COMPLICATED SO PROBABLY NO SUPPORT, JUST USE OLDER VER OF USSI; NEWEST CLIENTS
  1204. XML_Descriptors.Content = XML_Descriptors.ContentId
  1205. end
  1206.  
  1207. local ClassList
  1208.  
  1209. do
  1210. local ClassPropertyExceptions = {
  1211. Whitelist = {
  1212. MeshPart = ArrayToDict({ "CollisionFidelity" }),
  1213. PartOperation = ArrayToDict({ "CollisionFidelity" }),
  1214. TriangleMeshPart = ArrayToDict({ "CollisionFidelity" }),
  1215. },
  1216. Blacklist = {
  1217. LuaSourceContainer = ArrayToDict({ "ScriptGuid" }),
  1218. Instance = ArrayToDict({ "UniqueId", "HistoryId" }),
  1219. },
  1220. }
  1221.  
  1222. local function AttributesSerialize(attrs)
  1223. -- * There are certain restrictions for names of attributes
  1224. -- https://create.roblox.com/docs/reference/engine/classes/Instance#SetAttribute
  1225. -- But it seems like even if those are present, Studio still opens the file just fine
  1226. -- So there is no need to check for them currently
  1227.  
  1228. -- ? Return early for empty tags (this proved equally as fast when done using counter/next)
  1229.  
  1230. local attrs_n = 0
  1231. local buffer_size = 4
  1232. local attrs_sorted = {}
  1233. local attrs_formatted = table.clone(attrs)
  1234. for attr, val in attrs do
  1235. attrs_n += 1
  1236. attrs_sorted[attrs_n] = attr
  1237.  
  1238. local Type = typeof(val)
  1239.  
  1240. local Descriptor = Binary_Descriptors[Type]
  1241. local attr_size
  1242.  
  1243. attrs_formatted[attr], attr_size = Descriptor(val)
  1244.  
  1245. buffer_size += 5 + #attr + attr_size
  1246. end
  1247.  
  1248. table.sort(attrs_sorted)
  1249.  
  1250. local b = buffer.create(buffer_size)
  1251.  
  1252. buffer.writeu32(b, 0, attrs_n)
  1253.  
  1254. local string__descriptor = Binary_Descriptors["string"]
  1255. local offset = 4
  1256. for _, attr in attrs_sorted do
  1257. local b_Name, Name_size = string__descriptor(attr)
  1258.  
  1259. buffer.copy(b, offset, b_Name)
  1260. offset += Name_size
  1261.  
  1262. buffer.writeu8(b, offset, attr_Type_IDs[typeof(attrs[attr])])
  1263. offset += 1
  1264.  
  1265. local bb = attrs_formatted[attr]
  1266.  
  1267. buffer.copy(b, offset, bb)
  1268. offset += buffer.len(bb)
  1269. end
  1270.  
  1271. return buffer.tostring(b)
  1272. end
  1273.  
  1274. local function AttenuationSerialize(attenuations)
  1275. if not next(attenuations) then
  1276. return "\0" -- ? [CONSTANT] Version byte (likely)
  1277. end
  1278.  
  1279. local attenuations_n = 0
  1280.  
  1281. local attenuations_sorted = {}
  1282.  
  1283. for key in attenuations do
  1284. attenuations_n += 1
  1285. attenuations_sorted[attenuations_n] = key
  1286. end
  1287.  
  1288. table.sort(attenuations_sorted) -- ? Seems to be sorted by default, just in case
  1289.  
  1290. local b = buffer.create(1 + attenuations_n * 8)
  1291.  
  1292. local offset = 1
  1293. for _, key in attenuations_sorted do
  1294. buffer.writef32(b, offset, key)
  1295. offset += 4
  1296. buffer.writef32(b, offset, attenuations[key]) -- volume
  1297. offset += 4
  1298. end
  1299.  
  1300. return buffer.tostring(b)
  1301. end
  1302.  
  1303. local function TransformsSerialize(transforms)
  1304. local transforms_n = #transforms
  1305.  
  1306. if transforms_n == 0 then
  1307. return "\1\0\0\0\0\0\0\0"
  1308. end
  1309.  
  1310. local b = buffer.create(8 + transforms_n * 48)
  1311.  
  1312. buffer.writeu32(b, 0, 1) -- ? [CONSTANT] Version byte (likely)
  1313. buffer.writeu32(b, 4, transforms_n)
  1314.  
  1315. local __PACK_F32 = Binary_Descriptors.__PACK_F32
  1316.  
  1317. local offset = 8
  1318. for _, transform in transforms do
  1319. local X, Y, Z, R00, R01, R02, R10, R11, R12, R20, R21, R22 = transform:GetComponents()
  1320.  
  1321. local xBasis = __PACK_F32(R00, R01, R02)
  1322. buffer.copy(b, offset, xBasis)
  1323. offset += 12
  1324.  
  1325. local yBasis = __PACK_F32(R10, R11, R12)
  1326. buffer.copy(b, offset, yBasis)
  1327. offset += 12
  1328.  
  1329. local zBasis = __PACK_F32(R20, R21, R22)
  1330. buffer.copy(b, offset, zBasis)
  1331. offset += 12
  1332.  
  1333. local position = __PACK_F32(X, Y, Z)
  1334. buffer.copy(b, offset, position)
  1335. offset += 12
  1336. end
  1337.  
  1338. return buffer.tostring(b)
  1339. end
  1340.  
  1341. local NotScriptableFixes = { --[[
  1342. For more info:
  1343. - https://github.com/luau/UniversalSynSaveInstance/blob/main/Tools/NotScriptable-Related/Potentially%20Missing%20Properties%20Dumper/Potentially%20Missing%20Properties%20Dumper.luau
  1344. - https://github.com/luau/UniversalSynSaveInstance/blob/main/Tools/NotScriptable-Related/NotScriptable%20Dumper/NotScriptable%20Dumper.py
  1345. ]]
  1346. Instance = {
  1347. AttributesSerialize = function(instance)
  1348. local attrs = instance:GetAttributes()
  1349.  
  1350. if not next(attrs) then
  1351. return ""
  1352. end
  1353.  
  1354. return AttributesSerialize(attrs)
  1355. end,
  1356. DefinesCapabilities = "Sandboxed",
  1357. Tags = function(instance)
  1358. -- https://github.com/RobloxAPI/spec/blob/master/properties/Tags.md
  1359.  
  1360. local tags = service.CollectionService:GetTags(instance) -- ? Seems faster than instance:GetTags
  1361.  
  1362. if #tags == 0 then
  1363. return ""
  1364. end
  1365.  
  1366. return table.concat(tags, "\0")
  1367. end,
  1368. },
  1369. Path2D = {
  1370. PropertiesSerialize = function(instance)
  1371. local control_points = instance:GetControlPoints()
  1372. local control_points_n = #control_points
  1373.  
  1374. if control_points_n == 0 then
  1375. return "\0\0\0\0"
  1376. end
  1377.  
  1378. local b = buffer.create(4 + control_points_n * 49)
  1379. buffer.writeu32(b, 0, control_points_n)
  1380.  
  1381. local TypeID_Path2DControlPoint = attr_Type_IDs["Path2DControlPoint"]
  1382. local Path2DControlPoint_descriptor = Binary_Descriptors["Path2DControlPoint"]
  1383.  
  1384. local offset = 4
  1385. for i, point in control_points do
  1386. local buf = Path2DControlPoint_descriptor(point)
  1387.  
  1388. buffer.writeu8(b, offset, TypeID_Path2DControlPoint)
  1389. offset += 1
  1390.  
  1391. buffer.copy(b, offset, buf)
  1392. offset += 48
  1393. end
  1394.  
  1395. return buffer.tostring(b)
  1396. end,
  1397. },
  1398. PlayerEmulatorService = {
  1399. SerializedEmulatedPolicyInfo = function(instance)
  1400. local EmulatedPolicyInfo = instance:GetEmulatedPolicyInfo()
  1401.  
  1402. if not next(EmulatedPolicyInfo) then
  1403. return ""
  1404. end
  1405.  
  1406. return AttributesSerialize(EmulatedPolicyInfo)
  1407. end,
  1408. },
  1409. StyleRule = {
  1410. PropertiesSerialize = function(instance)
  1411. local props = instance:GetProperties()
  1412.  
  1413. if not next(props) then
  1414. return "\0\0\0\0" -- ! Essential
  1415. end
  1416.  
  1417. return AttributesSerialize(props)
  1418. end,
  1419. },
  1420. StyleQuery = {
  1421. ConditionsSerialize = function(instance)
  1422. local props = instance:GetConditions()
  1423.  
  1424. if not next(props) then
  1425. return "\0\0\0\0"
  1426. end
  1427.  
  1428. return AttributesSerialize(props)
  1429. end,
  1430. },
  1431. MarkerCurve = {
  1432. ValuesAndTimes = function(instance)
  1433. local markers = instance:GetMarkers()
  1434. local markers_n = #markers
  1435.  
  1436. if markers_n == 0 then
  1437. -- return "" -- Seems to also work
  1438. return "\2\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0"
  1439. end
  1440.  
  1441. local strings_size = 0
  1442. for i, marker in markers do
  1443. strings_size += #marker.Value + 1
  1444. end
  1445.  
  1446. local b = buffer.create(8 + strings_size + 8 + (markers_n * 4))
  1447.  
  1448. -- Values section
  1449. buffer.writeu32(b, 0, 2) -- Constant
  1450. buffer.writeu32(b, 4, markers_n)
  1451.  
  1452. local offset = 8
  1453. for i, marker in markers do
  1454. local value = marker.Value
  1455. buffer.writestring(b, offset, value)
  1456. offset += #value + 1
  1457. -- buffer.writeu8(b, offset, 0) -- Null terminator
  1458. -- offset += 1
  1459. end
  1460.  
  1461. -- Times section (as 2400x scaled integers)
  1462. buffer.writeu32(b, offset, 1) -- Constant
  1463. offset += 4
  1464. buffer.writeu32(b, offset, markers_n)
  1465. offset += 4
  1466.  
  1467. for i, marker in markers do
  1468. local scaled_time = math.round(marker.Time * 2400)
  1469. buffer.writeu32(b, offset, scaled_time)
  1470. offset += 4
  1471. end
  1472.  
  1473. return buffer.tostring(b)
  1474. end,
  1475. },
  1476. AnimationNodeDefinition = {
  1477. InputPinData = function(instance)
  1478. local input_pins = instance:GetInputPins()
  1479.  
  1480. local input_pins_n = #input_pins
  1481.  
  1482. if input_pins_n == 0 then
  1483. return "\1\0\0\0\0\0\0\0"
  1484. end
  1485.  
  1486. local buffer_size = 8
  1487.  
  1488. for _, pin in input_pins do
  1489. buffer_size += 4 + #pin
  1490. end
  1491.  
  1492. local b = buffer.create(buffer_size)
  1493.  
  1494. buffer.writeu32(b, 0, 1) -- ? [CONSTANT] Version byte (likely)
  1495. buffer.writeu32(b, 4, input_pins_n)
  1496.  
  1497. local string__descriptor = Binary_Descriptors["string"]
  1498. local offset = 8
  1499. for _, pin in input_pins do
  1500. local b_pin, pin_size = string__descriptor(pin)
  1501.  
  1502. buffer.copy(b, offset, b_pin)
  1503. offset += pin_size
  1504. end
  1505.  
  1506. return buffer.tostring(b)
  1507. end,
  1508. },
  1509. AnimationRigData = {
  1510. label = function(instance)
  1511. local labels = instance:GetLabels() -- RobloxScriptSecurity
  1512. local labels_n = #labels
  1513.  
  1514. if labels_n == 0 then
  1515. return "\1\0\0\0\0\0\0\0"
  1516. end
  1517.  
  1518. local b = buffer.create(8 + labels_n * 4)
  1519.  
  1520. buffer.writeu32(b, 0, 1) -- ? [CONSTANT] Version byte (likely)
  1521. buffer.writeu32(b, 4, labels_n)
  1522.  
  1523. local offset = 8
  1524.  
  1525. for _, label in labels do
  1526. buffer.writeu32(b, offset, label)
  1527. offset += 4
  1528. end
  1529.  
  1530. return buffer.tostring(b)
  1531. end,
  1532. name = function(instance)
  1533. local names = instance:GetNames() -- RobloxScriptSecurity
  1534. local names_n = #names
  1535.  
  1536. if names_n == 0 then
  1537. return "\1\0\0\0\0\0\0\0"
  1538. end
  1539.  
  1540. local buffer_size = 8
  1541.  
  1542. for _, name in names do
  1543. buffer_size += 4 + #name
  1544. end
  1545.  
  1546. local b = buffer.create(buffer_size)
  1547.  
  1548. buffer.writeu32(b, 0, 1) -- ? [CONSTANT] Version byte (likely)
  1549. buffer.writeu32(b, 4, names_n)
  1550.  
  1551. local offset = 8
  1552.  
  1553. for _, name in names do
  1554. buffer.writeu32(b, offset, #name)
  1555. offset += 4
  1556. end
  1557. for _, name in names do
  1558. buffer.writestring(b, offset, name)
  1559. offset += #name
  1560. end
  1561.  
  1562. return buffer.tostring(b)
  1563. end,
  1564. parent = function(instance)
  1565. local parents = instance:GetParents() -- RobloxScriptSecurity
  1566. local parents_n = #parents
  1567.  
  1568. if parents_n == 0 then
  1569. return "\1\0\0\0\0\0\0\0"
  1570. end
  1571.  
  1572. local b = buffer.create(8 + #parents * 2)
  1573.  
  1574. buffer.writeu32(b, 0, 1) -- ? [CONSTANT] Version byte (likely)
  1575. buffer.writeu32(b, 4, parents_n)
  1576.  
  1577. local offset = 8
  1578.  
  1579. for _, parent in parents do
  1580. buffer.writeu16(b, offset, parent) -- ? likely u8 with \0 (Null) terminators but this is safer in case they overflow beyond 255 values
  1581. offset += 2
  1582. end
  1583.  
  1584. return buffer.tostring(b)
  1585. end,
  1586. postTransform = function(instance)
  1587. return TransformsSerialize(instance:GetPostTransforms()) -- RobloxScriptSecurity
  1588. end,
  1589. preTransform = function(instance)
  1590. return TransformsSerialize(instance:GetPreTransforms()) -- RobloxScriptSecurity
  1591. end,
  1592. transform = function(instance)
  1593. return TransformsSerialize(instance:GetTransforms()) -- RobloxScriptSecurity
  1594. end,
  1595. },
  1596. AudioDeviceInput = {
  1597. AccessList = function(instance) -- CanSave & CanLoad false
  1598. local userid_accesslist = instance:GetUserIdAccessList()
  1599.  
  1600. local uid_n = #userid_accesslist
  1601.  
  1602. if uid_n == 0 then
  1603. return ""
  1604. end
  1605.  
  1606. local b = buffer.create(uid_n * 8)
  1607.  
  1608. local __writei64 = Binary_Descriptors.__writei64
  1609.  
  1610. local offset = 0
  1611. for _, user_id in userid_accesslist do
  1612. __writei64(b, offset, user_id)
  1613. offset += 8
  1614. end
  1615.  
  1616. return buffer.tostring(b)
  1617. end,
  1618. },
  1619. AudioEmitter = {
  1620. AngleAttenuation = function(instance)
  1621. return AttenuationSerialize(instance:GetAngleAttenuation())
  1622. end,
  1623. DistanceAttenuation = function(instance)
  1624. return AttenuationSerialize(instance:GetDistanceAttenuation())
  1625. end,
  1626. },
  1627. AudioListener = {
  1628. AngleAttenuation = function(instance)
  1629. return AttenuationSerialize(instance:GetAngleAttenuation())
  1630. end,
  1631. DistanceAttenuation = function(instance)
  1632. return AttenuationSerialize(instance:GetDistanceAttenuation())
  1633. end,
  1634. },
  1635. -- DebuggerBreakpoint = {line="Line"}, -- ? This shouldn't appear in live games (try to prove this wrong)
  1636. BallSocketConstraint = { MaxFrictionTorqueXml = "MaxFrictionTorque" },
  1637. BasePart = {
  1638. Color3uint8 = "Color",
  1639. MaterialVariantSerialized = "MaterialVariant",
  1640. size = "Size",
  1641. },
  1642. DoubleConstrainedValue = { value = "Value" },
  1643. IntConstrainedValue = { value = "Value" },
  1644.  
  1645. -- CustomEvent = {PersistedCurrentValue=function(instance) -- * Class is Deprecated and :SetValue doesn't seem to affect GetCurrentValue anymore
  1646. -- local Receiver = instance:GetAttachedReceivers()[1]
  1647. -- if Receiver then
  1648. -- return Receiver:GetCurrentValue()
  1649. -- else
  1650. -- error("No Receiver", 2)
  1651. -- end
  1652. -- end},
  1653. Terrain = {
  1654. AcquisitionMethod = "LastUsedModificationMethod", -- ? Not sure, RobloxScriptSecurity
  1655. MaterialColors = function(instance) -- https://github.com/RobloxAPI/spec/blob/master/properties/MaterialColors.md
  1656. local TERRAIN_MATERIAL_COLORS =
  1657. { --https://github.com/rojo-rbx/rbx-dom/blob/master/rbx_dom_lua/src/customProperties.lua#L5
  1658. Enum.Material.Grass,
  1659. Enum.Material.Slate,
  1660. Enum.Material.Concrete,
  1661. Enum.Material.Brick,
  1662. Enum.Material.Sand,
  1663. Enum.Material.WoodPlanks,
  1664. Enum.Material.Rock,
  1665. Enum.Material.Glacier,
  1666. Enum.Material.Snow,
  1667. Enum.Material.Sandstone,
  1668. Enum.Material.Mud,
  1669. Enum.Material.Basalt,
  1670. Enum.Material.Ground,
  1671. Enum.Material.CrackedLava,
  1672. Enum.Material.Asphalt,
  1673. Enum.Material.Cobblestone,
  1674. Enum.Material.Ice,
  1675. Enum.Material.LeafyGrass,
  1676. Enum.Material.Salt,
  1677. Enum.Material.Limestone,
  1678. Enum.Material.Pavement,
  1679. }
  1680.  
  1681. local b = buffer.create(69) -- 69 bytes: 6 reserved + 63 for colors (21 materials * 3 components)
  1682. local offset = 6 -- 6 reserved bytes
  1683.  
  1684. local RGB_components = { "R", "G", "B" }
  1685.  
  1686. for _, material in TERRAIN_MATERIAL_COLORS do
  1687. local color = instance:GetMaterialColor(material)
  1688. for _, component in RGB_components do
  1689. buffer.writeu8(b, offset, math.floor(color[component] * 255)) -- ? math.floor seems unneeded but it makes it faster
  1690. offset += 1
  1691. end
  1692. end
  1693.  
  1694. return buffer.tostring(b)
  1695. end,
  1696. },
  1697. TriangleMeshPart = {
  1698. FluidFidelityInternal = "FluidFidelity",
  1699. },
  1700. MeshPart = { InitialSize = "MeshSize" },
  1701. PartOperation = { InitialSize = "MeshSize" },
  1702. Part = { shape = "Shape" },
  1703. TrussPart = { style = "Style" },
  1704. FormFactorPart = {
  1705. formFactorRaw = "FormFactor",
  1706. },
  1707. Fire = { heat_xml = "Heat", size_xml = "Size" },
  1708. Humanoid = { Health_XML = "Health" },
  1709. HumanoidDescription = {
  1710. EmotesDataInternal = function(instance)
  1711. local emotes_data = ""
  1712. for name, ids in instance:GetEmotes() do
  1713. emotes_data ..= name .. "^" .. table.concat(ids, "^") .. "^\\"
  1714. end
  1715. return emotes_data
  1716. end,
  1717. EquippedEmotesDataInternal = function(instance)
  1718. local equipped_emotes_data = ""
  1719. for _, emote in instance:GetEquippedEmotes() do
  1720. equipped_emotes_data ..= emote.Slot .. "^" .. emote.Name .. "\\"
  1721. end
  1722. return equipped_emotes_data
  1723. end,
  1724. },
  1725. LocalizationTable = {
  1726. Contents = function(instance)
  1727. return instance:GetContents() --service.HttpService:JSONEncode(instance:GetEntries())
  1728. end,
  1729. },
  1730. MaterialService = { Use2022MaterialsXml = "Use2022Materials" }, -- RobloxScriptSecurity
  1731.  
  1732. Model = {
  1733. ScaleFactor = function(instance)
  1734. return instance:GetScale()
  1735. end,
  1736. WorldPivotData = "WorldPivot", -- TODO This doesn't accurately represent whether optional type property is present or not (it's never nil), gethiddenproperty or gethiddenproperty_fallback is preferred
  1737. },
  1738. PackageLink = { PackageIdSerialize = "PackageId", VersionIdSerialize = "VersionNumber" },
  1739. 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
  1740.  
  1741. StarterPlayer = { AvatarJointUpgrade_Serialized = "AvatarJointUpgrade" },
  1742. Smoke = { size_xml = "Size", opacity_xml = "Opacity", riseVelocity_xml = "RiseVelocity" },
  1743. Sound = {
  1744. xmlRead_MaxDistance_3 = "RollOffMaxDistance", -- * Also MaxDistance
  1745. },
  1746. -- ViewportFrame = { -- * Pointless because these reflect CurrentCamera's properties
  1747. -- CameraCFrame = function(instance) -- *
  1748. -- local CurrentCamera = instance.CurrentCamera
  1749. -- if CurrentCamera then
  1750. -- return CurrentCamera.CFrame
  1751. -- else
  1752. -- error("No CurrentCamera", 2)
  1753. -- end
  1754. -- end,
  1755. -- -- CameraFieldOfView =
  1756. -- },
  1757. WeldConstraint = {
  1758. Part0Internal = "Part0",
  1759. Part1Internal = "Part1",
  1760. -- State = function(instance)
  1761. -- -- If untouched then default state is 3 (default true)
  1762. -- return instance.Enabled and 1 or 0
  1763. -- end,
  1764. },
  1765. Workspace = {
  1766. -- SignalBehavior2 = "SignalBehavior", -- * Both are NotScriptable so it doesn't make sense to keep
  1767. CollisionGroupData = function()
  1768. local collision_groups = game:GetService("PhysicsService"):GetRegisteredCollisionGroups()
  1769.  
  1770. local col_groups_n = #collision_groups
  1771.  
  1772. if col_groups_n == 0 then
  1773. return "\1\0"
  1774. end
  1775.  
  1776. local buffer_size = 2 -- Initial size
  1777.  
  1778. for _, group in collision_groups do
  1779. buffer_size += 7 + #group.name
  1780. end
  1781.  
  1782. local b = buffer.create(buffer_size)
  1783.  
  1784. buffer.writeu8(b, 0, 1) -- ? [CONSTANT] Version byte (likely)
  1785. buffer.writeu8(b, 1, col_groups_n) -- Group count
  1786.  
  1787. local TypeID_int32 = attr_Type_IDs["int32"]
  1788. local offset = 2
  1789. for i, group in collision_groups do
  1790. local name, id, mask = group.name, i - 1, group.mask
  1791. local name_len = #name
  1792.  
  1793. buffer.writeu8(b, offset, id) -- ID
  1794. offset += 1
  1795.  
  1796. buffer.writeu8(b, offset, TypeID_int32) -- ? Type ID for int32 (0x04)
  1797. offset += 1
  1798.  
  1799. buffer.writei32(b, offset, mask) -- Mask value as signed 32-bit integer
  1800. offset += 4
  1801.  
  1802. buffer.writeu8(b, offset, name_len) -- Name length
  1803. offset += 1
  1804. buffer.writestring(b, offset, name) -- Name
  1805. offset += name_len
  1806. end
  1807.  
  1808. return buffer.tostring(b)
  1809. end,
  1810. },
  1811. }
  1812.  
  1813. local function FetchAPI()
  1814. -- Credits @MaximumADHD
  1815.  
  1816. local API_Dump
  1817.  
  1818. local ok, err = pcall(function()
  1819. if EXECUTOR_NAME == "Velocity" then -- TODO Temp fix as it crashes on HttpGet for sites that return 403 code
  1820. return
  1821. end
  1822.  
  1823. local CLIENT_VERSION_str = tostring(CLIENT_VERSION)
  1824. local ok, result = pcall(readfile, CLIENT_VERSION_str)
  1825. if
  1826. ok
  1827. and result
  1828. and result ~= ""
  1829. and pcall(service.HttpService.JSONDecode, service.HttpService, result)
  1830. then
  1831. API_Dump = result
  1832. return
  1833. end
  1834.  
  1835. local matching_versions, is_matched = {}
  1836.  
  1837. -- * https://setup.rbxcdn.com/versionQTStudio seems to be a bit behind DeployHistory.txt
  1838. local DeployHistory = string.split(game:HttpGet("https://setup.rbxcdn.com/DeployHistory.txt", true), "\n")
  1839. for i = #DeployHistory, 1, -1 do
  1840. local line = DeployHistory[i]
  1841.  
  1842. local file_version = string.match(line, "file version: ([%d, ]+)")
  1843. if file_version then
  1844. if string.split(file_version, ", ")[2] == CLIENT_VERSION_str then
  1845. is_matched = true
  1846.  
  1847. local version_hash = string.match(line, "(version%-[^%s]+)")
  1848. if version_hash then
  1849. matching_versions[version_hash] = true
  1850. end
  1851. elseif is_matched then
  1852. break
  1853. end
  1854. end
  1855. end
  1856.  
  1857. for version_hash in matching_versions do
  1858. ok, result = pcall(
  1859. game.HttpGet,
  1860. game,
  1861. "https://setup.rbxcdn.com/" .. version_hash .. "-Full-API-Dump.json",
  1862. true
  1863. )
  1864. if ok then
  1865. local o, r = pcall(service.HttpService.JSONDecode, service.HttpService, result)
  1866. if o then
  1867. API_Dump = service.HttpService:JSONEncode(r.Classes) -- minify it
  1868. break
  1869. end
  1870. end
  1871. end
  1872. if writefile then
  1873. writefile(CLIENT_VERSION_str, API_Dump)
  1874. end
  1875. end)
  1876.  
  1877. if not ok or not API_Dump then
  1878. warn("[DEBUG] Failed to get " .. version() .. " version API Dump, trying latest..")
  1879. warn("[DEBUG]", err)
  1880. API_Dump = service.HttpService:JSONEncode(
  1881. service.HttpService:JSONDecode(
  1882. game:HttpGet(
  1883. "https://raw.githubusercontent.com/MaximumADHD/Roblox-Client-Tracker/roblox/Mini-API-Dump.json",
  1884. true
  1885. )
  1886. ).Classes
  1887. )
  1888. end
  1889.  
  1890. local classList = {}
  1891.  
  1892. local ClassesWhitelist, ClassesBlacklist = ClassPropertyExceptions.Whitelist, ClassPropertyExceptions.Blacklist
  1893.  
  1894. for _, API_Class in service.HttpService:JSONDecode(API_Dump) do
  1895. local ClassProperties, ClassProperties_size = {}, 1
  1896. local Class = {
  1897. Properties = ClassProperties,
  1898. Superclass = API_Class.Superclass,
  1899. -- Tags = {},
  1900. NotCreatable = nil,
  1901. }
  1902.  
  1903. local ClassName = API_Class.Name
  1904. local ClassTags = API_Class.Tags
  1905.  
  1906. if ClassTags then
  1907. local Tags = ArrayToDict(ClassTags, nil, nil, "string")
  1908. -- Class.Tags = Tags -- or {}
  1909. Class.NotCreatable = Tags.NotCreatable
  1910. Class.Service = Tags.Service
  1911. end
  1912.  
  1913. local NotScriptableFixClass = NotScriptableFixes[ClassName]
  1914.  
  1915. -- ? Check 96ea8b2a755e55a78aedb55a7de7e83980e11077 commit - If a NotScriptableFix is needed that relies on another NotScriptable Property (which doesn't really make sense in the first place)
  1916.  
  1917. local ClassWhitelist, ClassBlacklist = ClassesWhitelist[ClassName], ClassesBlacklist[ClassName]
  1918.  
  1919. for _, Member in API_Class.Members do
  1920. -- ? print(game:GetService("ReflectionService"):GetPropertyNames("TextBox"))
  1921. if Member.MemberType == "Property" then
  1922. local Serialization = Member.Serialization
  1923.  
  1924. if 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
  1925. --[[
  1926. -- ! CanSave replaces "Tags.Deprecated" check because there are some old properties which are deprecated yet have CanSave.
  1927. 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)
  1928. 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
  1929. This also fixes everything in IgnoreClassProperties automatically without need to hardcode :)
  1930. A very simple fix for many problems that saveinstance scripts encounter!
  1931. --]]
  1932. local PropertyName = Member.Name
  1933. if
  1934. (Serialization.CanSave or ClassWhitelist and ClassWhitelist[PropertyName])
  1935. and not (ClassBlacklist and ClassBlacklist[PropertyName])
  1936. then
  1937. local MemberTags = Member.Tags
  1938.  
  1939. local ValueType = Member.ValueType
  1940. local ValueType_Name = ValueType.Name
  1941.  
  1942. if 645 <= CLIENT_VERSION and ValueType_Name == "Content" then -- TODO: Remove after Roblox adds a descriptor for it
  1943. continue
  1944. end
  1945.  
  1946. local Special, PreferredDescriptorName
  1947.  
  1948. if MemberTags then
  1949. for _, tag in MemberTags do
  1950. if type(tag) == "table" then
  1951. PreferredDescriptorName = tag.PreferredDescriptorName
  1952. if PreferredDescriptorName and Special then
  1953. break
  1954. end
  1955. elseif tag == "NotScriptable" then
  1956. Special = true
  1957. if PreferredDescriptorName then
  1958. break
  1959. end
  1960. end
  1961. end
  1962. end
  1963.  
  1964. -- if not Special then
  1965. local Property = {
  1966. Name = PropertyName,
  1967. Category = ValueType.Category,
  1968. -- Default = Member.Default,
  1969. -- Tags = MemberTags,
  1970. ValueType = ValueType_Name,
  1971.  
  1972. Special = Special,
  1973.  
  1974. CanRead = nil,
  1975. }
  1976.  
  1977. if string.sub(ValueType_Name, 1, 8) == "Optional" then
  1978. -- Extract the string after "Optional"
  1979. Property.Optional = string.sub(ValueType_Name, 9)
  1980. end
  1981.  
  1982. if NotScriptableFixClass then
  1983. local NotScriptableFix = NotScriptableFixClass[PropertyName]
  1984. if NotScriptableFix then
  1985. Property.Fallback = type(NotScriptableFix) == "function" and NotScriptableFix
  1986. or PreferredDescriptorName and function(instance)
  1987. local o, r = pcall(index, instance, PreferredDescriptorName)
  1988. if o then
  1989. return r
  1990. end
  1991. return instance[NotScriptableFix]
  1992. end
  1993. or function(instance)
  1994. return instance[NotScriptableFix]
  1995. end
  1996. end
  1997. elseif PreferredDescriptorName then
  1998. Property.Fallback = function(instance)
  1999. return instance[PreferredDescriptorName]
  2000. end
  2001. end
  2002. ClassProperties[ClassProperties_size] = Property
  2003. ClassProperties_size += 1
  2004.  
  2005. -- end
  2006. end
  2007. end
  2008. end
  2009. end
  2010.  
  2011. classList[ClassName] = Class
  2012. end
  2013.  
  2014. -- classList.Instance.Properties.Parent = nil -- ? Not sure if this is a better option than filtering through properties to remove this
  2015.  
  2016. return classList
  2017. end
  2018.  
  2019. local ok, result = pcall(FetchAPI)
  2020. if ok then
  2021. ClassList = result
  2022. else
  2023. warn("Failed to load the API Dump")
  2024. warn(result)
  2025. return
  2026. end
  2027. end
  2028.  
  2029. local GLOBAL_ENV = getgenv and getgenv() or _G or shared
  2030.  
  2031. --[=[
  2032. @class SynSaveInstance
  2033. Represents the options for saving instances with custom settings using the synsaveinstance function.
  2034. ]=]
  2035.  
  2036. --- @interface CustomOptions table
  2037. --- * Structure of the main CustomOptions table.
  2038. --- * Note: Options are case-insensitive, meaning you could type `NilInstances` option as `nilInStaNces` and it would still be valid.
  2039. --- @within SynSaveInstance
  2040. --- @field __DEBUG_MODE boolean -- This will print debug logs to console about unusual scenarios. Recommended to enable if you wish to help us improve our products and find bugs / issues with it! ___Default:___ false
  2041. --- @field ReadMe boolean --___Default:___ true
  2042. --- @field SafeMode boolean -- Kicks you before Saving, which prevents you from being detected in any game. ___Default:___ false
  2043. --- @field ShutdownWhenDone boolean -- Shuts the game down after saveinstance is finished. ___Default:___ false
  2044. --- @field AntiIdle boolean -- Prevents the 20-minute-Idle Kick. ___Default:___ true
  2045. --- .Anonymous {boolean|table{UserId = string, Name = string}} -- * **RISKY:** Cleans the file of any info related to your account like: Name, UserId. This is useful for some games that might store that info in GUIs or other Instances. Might potentially mess up parts of strings that contain characters that match your Name or parts of numbers that match your UserId. Can also be a table with UserId & Name keys. ___Default:___ false
  2046. --- @field ShowStatus boolean -- ___Default:___ true
  2047. --- @field Callback function -- If set, the serialized data will be sent to the callback function instead of to file. ___Default:___ false
  2048. --- @field mode string -- Valid modes: full, optimized, scripts. Change this to invalid mode like "invalid" if you only want ExtraInstances. "optimized" mode is **NOT** supported with *@Object* option. ___Default:___ `"optimized"`
  2049. --- @field noscripts boolean -- ___Aliases:___ `Decompile`. ___Default:___ false
  2050. --- @field scriptcache boolean -- ___Default:___ true
  2051. --- @field decomptype string -- * Deprecated. ___Default:___ Uses your executor's decompiler, if available.
  2052. --- @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
  2053. --- @field DecompileJobless boolean -- Includes already decompiled code in the output. No new scripts are decompiled. ___Default:___ false
  2054. --- @field SaveBytecode boolean -- Includes bytecode in the output. Useful if you wish to be able to decompile it yourself later. ___Default:___ false
  2055. --- .DecompileIgnore {Instance | Instance.ClassName | [Instance.ClassName] = {Instance.Name}} -- * Ignores match & it's descendants by default. To Ignore only the instance itself set the value to `= false`. Examples: "Chat", - Matches any instance with "Chat" ClassName, Players = {"MyPlayerName"} - Matches "Players" Class AND "MyPlayerName" Name ONLY, `workspace` - matches Instance by reference, `[workspace] = false` - matches Instance by reference and only ignores the instance itself and not it's descendants. ___Default:___ {TextChatService}
  2056. --- .IgnoreList {Instance | Instance.ClassName | [Instance.ClassName] = {Instance.Name}} -- Structure is similar to **@DecompileIgnore** except `= false` meaning if you ignore one instance it will automatically ignore it's descendants. ___Default:___ {CoreGui, CorePackages}
  2057. --- .ExtraInstances {Instance} -- If used with any invalid mode (like "invalidmode") it will only save these instances. ___Default:___ {}
  2058. --- @field IgnoreProperties table -- Ignores properties by Name. ___Default:___ {}
  2059. --- @field SaveCacheInterval number -- The less the value the more often it saves, but that would mean less performance due to constantly saving. ___Default:___ 0x1600 * 10
  2060. --- @field FilePath string -- Must only contain the name of the file, no file extension. ___Default:___ false
  2061. --- @field AvoidFileOverwrite boolean -- Prevents writing to place file that already exists. ___Default:___ true
  2062. --- @field Object Instance -- * 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
  2063. --- @field IsModel boolean -- If Object is specified then sets to true automatically, unless you set it to false. ___Default:___ false
  2064. --- @field NilInstances boolean -- Save instances that aren't Parented (Parented to nil). ___Default:___ false
  2065. --- .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}
  2066. --- @field IgnoreDefaultProperties boolean -- Ignores default properties during saving. ___Default:___ true
  2067. --- @field IgnoreNotArchivable boolean -- Ignores the Archivable property and saves Non-Archivable instances. ___Default:___ true
  2068. --- @field IgnorePropertiesOfNotScriptsOnScriptsMode boolean -- Ignores property of every instance that is not a script in "scripts" mode. ___Default:___ false
  2069. --- @field IgnoreSpecialProperties boolean -- Prevents calls to `gethiddenproperty` and uses fallback methods instead. This also helps with crashes. If your file is corrupted after saving, you can try turning this on. ___Default:___ false
  2070. --- @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
  2071. --- @field IsolateStarterPlayer boolean -- If enabled, StarterPlayer will be cleared and the saved starter player will be placed into folders. ___Default:___ false
  2072. --- @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
  2073. --- @field RemovePlayerCharacters boolean -- Ignore player characters while saving. (Enables SaveNotCreatable automatically). ___Default:___ true
  2074. --- @field SaveNotCreatable 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
  2075. --- .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", "TouchTransmitter" }
  2076. --- @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
  2077. --- @field AlternativeWritefile boolean -- * Splits file content string into segments and writes them using appendfile. This might help with crashes when it starts writing to file. Though there is a risk of appendfile working incorrectly on some executors. ___Default:___ true
  2078. --- @field IgnoreDefaultPlayerScripts boolean -- * **RISKY: Ignores Default PlayerScripts like PlayerModule & RbxCharacterSounds. Prevents crashes on certain Executors. ___Default:___ true
  2079. --- @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
  2080. --- @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
  2081. --- @field TreatUnionsAsParts boolean -- * **RISKY:** Converts all UnionOperations to Parts. Useful if your Executor isn't able to save (read) Unions, because otherwise they will be invisible. ___Default:___ false (except Solara)
  2082.  
  2083. --- @interface OptionsAliases
  2084. --- @within SynSaveInstance
  2085. --- Aliases for the [SynSaveInstance.CustomOptions table]. Format: {Option -- Alias1, Alias2}
  2086. --- @field FilePath string -- FileName
  2087. --- @field IgnoreDefaultProperties string -- IgnoreDefaultProps
  2088. --- @field SaveNotCreatable string -- SaveNonCreatable
  2089. --- @field InstancesBlacklist string -- IgnoreList
  2090. --- @field IsolatePlayerGui string -- IsolateLocalPlayer
  2091. --- @field IsolatePlayers string -- SavePlayers
  2092. --- @field scriptcache string -- DecompileJobless
  2093. --- @field timeout string -- DecompileTimeout
  2094. --- @field IgnoreNotArchivable string -- IgnoreArchivable
  2095. --- @field RemovePlayerCharacters string -- INVERSE SavePlayerCharacters
  2096.  
  2097. --[=[
  2098. @function saveinstance
  2099. Saves instances with specified options. Example:
  2100. ```lua
  2101. local Params = {
  2102. RepoURL = "https://raw.githubusercontent.com/luau/SynSaveInstance/main/",
  2103. SSI = "saveinstance",
  2104. }
  2105.  
  2106. local synsaveinstance = loadstring(game:HttpGet(Params.RepoURL .. Params.SSI .. ".luau", true), Params.SSI)()
  2107.  
  2108. local CustomOptions = { SafeMode = true, timeout = 15, SaveBytecode = true }
  2109.  
  2110. synsaveinstance(CustomOptions)
  2111. ```
  2112. @within SynSaveInstance
  2113. @yields
  2114. @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).
  2115. @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, {})`
  2116. ]=]
  2117.  
  2118. local function synsaveinstance(CustomOptions, CustomOptions2)
  2119. if GLOBAL_ENV.USSI then
  2120. return
  2121. end
  2122. GLOBAL_ENV.USSI = true
  2123. do
  2124. local setthreadidentity = global_container.setthreadidentity
  2125. if setthreadidentity then
  2126. pcall(setthreadidentity, 8) -- ? Arceus X Fix
  2127. end
  2128. end
  2129.  
  2130. local currentstr, currentsize, totalsize, chunks = "", 0, 0, table.create(1)
  2131. local savebuffer, savebuffer_size = {}, 1
  2132. local header =
  2133. '<!-- Saved by UniversalSynSaveInstance (Join to Copy Games) https://discord.gg/wx4ThpAsmw --><roblox version="4">'
  2134.  
  2135. local StatusText
  2136.  
  2137. local OPTIONS = {
  2138. usekonstantdecompiler = false,
  2139. mode = "optimized",
  2140. noscripts = false,
  2141. scriptcache = true,
  2142. -- decomptype = "",
  2143. timeout = 10,
  2144. -- * New:
  2145. __DEBUG_MODE = false,
  2146.  
  2147. -- Binary = false, -- true in syn newer versions (false in our case because no binary support yet), Description: Saves everything in Binary Mode (rbxl/rbxm).
  2148. Callback = false,
  2149. --Clipboard/CopyToClipboard = 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. (Binary Only)
  2150. -- MaxThreads = 3 -- Description: The number of decompilation threads that can run at once. More threads means it can decompile for scripts at a time.
  2151. -- DisableCompression = false, --Description: Disables compression in the binary output
  2152.  
  2153. DecompileJobless = false,
  2154. DecompileIgnore = { -- * Clean these up (merged Old Syn and New Syn)
  2155. -- "Chat",
  2156. "TextChatService",
  2157. ModuleScript = nil,
  2158. },
  2159. IgnoreDefaultPlayerScripts = true,
  2160. SaveBytecode = false,
  2161.  
  2162. IgnoreProperties = {},
  2163.  
  2164. IgnoreList = { "CoreGui", "CorePackages" },
  2165.  
  2166. ExtraInstances = {},
  2167. NilInstances = false,
  2168. NilInstancesFixes = {},
  2169.  
  2170. SaveCacheInterval = 0x1600 * 10,
  2171. ShowStatus = true,
  2172. SafeMode = false,
  2173. ShutdownWhenDone = false,
  2174. AntiIdle = true,
  2175. Anonymous = false,
  2176. ReadMe = true,
  2177. FilePath = false,
  2178. AvoidFileOverwrite = true,
  2179. Object = false,
  2180. IsModel = false,
  2181.  
  2182. IgnoreDefaultProperties = true,
  2183. IgnoreNotArchivable = true,
  2184. IgnorePropertiesOfNotScriptsOnScriptsMode = false,
  2185. IgnoreSpecialProperties = false,
  2186.  
  2187. IsolateLocalPlayer = false, -- #service.StarterGui:GetChildren() == 0
  2188. IsolateLocalPlayerCharacter = false,
  2189. IsolatePlayers = false,
  2190. IsolateStarterPlayer = false,
  2191. RemovePlayerCharacters = true,
  2192.  
  2193. SaveNotCreatable = false,
  2194. NotCreatableFixes = {
  2195. -- "InputObject",
  2196. -- "LodDataEntity",
  2197. -- "Path",
  2198. "", -- * FilteredSelection
  2199. "AdvancedDragger",
  2200. "AnimationTrack",
  2201. "Dragger",
  2202. "Player",
  2203. "PlayerGui",
  2204. "PlayerMouse",
  2205. "PlayerMouse",
  2206. "PlayerScripts",
  2207. "ScreenshotHud",
  2208. "StudioData",
  2209. "TextChatMessage",
  2210. "TextSource",
  2211. "TouchTransmitter",
  2212. "Translator",
  2213. CloudLocalizationTable = "LocalizationTable",
  2214. Platform = "Part",
  2215. Status = "Model", -- gets created by studio automatically usually so there will be duplicates due to this
  2216. -- CoreScript = "Script", -- useless
  2217. -- ChatWindowMessageProperties = "TextChatMessageProperties", -- ? Not needed as this can be derived with ChatWindowConfiguration.DeriveNewMessageProperties
  2218. },
  2219.  
  2220. -- ! Risky
  2221.  
  2222. IgnoreSharedStrings = EXECUTOR_NAME ~= "Wave",
  2223. SharedStringOverwrite = false,
  2224. TreatUnionsAsParts = EXECUTOR_NAME == "Solara", -- TODO Temporary true (once removed, remove Note from docs too)
  2225. AlternativeWritefile = not ArrayToDict({ "WRD", "Xeno", "Zorara" })[EXECUTOR_NAME],
  2226.  
  2227. OptionsAliases = { -- You can't really modify these as a user
  2228. DecompileTimeout = "timeout",
  2229. FileName = "FilePath",
  2230. IgnoreArchivable = "IgnoreNotArchivable",
  2231. IgnoreDefaultProps = "IgnoreDefaultProperties",
  2232. InstancesBlacklist = "IgnoreList",
  2233. IsolatePlayerGui = "IsolateLocalPlayer",
  2234. SaveNonCreatable = "SaveNotCreatable",
  2235. SavePlayers = "IsolatePlayers",
  2236. },
  2237. }
  2238.  
  2239. local OPTIONS_lowercase, CustomOptions_valid = {}, {}
  2240. for option_name in OPTIONS do
  2241. local option_name_lowercase = string.lower(option_name)
  2242. if OPTIONS_lowercase[option_name_lowercase] then
  2243. warn("DUPLICATE OPTION", option_name)
  2244. else
  2245. OPTIONS_lowercase[option_name_lowercase] = option_name
  2246. end
  2247. end
  2248. for option_alias, option_name in OPTIONS.OptionsAliases do
  2249. local option_name_lowercase = string.lower(option_alias)
  2250. if OPTIONS_lowercase[option_name_lowercase] then
  2251. warn("DUPLICATE ALIAS", option_alias)
  2252. else
  2253. OPTIONS_lowercase[option_name_lowercase] = option_name
  2254. end
  2255. end
  2256.  
  2257. do -- * Load Settings
  2258. local function construct_NilinstanceFix(Name, ClassName, Separate)
  2259. return function(instance, instancePropertyOverrides)
  2260. local Exists
  2261.  
  2262. if not Separate then
  2263. Exists = OPTIONS.NilInstancesFixes[Name]
  2264. end
  2265.  
  2266. local Fix
  2267.  
  2268. local DoesntExist = not Exists
  2269. if DoesntExist then
  2270. Fix = Instance.new(ClassName)
  2271. if not Separate then
  2272. OPTIONS.NilInstancesFixes[Name] = Fix
  2273. end
  2274. -- Fix.Name = Name
  2275.  
  2276. instancePropertyOverrides[Fix] =
  2277. { __SaveSpecific = true, __Children = { instance }, Properties = { Name = Name } }
  2278. else
  2279. Fix = Exists
  2280. table.insert(instancePropertyOverrides[Fix].__Children, instance)
  2281. end
  2282.  
  2283. -- InstancesOverrides[instance].Parent = AnimationController
  2284. if DoesntExist then
  2285. return Fix
  2286. end
  2287. end
  2288. end
  2289.  
  2290. -- TODO: Merge BaseWrap & Attachment & AdPortal fix (put all under MeshPart container)
  2291. -- TODO?:
  2292. -- DebuggerWatch DebuggerWatch must be a child of ScriptDebugger
  2293. -- PluginAction Parent of PluginAction must be Plugin or PluginMenu that created it!
  2294. OPTIONS.NilInstancesFixes.Animator = construct_NilinstanceFix(
  2295. "Animator has to be placed under Humanoid or AnimationController",
  2296. "AnimationController"
  2297. )
  2298. OPTIONS.NilInstancesFixes.AdPortal = construct_NilinstanceFix("AdPortal must be parented to a Part", "Part")
  2299. OPTIONS.NilInstancesFixes.Attachment =
  2300. construct_NilinstanceFix("Attachments must be parented to a BasePart or another Attachment", "Part") -- * Bones inherit from Attachments
  2301. OPTIONS.NilInstancesFixes.BaseWrap =
  2302. construct_NilinstanceFix("BaseWrap must be parented to a MeshPart", "MeshPart")
  2303. OPTIONS.NilInstancesFixes.PackageLink =
  2304. construct_NilinstanceFix("Package already has a PackageLink", "Folder", true)
  2305.  
  2306. if CustomOptions2 and type(CustomOptions2) == "table" then
  2307. local tmp = CustomOptions
  2308. local Type = typeof(tmp)
  2309. CustomOptions = CustomOptions2
  2310. if Type == "Instance" then
  2311. CustomOptions.Object = tmp
  2312. elseif Type == "table" and typeof(tmp[1]) == "Instance" then
  2313. CustomOptions.ExtraInstances = tmp
  2314. OPTIONS.IsModel = true
  2315. end
  2316. end
  2317.  
  2318. local Type = typeof(CustomOptions)
  2319.  
  2320. if Type == "table" then
  2321. if typeof(CustomOptions[1]) == "Instance" then
  2322. OPTIONS.mode = "invalidmode"
  2323. OPTIONS.ExtraInstances = CustomOptions
  2324. OPTIONS.IsModel = true
  2325. CustomOptions = {}
  2326. else
  2327. for key, value in CustomOptions do
  2328. local option = OPTIONS_lowercase[string.lower(key)]
  2329.  
  2330. if option then
  2331. OPTIONS[option] = value
  2332. CustomOptions_valid[option] = true
  2333. end
  2334. end
  2335. local Decompile = CustomOptions.Decompile
  2336. if Decompile ~= nil then
  2337. OPTIONS.noscripts = not Decompile
  2338. end
  2339. local SavePlayerCharacters = CustomOptions.SavePlayerCharacters
  2340. if SavePlayerCharacters ~= nil then
  2341. OPTIONS.RemovePlayerCharacters = not SavePlayerCharacters
  2342. end
  2343. local RemovePlayers = CustomOptions.RemovePlayers
  2344. if RemovePlayers ~= nil then
  2345. OPTIONS.IsolatePlayers = not RemovePlayers
  2346. end
  2347. end
  2348. elseif Type == "Instance" then
  2349. OPTIONS.mode = "invalidmode"
  2350. OPTIONS.Object = CustomOptions
  2351. CustomOptions = {}
  2352. else
  2353. CustomOptions = {}
  2354. end
  2355. end
  2356.  
  2357. if not writefile and not OPTIONS.Callback then
  2358. -- appendfile could be used a fallback but what kind of exec has appendfile without writefile
  2359. local function coreCall(method, ...)
  2360. local StarterGui = service.StarterGui
  2361. method = StarterGui[method]
  2362. if not method then
  2363. return
  2364. end
  2365.  
  2366. for _ = 1, 10 do -- maxtries
  2367. local success, result = pcall(method, StarterGui, ...)
  2368. if success then
  2369. return result
  2370. end
  2371. task.wait(1)
  2372. end
  2373. end
  2374.  
  2375. local text = 'Function "writefile" is NOT available\nUse the Option "Callback" instead for now (check docs)'
  2376.  
  2377. coreCall("SetCore", "SendNotification", {
  2378. Title = "SAVEINSTANCE ERROR",
  2379. Text = text,
  2380. Duration = 15,
  2381. Icon = "rbxassetid://9072920609",
  2382. })
  2383. coreCall("SetCore", "SendNotification", {
  2384. Title = "SAVEINSTANCE ERROR",
  2385. Text = "Please ask your executor's developers to add writefile",
  2386. Duration = 15,
  2387. Icon = "rbxassetid://9072920609",
  2388. })
  2389.  
  2390. warn(text)
  2391.  
  2392. GLOBAL_ENV.USSI = nil
  2393. return
  2394. end
  2395.  
  2396. if OPTIONS.IgnoreDefaultPlayerScripts then
  2397. -- TODO This is a bad workaround, find a better automatic way
  2398. -- TODO Look into https://robloxapi.github.io/ref/class/LuaSourceContainer.html#member-isPlayerScript
  2399. local DecompileIgnore = OPTIONS.DecompileIgnore
  2400.  
  2401. local default_scripts = ArrayToDict({
  2402. ModuleScript = { "PlayerModule" },
  2403. LocalScript = {
  2404. "BubbleChat",
  2405. "ChatScript",
  2406. "PlayerScriptsLoader",
  2407. "RbxCharacterSounds",
  2408. },
  2409. }, true)
  2410.  
  2411. local function ignorePath(path)
  2412. if path then
  2413. for _, child in path:GetChildren() do
  2414. local class_match = default_scripts[child.ClassName]
  2415. if class_match then
  2416. local name_match = class_match[child.Name]
  2417. if name_match then
  2418. table.insert(DecompileIgnore, child)
  2419. end
  2420. end
  2421. end
  2422. end
  2423. end
  2424.  
  2425. ignorePath(service.StarterPlayer:FindFirstChildOfClass("StarterPlayerScripts"))
  2426.  
  2427. local LocalPlayer = service.Players.LocalPlayer
  2428. if LocalPlayer then
  2429. ignorePath(LocalPlayer:FindFirstChildOfClass("PlayerScripts"))
  2430. end
  2431. end
  2432.  
  2433. local InstancesOverrides = {}
  2434.  
  2435. local DecompileIgnore, IgnoreList, IgnoreProperties, NotCreatableFixes =
  2436. ArrayToDict(OPTIONS.DecompileIgnore, true),
  2437. ArrayToDict(OPTIONS.IgnoreList, true),
  2438. ArrayToDict(OPTIONS.IgnoreProperties),
  2439. ArrayToDict(OPTIONS.NotCreatableFixes, true, "Folder")
  2440.  
  2441. local __DEBUG_MODE = OPTIONS.__DEBUG_MODE
  2442.  
  2443. if __DEBUG_MODE and type(__DEBUG_MODE) ~= "function" then
  2444. __DEBUG_MODE = warn
  2445. end
  2446.  
  2447. local FilePath = OPTIONS.FilePath
  2448. local SaveCacheInterval = OPTIONS.SaveCacheInterval
  2449. local ToSaveInstance = OPTIONS.Object
  2450. local IsModel = OPTIONS.IsModel
  2451.  
  2452. if ToSaveInstance and CustomOptions.IsModel == nil then
  2453. IsModel = true
  2454. end
  2455.  
  2456. local IgnoreDefaultProperties = OPTIONS.IgnoreDefaultProperties
  2457. local IgnoreNotArchivable = not OPTIONS.IgnoreNotArchivable
  2458. local IgnorePropertiesOfNotScriptsOnScriptsMode = OPTIONS.IgnorePropertiesOfNotScriptsOnScriptsMode
  2459.  
  2460. local old_gethiddenproperty
  2461. if OPTIONS.IgnoreSpecialProperties and gethiddenproperty then
  2462. old_gethiddenproperty = gethiddenproperty
  2463. gethiddenproperty = nil
  2464. end
  2465.  
  2466. local SaveNotCreatable = OPTIONS.SaveNotCreatable
  2467. local TreatUnionsAsParts = OPTIONS.TreatUnionsAsParts
  2468.  
  2469. local DecompileJobless = OPTIONS.DecompileJobless
  2470. if DecompileJobless then
  2471. OPTIONS.scriptcache = true
  2472. end
  2473. local ScriptCache = OPTIONS.scriptcache and getscriptbytecode
  2474.  
  2475. local Timeout = OPTIONS.timeout
  2476.  
  2477. local IgnoreSharedStrings = OPTIONS.IgnoreSharedStrings
  2478. local SharedStringOverwrite = OPTIONS.SharedStringOverwrite
  2479.  
  2480. local ldeccache = GLOBAL_ENV.scriptcache
  2481.  
  2482. local DecompileIgnoring, ToSaveList, ldecompile, placename, elapse_t, SaveNotCreatableWillBeEnabled, RecoveredScripts
  2483.  
  2484. if OPTIONS.ReadMe then
  2485. RecoveredScripts = {}
  2486. end
  2487.  
  2488. if ScriptCache and not ldeccache then
  2489. ldeccache = {}
  2490. GLOBAL_ENV.scriptcache = ldeccache
  2491. end
  2492.  
  2493. if ToSaveInstance == game then
  2494. OPTIONS.mode = "full"
  2495. ToSaveInstance = nil
  2496. IsModel = nil
  2497. end
  2498.  
  2499. local function isLuaSourceContainer(instance)
  2500. return instance:IsA("LuaSourceContainer")
  2501. end
  2502.  
  2503. do
  2504. local mode = string.lower(OPTIONS.mode)
  2505. local tmp = table.clone(OPTIONS.ExtraInstances)
  2506.  
  2507. local PlaceName = game.PlaceId
  2508.  
  2509. pcall(function()
  2510. PlaceName ..= " " .. service.MarketplaceService:GetProductInfo(PlaceName).Name .. "_" .. os.date("%m_%d_%Y_%I_%M%p"):lower()
  2511. end)
  2512.  
  2513. local function sanitizeFileName(str)
  2514. return string.sub(string.gsub(string.gsub(string.gsub(str, "[^%w _]", ""), " +", " "), " +$", ""), 1, 240)
  2515. end
  2516.  
  2517. if ToSaveInstance then
  2518. if mode == "optimized" then -- ! NOT supported with Model file mode
  2519. mode = "full"
  2520. end
  2521.  
  2522. for _, key in
  2523. {
  2524. "IsolateLocalPlayer",
  2525. "IsolateLocalPlayerCharacter",
  2526. "IsolatePlayers",
  2527. "IsolateStarterPlayer",
  2528. "NilInstances",
  2529. }
  2530. do
  2531. if CustomOptions_valid[key] == nil then
  2532. OPTIONS[key] = false
  2533. end
  2534. end
  2535. end
  2536.  
  2537. local filetype = IsModel and ".rbxmx" or ".rbxlx"
  2538.  
  2539. if FilePath then
  2540. placename = FilePath
  2541. elseif IsModel then
  2542. placename =
  2543. sanitizeFileName("model " .. PlaceName .. " " .. (ToSaveInstance or tmp[1] or game):GetFullName())
  2544. else
  2545. placename = sanitizeFileName("place " .. PlaceName)
  2546. end
  2547.  
  2548. if OPTIONS.AvoidFileOverwrite and isfile then
  2549. local counter = 0
  2550. local temp = placename
  2551.  
  2552. while isfile(temp .. filetype) do
  2553. counter += 1
  2554. temp = placename .. "(" .. counter .. ")"
  2555. end
  2556.  
  2557. placename = temp .. filetype
  2558. else
  2559. placename = placename .. filetype
  2560. end
  2561.  
  2562. if GLOBAL_ENV[placename] then
  2563. -- warn("UniversalSynSaveInstance is already saving to this file")
  2564. return
  2565. end
  2566.  
  2567. GLOBAL_ENV[placename] = true
  2568. GLOBAL_ENV.USSI = nil
  2569.  
  2570. if mode ~= "scripts" then
  2571. IgnorePropertiesOfNotScriptsOnScriptsMode = nil
  2572. end
  2573.  
  2574. local TempRoot = ToSaveInstance or game
  2575.  
  2576. if mode == "full" then
  2577. if not ToSaveInstance then
  2578. local Children = TempRoot:GetChildren()
  2579. if 0 < #Children then
  2580. local tmp_dict = ArrayToDict(tmp)
  2581. for _, child in Children do
  2582. if not tmp_dict[child] then
  2583. table.insert(tmp, child)
  2584. end
  2585. end
  2586. end
  2587. end
  2588. elseif mode == "optimized" then -- ! Incompatible with .rbxmx (Model file) mode
  2589. -- if IsolatePlayers then
  2590. -- table.insert(_list_0, "Players")
  2591. -- end
  2592. local tmp_dict = ArrayToDict(tmp)
  2593.  
  2594. for _, serviceName in
  2595. {
  2596. "Workspace",
  2597. "Players",
  2598. "Lighting",
  2599. "MaterialService",
  2600. "ReplicatedFirst",
  2601. "ReplicatedStorage",
  2602.  
  2603. "ServerScriptService", -- LoadStringEnabled property (doesn't replicate); Just in case
  2604. "ServerStorage", -- Just in case
  2605.  
  2606. "StarterGui",
  2607. "StarterPack",
  2608. "StarterPlayer",
  2609. "Teams",
  2610. "SoundService",
  2611. "Chat",
  2612. "TextChatService",
  2613.  
  2614. "LocalizationService", -- For LocalizationTables
  2615. -- "InsertService",
  2616. "JointsService",
  2617.  
  2618. -- "TestService",
  2619. -- "VoiceChatService",
  2620. }
  2621. do
  2622. local _service = game:FindService(serviceName)
  2623. if _service and not tmp_dict[_service] then
  2624. table.insert(tmp, _service)
  2625. end
  2626. end
  2627. elseif mode == "scripts" then
  2628. -- TODO: Only save paths that lead to scripts (nothing else)
  2629. -- Currently saves paths along with children of each tree
  2630. local unique = {}
  2631. for _, instance in TempRoot:GetDescendants() do
  2632. if isLuaSourceContainer(instance) then
  2633. local Parent = instance.Parent
  2634. while Parent and Parent ~= TempRoot do
  2635. instance = instance.Parent
  2636. Parent = instance.Parent
  2637. end
  2638. if Parent then
  2639. unique[instance] = true
  2640. end
  2641. end
  2642. end
  2643. for instance in unique do
  2644. table.insert(tmp, instance)
  2645. end
  2646. end
  2647.  
  2648. ToSaveList = tmp
  2649.  
  2650. if ToSaveInstance then
  2651. table.insert(ToSaveList, 1, ToSaveInstance)
  2652. end
  2653. end
  2654.  
  2655. local IsolateLocalPlayer = OPTIONS.IsolateLocalPlayer
  2656. local IsolateLocalPlayerCharacter = OPTIONS.IsolateLocalPlayerCharacter
  2657. local IsolatePlayers = OPTIONS.IsolatePlayers
  2658. local IsolateStarterPlayer = OPTIONS.IsolateStarterPlayer
  2659. local NilInstances = OPTIONS.NilInstances
  2660.  
  2661. if NilInstances and enablenilinstances then -- ? Solara fix
  2662. enablenilinstances()
  2663. end
  2664. local function get_size_format()
  2665. local Size
  2666.  
  2667. -- local totalsize = #totalstr
  2668.  
  2669. for i, unit in
  2670. {
  2671. "B",
  2672. "KB",
  2673. "MB",
  2674. "GB",
  2675. "TB",
  2676. }
  2677. do
  2678. if totalsize < 0x400 ^ i then
  2679. Size = math.floor(totalsize / (0x400 ^ (i - 1)) * 10) / 10 .. " " .. unit
  2680. break
  2681. end
  2682. end
  2683.  
  2684. return Size
  2685. end
  2686.  
  2687. local RunService = service.RunService
  2688. local function wait_for_render()
  2689. RunService.RenderStepped:Wait()
  2690. end
  2691.  
  2692. local Loading
  2693. local function run_with_loading(text, keepStatus, waitForRender, taskFunction, ...)
  2694. local previousStatus
  2695.  
  2696. if StatusText then
  2697. if keepStatus then
  2698. previousStatus = StatusText.Text
  2699. end
  2700. Loading = task.spawn(function()
  2701. local spinner_count = 0
  2702. local chars = { "|", "/", "—", "\\" }
  2703. local chars_size = #chars
  2704.  
  2705. local function getLoadingText()
  2706. spinner_count += 1
  2707.  
  2708. if chars_size < spinner_count then
  2709. spinner_count = 1
  2710. end
  2711.  
  2712. return chars[spinner_count]
  2713. end
  2714.  
  2715. text ..= " "
  2716.  
  2717. while true do
  2718. StatusText.Text = text .. getLoadingText()
  2719. task.wait(0.25)
  2720. end
  2721. end)
  2722. if waitForRender then
  2723. wait_for_render()
  2724. end
  2725. end
  2726.  
  2727. local result = { taskFunction(...) }
  2728.  
  2729. if Loading then
  2730. task.cancel(Loading)
  2731. Loading = nil
  2732. if previousStatus then
  2733. StatusText.Text = previousStatus
  2734. end
  2735. end
  2736.  
  2737. return unpack(result)
  2738. end
  2739.  
  2740. local function construct_TimeoutHandler(timeout, f, timeout_ret)
  2741. return timeout < 0 and function(script)
  2742. return pcall(f, script)
  2743. end or function(script) -- TODO Ideally use ... (vararg) instead of `script` in case this is reused for something other than `decompile` & `getscriptbytecode`
  2744. local thread = coroutine.running()
  2745. local timeoutThread, isCancelled
  2746.  
  2747. timeoutThread = task.delay(timeout, function()
  2748. isCancelled = true -- TODO task.cancel
  2749. coroutine.resume(thread, nil, timeout_ret)
  2750. end)
  2751.  
  2752. task.spawn(function()
  2753. local ok, result = pcall(f, script)
  2754.  
  2755. if isCancelled then
  2756. return
  2757. end
  2758.  
  2759. task.cancel(timeoutThread)
  2760.  
  2761. while coroutine.status(thread) ~= "suspended" do
  2762. task.wait()
  2763. end
  2764.  
  2765. coroutine.resume(thread, ok, result)
  2766. end)
  2767.  
  2768. return coroutine.yield()
  2769. end
  2770. end
  2771.  
  2772. local getbytecode
  2773. if getscriptbytecode then
  2774. getbytecode = construct_TimeoutHandler(3, getscriptbytecode) -- ? Solara fix
  2775. end
  2776.  
  2777. local SaveBytecode
  2778. if OPTIONS.SaveBytecode and getscriptbytecode then
  2779. SaveBytecode = function(script)
  2780. local s, bytecode = getbytecode(script)
  2781.  
  2782. if s and bytecode and bytecode ~= "" then
  2783. return "-- Bytecode (Base64):\n-- " .. base64encode(bytecode) .. "\n\n"
  2784. end
  2785. end
  2786. end
  2787.  
  2788. do
  2789. local Decompiler = decompile
  2790.  
  2791. if OPTIONS.noscripts then
  2792. ldecompile = function()
  2793. return "-- Decompiling is disabled"
  2794. end
  2795. elseif Decompiler then
  2796. local decomp = construct_TimeoutHandler(Timeout, Decompiler, "Decompiler timed out")
  2797.  
  2798. ldecompile = function(script)
  2799. -- local name = scr.ClassName .. scr.Name
  2800. local bytecode
  2801. if ScriptCache then
  2802. local s
  2803. s, bytecode = getbytecode(script)
  2804. local cached
  2805.  
  2806. if s then
  2807. if not bytecode or bytecode == "" then
  2808. return "-- The Script is Empty"
  2809. end
  2810. cached = ldeccache[bytecode]
  2811. else
  2812. bytecode = nil
  2813. end
  2814.  
  2815. if cached then
  2816. if __DEBUG_MODE then
  2817. __DEBUG_MODE("Found in Cache", script:GetFullName())
  2818. end
  2819. return cached
  2820. end
  2821. else
  2822. if DecompileJobless then
  2823. return "-- Not found in already decompiled ScriptCache"
  2824. end
  2825.  
  2826. task.wait() -- TODO Maybe remove?
  2827. end
  2828.  
  2829. local ok, result
  2830. if OPTIONS.usekonstantdecompiler then
  2831. ok, result = pcall(function()
  2832. return konstantdecompiler.decompile(bytecode)
  2833. end)
  2834. else
  2835. ok, result = run_with_loading("Decompiling " .. script.Name, true, nil, decomp, script)
  2836. end
  2837.  
  2838. if not result then
  2839. ok, result = false, "Empty Output"
  2840. end
  2841.  
  2842. local output
  2843. if ok then
  2844. result = string.gsub(result, "\0", "\\0") -- ? Some decompilers sadly output \0 which prevents files from opening
  2845. output = result
  2846. else
  2847. output = "--[[ Failed to decompile. Reason:\n" .. (result or "") .. "\n]]"
  2848. end
  2849.  
  2850. if ScriptCache and 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
  2851. ldeccache[bytecode] = output -- ? Should we cache even if it timed out?
  2852. if __DEBUG_MODE then
  2853. __DEBUG_MODE("Cached", script:GetFullName())
  2854. end
  2855. end
  2856.  
  2857. return output
  2858. end
  2859. else
  2860. ldecompile = function()
  2861. return "-- Your Executor does NOT have a Decompiler"
  2862. end
  2863. end
  2864. end
  2865.  
  2866. local function GetLocalPlayer()
  2867. return service.Players.LocalPlayer
  2868. or service.Players:GetPropertyChangedSignal("LocalPlayer"):Wait()
  2869. or service.Players.LocalPlayer
  2870. end
  2871.  
  2872. local function filterLinkedSource(str)
  2873. local o, r = pcall(service.HttpService.JSONDecode, service.HttpService, str)
  2874. if o and r.errors then
  2875. return
  2876. end
  2877. return true
  2878. end
  2879.  
  2880. local function replaceClassName(instance, InstanceName, ClassName)
  2881. local InstanceOverride
  2882. if InstanceName ~= ClassName then -- TODO Compare against default instance instead (TouchTransmitter is called TouchInterest by default)
  2883. InstanceOverride = InstancesOverrides[instance]
  2884. if not InstanceOverride then
  2885. InstanceOverride = { Properties = { Name = "[" .. ClassName .. "] " .. InstanceName } }
  2886. InstancesOverrides[instance] = InstanceOverride
  2887. end
  2888. end
  2889. return InstanceOverride
  2890. end
  2891.  
  2892. local function filterPropVal(result, propertyName, category) -- ? raw == nil thanks to SerializedDefaultAttributes; "can't get value" - due to WriteOnly tag; "Invalid value for enum " - "StreamingPauseMode" (old games probably) Roexec
  2893. return result == nil
  2894. or result == "can't get value"
  2895. or type(result) == "string"
  2896. and (category == "Enum" or string_find(result, "Unable to get property " .. propertyName))
  2897. end
  2898.  
  2899. local __BREAK = "__BREAK" .. service.HttpService:GenerateGUID(false)
  2900.  
  2901. local function ReadProperty(instance, property, propertyName, special, category, optional)
  2902. local raw = __BREAK
  2903.  
  2904. local InstanceOverride = InstancesOverrides[instance]
  2905. if InstanceOverride then
  2906. local PropertiesOverride = InstanceOverride.Properties
  2907. if PropertiesOverride then
  2908. local PropertyOverride = PropertiesOverride[propertyName]
  2909. if PropertyOverride ~= nil then
  2910. return PropertyOverride
  2911. end
  2912. end
  2913. end
  2914.  
  2915. local CanRead = property.CanRead
  2916.  
  2917. if CanRead == false then -- * Skips because we've checked this property before
  2918. return __BREAK
  2919. end
  2920.  
  2921. if special then
  2922. if gethiddenproperty then
  2923. local ok, result = pcall(gethiddenproperty, instance, propertyName)
  2924.  
  2925. if ok then
  2926. raw = result
  2927. end
  2928.  
  2929. if filterPropVal(raw, propertyName, category) then
  2930. -- * Skip next time we encounter this too perhaps (unless there's a chance for it to be readable on other instance, somehow)
  2931.  
  2932. if result ~= nil or not optional then
  2933. if __DEBUG_MODE then
  2934. __DEBUG_MODE("Filtered", propertyName)
  2935. end
  2936. -- Property.Special = false
  2937. property.CanRead = false
  2938. end
  2939.  
  2940. return __BREAK -- ? 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)
  2941. end
  2942. end
  2943. else
  2944. if CanRead then
  2945. raw = instance[propertyName]
  2946. else -- Assuming CanRead == nil (untested)
  2947. local ok, result = pcall(index, instance, propertyName)
  2948.  
  2949. if ok then
  2950. raw = result
  2951. elseif gethiddenproperty then -- ! Be careful with this 'and gethiddenproperty' logic
  2952. ok, result = pcall(gethiddenproperty, instance, propertyName)
  2953.  
  2954. if ok then
  2955. raw = result
  2956.  
  2957. property.Special = true
  2958. end
  2959. end
  2960.  
  2961. property.CanRead = ok
  2962.  
  2963. if not ok or filterPropVal(raw, propertyName, category) then
  2964. return __BREAK
  2965. end
  2966. end
  2967. end
  2968.  
  2969. return raw
  2970. end
  2971.  
  2972. local function ReturnItem(className, instance)
  2973. return '<Item class="' .. className .. '" referent="' .. GetRef(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%)
  2974. end
  2975.  
  2976. local function ReturnProperty(tag, propertyName, value)
  2977. return "<" .. tag .. ' name="' .. propertyName .. '">' .. value .. "</" .. tag .. ">"
  2978. end
  2979.  
  2980. local function ReturnValueAndTag(raw, valueType, descriptor)
  2981. local value, tag = (descriptor or XML_Descriptors[valueType])(raw)
  2982.  
  2983. return value, tag or valueType
  2984. end
  2985.  
  2986. local function InheritsFix(fixes, className, instance)
  2987. local Fix = fixes[className]
  2988. if Fix then
  2989. return Fix
  2990. elseif Fix == nil then
  2991. for class_name, fix in fixes do
  2992. if instance:IsA(class_name) then
  2993. return fix
  2994. end
  2995. end
  2996. end
  2997. end
  2998.  
  2999. local function GetInheritedProps(className)
  3000. local cached = inherited_properties[className]
  3001. if cached then
  3002. return cached
  3003. end
  3004.  
  3005. local prop_list = {}
  3006. local layer = ClassList[className]
  3007. while layer do
  3008. local layer_props = layer.Properties
  3009. table.move(layer_props, 1, #layer_props, #prop_list + 1, prop_list)
  3010.  
  3011. -- for _, prop in layer.Properties do
  3012. -- prop_list[prop_count] = prop -- ? table.clone is needed for case where .Default is modified
  3013. -- prop_count += 1
  3014. -- end
  3015.  
  3016. layer = ClassList[layer.Superclass]
  3017. end
  3018. inherited_properties[className] = prop_list
  3019. return prop_list
  3020. end
  3021.  
  3022. local CHUNK_LIMIT = 200 * 1024 * 1024 -- string length overflow prevention
  3023. local function save_cache(final)
  3024. local savestr = table.concat(savebuffer)
  3025. currentstr ..= savestr -- TODO: Causes "not enough memory" error on some exec
  3026.  
  3027. -- writefile(placename, totalstr)
  3028. -- appendfile(placename, savestr) -- * 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?
  3029. local savestr_len = #savestr
  3030. totalsize += savestr_len
  3031. currentsize += savestr_len
  3032.  
  3033. table.clear(savebuffer)
  3034. savebuffer_size = 1
  3035.  
  3036. if CHUNK_LIMIT < currentsize or final then
  3037. table.insert(chunks, { size = currentsize, str = currentstr })
  3038. currentstr, currentsize = "", 0
  3039. end
  3040.  
  3041. if StatusText then
  3042. StatusText.Text = "Saving.. Size: " .. get_size_format()
  3043. end
  3044. -- ? Needed for at least 1fps (status text)
  3045. -- task.wait()
  3046. wait_for_render()
  3047. end
  3048.  
  3049. local function save_specific(className, properties)
  3050. local Ref = Instance.new(className) -- ! Assuming anything passed here is Creatable
  3051. local Item = ReturnItem(Ref.ClassName, Ref)
  3052.  
  3053. for propertyName, val in properties do
  3054. local whitelisted, value, tag
  3055.  
  3056. -- TODO: Improve all sort of overrides & exceptions in the code (code below is awful)
  3057. if "Source" == propertyName then
  3058. tag = "ProtectedString"
  3059. value = XML_Descriptors.__PROTECTEDSTRING(val)
  3060. whitelisted = true
  3061. elseif "Name" == propertyName then
  3062. whitelisted = true
  3063. value, tag = ReturnValueAndTag(val, "string") -- * Doubt ValueType will change
  3064. end
  3065.  
  3066. if whitelisted then
  3067. Item ..= ReturnProperty(tag, propertyName, value)
  3068. end
  3069. end
  3070. Item ..= "</Properties>"
  3071. return Item
  3072. end
  3073.  
  3074. local function save_hierarchy(hierarchy)
  3075. for _, instance in hierarchy do
  3076. local InstanceOverride, ClassTagOverride, ClassNameOverride
  3077.  
  3078. if not InstanceOverride then
  3079. InstanceOverride = InstancesOverrides[instance]
  3080. if InstanceOverride then
  3081. ClassTagOverride = InstanceOverride.__ClassName
  3082. end
  3083. end
  3084. local ClassName = instance.ClassName
  3085.  
  3086. local InstanceName = instance.Name
  3087. local SkipEntirely
  3088.  
  3089. if not ClassTagOverride then -- ! Assuming anything that has __ClassName comes from save_extra
  3090. if IgnoreNotArchivable and not instance.Archivable then
  3091. continue
  3092. end
  3093.  
  3094. SkipEntirely = IgnoreList[instance]
  3095. if SkipEntirely then
  3096. continue
  3097. end
  3098.  
  3099. do
  3100. local OnIgnoredList = IgnoreList[ClassName]
  3101. if OnIgnoredList and (OnIgnoredList == true or OnIgnoredList[InstanceName]) then
  3102. continue
  3103. end
  3104. end
  3105.  
  3106. if not DecompileIgnoring then
  3107. DecompileIgnoring = DecompileIgnore[instance]
  3108.  
  3109. if DecompileIgnoring == nil then
  3110. local DecompileIgnored = DecompileIgnore[ClassName]
  3111. if DecompileIgnored then
  3112. DecompileIgnoring = DecompileIgnored == true or DecompileIgnored[InstanceName]
  3113. end
  3114. end
  3115.  
  3116. if DecompileIgnoring then
  3117. DecompileIgnoring = instance
  3118. elseif DecompileIgnoring == false then
  3119. DecompileIgnoring = 1 -- Ignore one instance
  3120. end
  3121. end
  3122.  
  3123. do
  3124. local Fix = NotCreatableFixes[ClassName]
  3125.  
  3126. if Fix then
  3127. if SaveNotCreatable then
  3128. ClassName, InstanceOverride = Fix, replaceClassName(instance, InstanceName, ClassName)
  3129. else
  3130. continue -- They won't show up in Studio anyway (Enable SaveNotCreatable if you wish to bypass this)
  3131. end
  3132. else -- ! Assuming nothing that is a PartOperation or inherits from it is in NotCreatableFixes
  3133. if TreatUnionsAsParts and instance:IsA("PartOperation") then
  3134. ClassName, InstanceOverride = "Part", replaceClassName(instance, InstanceName, ClassName)
  3135. ClassNameOverride = "BasePart" -- * Mutual Superclass for PartOperation and Part; For properties only
  3136. elseif not ClassList[ClassName] then -- ? API Dump is outdated then
  3137. if __DEBUG_MODE then
  3138. __DEBUG_MODE("Class not Found", ClassName)
  3139. end
  3140.  
  3141. ClassTagOverride = ClassName -- ? To at least retain .ClassName unlike the rest of the class-specific properties
  3142. ClassName = "Folder" -- ? replaceClassName is not needed because of the ClassTagOverride
  3143. end
  3144. end
  3145. end
  3146. end
  3147. -- ? The reason we only save .Name (and few other props in save_specific) is because
  3148. -- ? we can be sure this is a custom container (ex. NilInstancesFixes)
  3149. -- ? However, in case of NotCreatableFixes, the Instance might have Tags, Attributes etc. that can potentially be saved (even though it's a Folder)
  3150. if InstanceOverride and InstanceOverride.__SaveSpecific then
  3151. savebuffer[savebuffer_size] = save_specific(ClassName, InstanceOverride.Properties) -- ! Assuming anything that has __SaveSpecific will have .Properties
  3152. savebuffer_size += 1
  3153. else
  3154. -- local Properties =
  3155. savebuffer[savebuffer_size] = ReturnItem(ClassTagOverride or ClassName, instance) -- TODO: Ideally this shouldn't return <Properties> as well as the line below to close it IF IgnorePropertiesOfNotScriptsOnScriptsMode is ENABLED
  3156. savebuffer_size += 1
  3157. if not (IgnorePropertiesOfNotScriptsOnScriptsMode and not isLuaSourceContainer(instance)) then
  3158. local default_instance, new_def_inst
  3159.  
  3160. if IgnoreDefaultProperties then
  3161. default_instance = default_instances[ClassName]
  3162. if not default_instance then
  3163. local Class = ClassList[ClassName]
  3164. if not Class.NotCreatable then -- __api_dump_class_not_creatable__ also indicates this
  3165. -- NotCreatableFixes are exceptions to the check above meaning if we don't keep the NotCreatableFixes updated then Instance.new below might start erroring in the future potentially; HOWEVER IsPropertyModified solves this issue and no updates are really needed as NotCreatableFixes is up-to-date as of VERSION-HERE (which is when IPM gets enabled)
  3166. local ok, result = pcall(Instance.new, ClassName) -- ! pcall is needed for level 3 execs (for example TestService); EXCEPTION NOTED ABOVE (irrelevant)
  3167.  
  3168. if ok then
  3169. new_def_inst = result
  3170.  
  3171. default_instance = {}
  3172.  
  3173. default_instances[ClassName] = default_instance
  3174. else
  3175. Class.NotCreatable = true
  3176. if __DEBUG_MODE then
  3177. __DEBUG_MODE("Failed to create default Instance", ClassName, result)
  3178. end
  3179. end
  3180. elseif __DEBUG_MODE then
  3181. __DEBUG_MODE("Unable to create default Instance (NotCreatable)", ClassName)
  3182. end
  3183. end
  3184. end
  3185.  
  3186. for _, Property in GetInheritedProps(ClassNameOverride or ClassName) do
  3187. local PropertyName = Property.Name
  3188.  
  3189. if IgnoreProperties[PropertyName] then
  3190. continue
  3191. end
  3192.  
  3193. local ValueType = Property.ValueType
  3194.  
  3195. if IgnoreSharedStrings and ValueType == "SharedString" then -- ? More info in Options
  3196. continue
  3197. end
  3198.  
  3199. local Special, Category, Optional = Property.Special, Property.Category, Property.Optional
  3200.  
  3201. local raw = ReadProperty(instance, Property, PropertyName, Special, Category, Optional)
  3202.  
  3203. if raw == __BREAK then -- ! Assuming __BREAK is always returned when there's a failure to read a property
  3204. local GHPFFailed, Fallback = Property.GHPFFailed, Property.Fallback
  3205. if GHPFFailed and not Fallback then
  3206. continue
  3207. end
  3208.  
  3209. if not GHPFFailed then
  3210. local ok, result = pcall(gethiddenproperty_fallback, instance, PropertyName) -- * This helps in reading: Vector3int16, OptionalCoordinateFrame DataTypes. It also acts as an almost entire fallback for gethiddenproperty in case it is missing
  3211. if result == nil and not Optional then
  3212. ok = nil
  3213. end
  3214.  
  3215. if ok then
  3216. raw = result
  3217. else
  3218. GHPFFailed = true
  3219. Property.GHPFFailed = GHPFFailed
  3220. end
  3221. end
  3222.  
  3223. if GHPFFailed and Fallback then
  3224. local ok, result = pcall(Fallback, instance)
  3225.  
  3226. if ok then
  3227. raw = result
  3228. else
  3229. Property.Fallback = nil -- Low level execs might fail due to lack of some Capabilities
  3230. if __DEBUG_MODE then
  3231. __DEBUG_MODE("Fix Failed", PropertyName, result)
  3232. end
  3233. continue
  3234. end
  3235. end
  3236.  
  3237. if raw == __BREAK then
  3238. continue
  3239. end
  3240. end
  3241.  
  3242. if SharedStringOverwrite and ValueType == "BinaryString" then -- TODO: Convert this to table if more types are added
  3243. ValueType = "SharedString"
  3244. end
  3245.  
  3246. -- Special = Property.Special -- ? Read TODO below (must be updated if it's used frequently afterwards)
  3247.  
  3248. if
  3249. default_instance
  3250. and Property.CanRead
  3251. and not Property.Special -- TODO: .Special is checked more than once (because it might be updated during ReadProperty)
  3252. and not (PropertyName == "Source" and isLuaSourceContainer(instance))
  3253. then -- ? Could be not just "Source" in the future
  3254. if new_def_inst then
  3255. default_instance[PropertyName] = index(new_def_inst, PropertyName)
  3256. end
  3257. if default_instance[PropertyName] == raw then
  3258. continue
  3259. end
  3260. end
  3261.  
  3262. -- Serialization start
  3263.  
  3264. local tag, value
  3265. if Category == "Class" then
  3266. tag = "Ref"
  3267. if raw then
  3268. if SaveNotCreatableWillBeEnabled then
  3269. local Fix = NotCreatableFixes[raw.ClassName]
  3270. if
  3271. Fix
  3272. and (
  3273. PropertyName == "PlayerToHideFrom"
  3274. or ValueType ~= "Instance" and ValueType ~= Fix
  3275. )
  3276. then
  3277. -- * To avoid errors
  3278. continue
  3279. end
  3280. end
  3281.  
  3282. value = GetRef(raw)
  3283. else
  3284. value = "null"
  3285. end
  3286. 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
  3287. value, tag = XML_Descriptors.EnumItem(raw)
  3288. else
  3289. local Descriptor = XML_Descriptors[ValueType]
  3290.  
  3291. if Descriptor then
  3292. value, tag = ReturnValueAndTag(raw, ValueType, Descriptor)
  3293. elseif "ProtectedString" == ValueType then -- TODO: Try fitting this inside Descriptors
  3294. tag = ValueType
  3295.  
  3296. if PropertyName == "Source" then
  3297. if DecompileIgnoring then -- ? Should this really prevent extraction of the original source if present ?
  3298. if DecompileIgnoring == 1 then
  3299. DecompileIgnoring = nil
  3300. end
  3301. value = "-- Ignored"
  3302. else
  3303. local should_decompile = true
  3304. local LinkedSource
  3305. local LinkedSource_Url = instance.LinkedSource -- ! Assuming every Class that has ProtectedString Source property also has a LinkedSource property
  3306. local hasLinkedSource = LinkedSource_Url ~= ""
  3307. local LinkedSource_type
  3308. if hasLinkedSource then
  3309. local Path = instance:GetFullName()
  3310. if RecoveredScripts then
  3311. table.insert(RecoveredScripts, Path)
  3312. end
  3313.  
  3314. LinkedSource = string.match(LinkedSource_Url, "%w+$") -- TODO: No sure if this pattern matches all possible cases. Example is: 'rbxassetid://0&hash=cd73dd2fe5e5013137231c227da3167e'
  3315. if LinkedSource then
  3316. if ScriptCache then
  3317. local cached = ldeccache[LinkedSource]
  3318.  
  3319. if cached then
  3320. value = cached
  3321. should_decompile = nil
  3322. end
  3323. end
  3324. if should_decompile then
  3325. if DecompileJobless then
  3326. value = "-- Not found in LinkedSource ScriptCache"
  3327. should_decompile = nil
  3328. end
  3329.  
  3330. LinkedSource_type = string.find(LinkedSource, "%a") and "hash"
  3331. or "id"
  3332.  
  3333. local asset = LinkedSource_type .. "=" .. LinkedSource
  3334.  
  3335. local ok, source = pcall(function()
  3336. -- Credits @halffalse
  3337. return game:HttpGet(
  3338. "https://assetdelivery.roproxy.com/v1/asset/?" .. asset
  3339. )
  3340. end)
  3341.  
  3342. if ok and filterLinkedSource(source) then
  3343. if ScriptCache then
  3344. ldeccache[LinkedSource] = source
  3345. end
  3346.  
  3347. value = source
  3348.  
  3349. should_decompile = nil
  3350. end
  3351. end
  3352. else --if __DEBUG_MODE then -- * We print this anyway because very important
  3353. warn(
  3354. "FAILED TO EXTRACT ORIGINAL SCRIPT SOURCE (OPEN A GITHUB ISSUE): ",
  3355. instance:GetFullName(),
  3356. LinkedSource_Url
  3357. )
  3358. end
  3359. end
  3360.  
  3361. if should_decompile then
  3362. local isLocalScript = instance:IsA("LocalScript")
  3363. if
  3364. isLocalScript and instance.RunContext == Enum.RunContext.Server
  3365. or not isLocalScript
  3366. and instance:IsA("Script")
  3367. and instance.RunContext ~= Enum.RunContext.Client
  3368. then
  3369. value = "-- [FilteringEnabled] Server Scripts are IMPOSSIBLE to save" -- TODO: Could be not just server scripts in the future
  3370. else
  3371. if OPTIONS.usekonstantdecompiler then
  3372. value = konstantdecompiler.decompile(instance)
  3373. else
  3374. value = ldecompile(instance)
  3375. end
  3376. if SaveBytecode then
  3377. local output = SaveBytecode(instance)
  3378. if output then
  3379. value = output .. value
  3380. end
  3381. end
  3382. end
  3383. end
  3384.  
  3385. value = "-- Saved by UniversalSynSaveInstance (Join to Copy Games) https://discord.gg/wx4ThpAsmw\n\n"
  3386. .. (hasLinkedSource and "-- Original Source: https://assetdelivery.roblox.com/v1/asset/?" .. (LinkedSource_type or "id") .. "=" .. (LinkedSource or LinkedSource_Url) .. "\n\n" or "")
  3387. .. value
  3388. end
  3389. end
  3390. value = XML_Descriptors.__PROTECTEDSTRING(value)
  3391. else
  3392. --OptionalCoordinateFrame and so on, we make it dynamic
  3393.  
  3394. if Optional then
  3395. Descriptor = XML_Descriptors[Optional]
  3396.  
  3397. if Descriptor then
  3398. if raw == nil then
  3399. -- * It can be empty, because it's optional
  3400. -- ? Though why even save it if it's empty considering it's optional
  3401. continue
  3402. -- value, tag = "", ValueType
  3403. else
  3404. value, tag = ReturnValueAndTag(raw, ValueType, Descriptor)
  3405. end
  3406. end
  3407. end
  3408. end
  3409. end
  3410.  
  3411. if tag then
  3412. savebuffer[savebuffer_size] = ReturnProperty(tag, PropertyName, value)
  3413. savebuffer_size += 1
  3414. else --if __DEBUG_MODE then -- * We print this anyway because very important
  3415. warn("UNSUPPORTED TYPE (OPEN A GITHUB ISSUE): ", ValueType, ClassName, PropertyName)
  3416. end
  3417. end
  3418. end
  3419. savebuffer[savebuffer_size] = "</Properties>"
  3420. savebuffer_size += 1
  3421.  
  3422. if SaveCacheInterval < savebuffer_size then
  3423. save_cache()
  3424. end
  3425. end
  3426.  
  3427. if SkipEntirely ~= false then -- ? We save instance without it's descendants in this case (== false)
  3428. local Children = InstanceOverride and InstanceOverride.__Children or instance:GetChildren()
  3429.  
  3430. if #Children ~= 0 then
  3431. save_hierarchy(Children)
  3432. end
  3433. end
  3434.  
  3435. if DecompileIgnoring and DecompileIgnoring == instance then
  3436. DecompileIgnoring = nil
  3437. end
  3438.  
  3439. savebuffer[savebuffer_size] = "</Item>"
  3440. savebuffer_size += 1
  3441. end
  3442. end
  3443.  
  3444. local function save_extra(name, instanceOrTable, saveProps, customClassName, source)
  3445. if not customClassName then
  3446. customClassName = "Folder"
  3447. end
  3448.  
  3449. local properties = { Name = name, Source = source }
  3450. local hierarchy
  3451.  
  3452. if instanceOrTable then
  3453. if type(instanceOrTable) == "table" then
  3454. hierarchy = instanceOrTable
  3455. else
  3456. hierarchy = instanceOrTable:GetChildren()
  3457. if saveProps then
  3458. -- IgnoreList[instanceOrTable] = nil
  3459. -- IgnoreNotArchivable = false
  3460.  
  3461. InstancesOverrides[instanceOrTable] = {
  3462. __ClassName = customClassName, -- ! Assuming any class that contains ProtectedString is never passed, because it expects bytecode, not normal code
  3463. __Children = hierarchy,
  3464. Properties = properties,
  3465. }
  3466.  
  3467. save_hierarchy({ instanceOrTable })
  3468. end
  3469. end
  3470. end
  3471.  
  3472. if not saveProps then
  3473. savebuffer[savebuffer_size] = save_specific(customClassName, properties)
  3474. savebuffer_size += 1
  3475. if hierarchy then
  3476. save_hierarchy(hierarchy)
  3477. end
  3478. savebuffer[savebuffer_size] = "</Item>"
  3479. savebuffer_size += 1
  3480. end
  3481. end
  3482.  
  3483. local function save_game()
  3484. do
  3485. if IsModel then
  3486. --[[
  3487. -- ? 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"
  3488. Also http can be converted to https but not sure if Roblox cares
  3489. -- ? <External>null</External><External>nil</External> - <External> is a legacy concept that is no longer used.
  3490. ]]
  3491. header ..= '<Meta name="ExplicitAutoJoints">true</Meta>'
  3492. end
  3493. if writefile and not OPTIONS.Callback then
  3494. writefile(placename, header) -- TODO This is sort of useless if writefile will be used at the end (like if AlternativeWritefile and Callback are unused)
  3495. end
  3496. end
  3497.  
  3498. -- TODO Find a better solution for this
  3499. SaveNotCreatableWillBeEnabled = SaveNotCreatable
  3500. or (IsolateLocalPlayer or IsolateLocalPlayerCharacter) and IsolateLocalPlayer
  3501. or IsolatePlayers
  3502. or NilInstances and global_container.getnilinstances -- ! Make sure this accurately reflects everything below
  3503.  
  3504. save_hierarchy(ToSaveList)
  3505.  
  3506. if IsolateLocalPlayer or IsolateLocalPlayerCharacter then
  3507. local LocalPlayer = service.Players.LocalPlayer
  3508. if LocalPlayer then
  3509. if IsolateLocalPlayer then
  3510. SaveNotCreatable = true
  3511. save_extra("LocalPlayer", LocalPlayer, true)
  3512. end
  3513. if IsolateLocalPlayerCharacter then
  3514. local LocalPlayerCharacter = LocalPlayer.Character
  3515. if LocalPlayerCharacter then
  3516. save_extra("LocalPlayer Character", LocalPlayerCharacter, true, "Model")
  3517. end
  3518. end
  3519. end
  3520. end
  3521.  
  3522. if IsolateStarterPlayer then
  3523. -- SaveNotCreatable = true -- TODO: Enable if StarterPlayerScripts or StarterCharacterScripts stop showing up in isolated folder in Studio
  3524. save_extra("StarterPlayer", service.StarterPlayer) -- no reason to saveprops as you can see the props on the original instance
  3525. end
  3526.  
  3527. if IsolatePlayers then
  3528. SaveNotCreatable = true
  3529. save_extra("Players", service.Players) -- no reason to saveprops as you can see the props on the original instance
  3530. end
  3531.  
  3532. if NilInstances and global_container.getnilinstances then
  3533. local nil_instances, nil_instances_size = {}, 1
  3534.  
  3535. local NilInstancesFixes = OPTIONS.NilInstancesFixes
  3536.  
  3537. for _, instance in global_container.getnilinstances() do
  3538. if instance == game then
  3539. instance = nil
  3540. -- break
  3541. else
  3542. local ClassName = instance.ClassName
  3543.  
  3544. local Fix = InheritsFix(NilInstancesFixes, ClassName, instance)
  3545.  
  3546. if Fix then
  3547. instance = Fix(instance, InstancesOverrides)
  3548. -- continue
  3549. end
  3550.  
  3551. local Class = ClassList[ClassName]
  3552. if Class then
  3553. if Class.Service then -- For CSGDictionaryService, NonReplicatedCSGDictionaryService, LogService, ProximityPromptService, TestService & more
  3554. -- instance.Parent = game
  3555. instance = nil
  3556. -- continue
  3557. end
  3558. end
  3559. end
  3560. if instance then
  3561. nil_instances[nil_instances_size] = instance
  3562. nil_instances_size += 1
  3563. end
  3564. end
  3565. SaveNotCreatable = true
  3566. save_extra("Nil Instances", nil_instances)
  3567. end
  3568.  
  3569. if OPTIONS.ReadMe then
  3570. save_extra(
  3571. "README",
  3572. nil,
  3573. nil,
  3574. "Script",
  3575. "--[[\n"
  3576. .. (#RecoveredScripts ~= 0 and "\t\tIMPORTANT: Original Source of these Scripts was Recovered: " .. service.HttpService:JSONEncode(
  3577. RecoveredScripts
  3578. ) .. "\n" or "")
  3579. .. [[
  3580. Thank you for using UniversalSynSaveInstance (Join to Copy Games) https://discord.gg/wx4ThpAsmw.
  3581.  
  3582. If you didn't save in Binary (rbxl) - it's 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).
  3583. You can do that by going to FILE -> Save to File As -> Make sure File Name ends with .rbxl -> Save
  3584.  
  3585. ServerStorage, ServerScriptService and Server Scripts are IMPOSSIBLE to save because of FilteringEnabled.
  3586.  
  3587. If your player cannot spawn into the game, please move the scripts in StarterPlayer somewhere else or delete them. Then run `game:GetService("Players").CharacterAutoLoads = true`.
  3588. And use "Play Here" to start game instead of "Play" to spawn your Character where your Camera currently is.
  3589.  
  3590. If the chat system does not work, please use the explorer and delete everything inside the TextChatService/Chat service(s).
  3591. Or run `game:GetService("Chat"):ClearAllChildren() game:GetService("TextChatService"):ClearAllChildren()`
  3592.  
  3593. If Union and MeshPart collisions don't work, run the script below in the Studio Command Bar:
  3594.  
  3595.  
  3596. local C = game:GetService("CoreGui")
  3597. local D = Enum.CollisionFidelity.Default
  3598.  
  3599. for _, v in game:GetDescendants() do
  3600. if v:IsA("TriangleMeshPart") and not v:IsDescendantOf(C) then
  3601. v.CollisionFidelity = D
  3602. end
  3603. end
  3604. print("Done")
  3605.  
  3606. If you can't move the Camera, run this script in the Studio Command Bar:
  3607.  
  3608. workspace.CurrentCamera.CameraType = Enum.CameraType.Fixed
  3609.  
  3610. Or Destroy the Camera.
  3611.  
  3612. This file was generated with the following settings:
  3613. ]]
  3614. .. service.HttpService:JSONEncode(OPTIONS)
  3615. .. "\n\n\t\tElapsed time: "
  3616. .. os.clock() - elapse_t
  3617. .. " PlaceId: "
  3618. .. game.PlaceId
  3619. .. " PlaceVersion: "
  3620. .. game.PlaceVersion
  3621. .. " Client Version: "
  3622. .. version()
  3623. .. " Executor: "
  3624. .. (identify_executor and table.concat({ identify_executor() }, " ") or "Unknown")
  3625. .. "\n]]"
  3626. )
  3627. end
  3628. do
  3629. local tmp = { "<SharedStrings>" }
  3630. for identifier, value in SharedStrings do
  3631. table.insert(tmp, '<SharedString md5="' .. identifier .. '">' .. value .. "</SharedString>")
  3632. end
  3633.  
  3634. if 1 < #tmp then -- TODO: This sucks so much because we try to iterate a table just to check this (check above)
  3635. savebuffer[savebuffer_size] = table.concat(tmp)
  3636. savebuffer_size += 1
  3637. savebuffer[savebuffer_size] = "</SharedStrings>"
  3638. savebuffer_size += 1
  3639. end
  3640. end
  3641.  
  3642. savebuffer[savebuffer_size] =
  3643. "</roblox><!-- Saved by UniversalSynSaveInstance (Join to Copy Games) https://discord.gg/wx4ThpAsmw -->"
  3644. savebuffer_size += 1
  3645. save_cache(true)
  3646. do
  3647. -- ! Assuming we only write to file once hence why we only filter once
  3648. -- TODO This might cause issues on non-unique Usernames (ex. "Cake" if game is about cakes then everything supposedly related to your name will be replaced with "Roblox"); Certain UserIds might also affect numbers, like if your UserId is 2481848 and there is some number that goes like "1.248184818837" then that the matched part will be replaced with 1, potentially making the number incorrect.
  3649. -- TODO So for now it's best to keep this disabled by default
  3650. -- TODO It's also not smart to filter entire file string at the end as this might also affect decompiled scripts content, which has no way of containing any user-related information. It would be better to use gsub in string Descriptor and such
  3651. if OPTIONS.Anonymous then
  3652. local LocalPlayer = service.Players.LocalPlayer
  3653. if LocalPlayer then
  3654. local function gsubCaseInsensitive(input, search, replacement) -- * Credits to friends
  3655. local inputLower = string.lower(input)
  3656.  
  3657. search = string.lower(search)
  3658.  
  3659. local lastFinish = 0
  3660. local subStrings = {}
  3661. local search_len = #search
  3662. local input_len = #input
  3663. while search_len <= input_len - lastFinish do
  3664. local init = lastFinish + 1
  3665.  
  3666. local start, finish = string.find(inputLower, search, init, true)
  3667.  
  3668. if start == nil then
  3669. break
  3670. end
  3671.  
  3672. table.insert(subStrings, string.sub(input, init, start - 1))
  3673.  
  3674. lastFinish = finish
  3675. end
  3676.  
  3677. if lastFinish == 0 then
  3678. return input
  3679. end
  3680.  
  3681. table.insert(subStrings, string.sub(input, lastFinish + 1))
  3682.  
  3683. return table.concat(subStrings, replacement)
  3684. end
  3685.  
  3686. local Anonymous = type(OPTIONS.Anonymous) == "table" and OPTIONS.Anonymous
  3687. or { UserId = "1", Name = "Roblox" }
  3688.  
  3689. for _, chunk in chunks do
  3690. chunk.str = gsubCaseInsensitive(
  3691. string.gsub(chunk.str, LocalPlayer.UserId, Anonymous.UserId),
  3692. LocalPlayer.Name,
  3693. Anonymous.Name
  3694. )
  3695. end
  3696. end
  3697. end
  3698.  
  3699. local Callback = OPTIONS.Callback
  3700. local AlternativeWritefile = OPTIONS.AlternativeWritefile
  3701. local SEGMENT_SIZE = 4145728
  3702.  
  3703. if Callback then
  3704. local t = {header}
  3705. for i = 1, #chunks do
  3706. t[#t+1] = chunks[i].str
  3707. end
  3708. local totalstr = table.concat(t)
  3709. Callback(totalstr, chunks, totalsize)
  3710.  
  3711. elseif AlternativeWritefile and appendfile then
  3712. local totallen = math.ceil(totalsize / SEGMENT_SIZE)
  3713. local currentlen = 1
  3714.  
  3715. for c = 1, #chunks do
  3716. local chunk = chunks[c]
  3717. local str = chunk.str
  3718. local size = chunk.size
  3719. local length = math.ceil(size / SEGMENT_SIZE)
  3720.  
  3721. for i = 1, length do
  3722. local a = (i - 1) * SEGMENT_SIZE + 1
  3723. local b = i * SEGMENT_SIZE
  3724. local savestr = string.sub(str, a, b)
  3725.  
  3726. run_with_loading(
  3727. "Writing to File " .. math.round(currentlen / totallen * 100) .. "%",
  3728. nil,
  3729. true,
  3730. appendfile,
  3731. placename,
  3732. savestr
  3733. )
  3734.  
  3735. currentlen = currentlen + 1
  3736. if i ~= length then task.wait() end
  3737. end
  3738. end
  3739.  
  3740. else
  3741. local t = {header}
  3742. for i = 1, #chunks do
  3743. t[#t+1] = chunks[i].str
  3744. end
  3745. local totalstr = table.concat(t)
  3746.  
  3747. run_with_loading(
  3748. "Writing " .. get_size_format() .. " to File",
  3749. nil,
  3750. true,
  3751. writefile,
  3752. placename,
  3753. totalstr
  3754. )
  3755. end
  3756. end
  3757. table.clear(SharedStrings)
  3758. end
  3759.  
  3760. local Connections
  3761. do
  3762. local Players = service.Players
  3763.  
  3764. if IgnoreList.Model ~= true then
  3765. Connections = {}
  3766. local function ignoreCharacter(player)
  3767. table.insert(
  3768. Connections,
  3769. player.CharacterAdded:Connect(function(character)
  3770. IgnoreList[character] = true
  3771. end)
  3772. )
  3773.  
  3774. local Character = player.Character
  3775. if Character then
  3776. IgnoreList[Character] = true
  3777. end
  3778. end
  3779.  
  3780. if OPTIONS.RemovePlayerCharacters then
  3781. table.insert(
  3782. Connections,
  3783. Players.PlayerAdded:Connect(function(player)
  3784. ignoreCharacter(player)
  3785. end)
  3786. )
  3787. for _, player in Players:GetPlayers() do
  3788. ignoreCharacter(player)
  3789. end
  3790. else
  3791. IgnoreNotArchivable = false -- TODO Bad solution (Characters are NotArchivable); Also make sure the next solution is compatible with IsolateLocalPlayerCharacter
  3792. if IsolateLocalPlayerCharacter then
  3793. task.spawn(function()
  3794. ignoreCharacter(GetLocalPlayer())
  3795. end)
  3796. end
  3797. end
  3798. end
  3799. if IsolateLocalPlayer and IgnoreList.Player ~= true then
  3800. task.spawn(function()
  3801. IgnoreList[GetLocalPlayer()] = true
  3802. end)
  3803. end
  3804. end
  3805.  
  3806. if IsolateStarterPlayer then
  3807. IgnoreList.StarterPlayer = false
  3808. end
  3809.  
  3810. if IsolatePlayers then
  3811. IgnoreList.Players = false
  3812. end
  3813.  
  3814. if OPTIONS.ShowStatus then
  3815. do
  3816. local Exists = GLOBAL_ENV._statustext
  3817. if Exists then
  3818. Exists:Destroy()
  3819. end
  3820. end
  3821.  
  3822. local StatusGui = Instance.new("ScreenGui")
  3823.  
  3824. GLOBAL_ENV._statustext = StatusGui
  3825.  
  3826. StatusGui.DisplayOrder = 2e9
  3827. pcall(function() -- ? Compatibility with level 2
  3828. StatusGui.OnTopOfCoreBlur = true
  3829. end)
  3830.  
  3831. StatusText = Instance.new("TextLabel")
  3832.  
  3833. StatusText.Text = "Saving..."
  3834.  
  3835. StatusText.BackgroundTransparency = 1
  3836. StatusText.Font = Enum.Font.Code
  3837. StatusText.AnchorPoint = Vector2.new(1)
  3838. StatusText.Position = UDim2.new(1)
  3839. StatusText.Size = UDim2.new(0.3, 0, 0, 20)
  3840.  
  3841. StatusText.TextColor3 = Color3.new(1, 1, 1)
  3842. StatusText.TextScaled = true
  3843. StatusText.TextStrokeTransparency = 0.7
  3844. StatusText.TextXAlignment = Enum.TextXAlignment.Right
  3845. StatusText.TextYAlignment = Enum.TextYAlignment.Top
  3846.  
  3847. StatusText.Parent = StatusGui
  3848.  
  3849. local function randomString()
  3850. local length = math.random(10, 20)
  3851. local randomarray = table.create(length)
  3852. for i = 1, length do
  3853. randomarray[i] = string.char(math.random(32, 126))
  3854. end
  3855. return table.concat(randomarray)
  3856. end
  3857.  
  3858. if global_container.gethui then
  3859. StatusGui.Name = randomString()
  3860. StatusGui.Parent = global_container.gethui()
  3861. else
  3862. if global_container.protectgui then
  3863. StatusGui.Name = randomString()
  3864. global_container.protectgui(StatusGui)
  3865. StatusGui.Parent = game:GetService("CoreGui")
  3866. else
  3867. local RobloxGui = game:GetService("CoreGui"):FindFirstChild("RobloxGui")
  3868. if RobloxGui then
  3869. StatusGui.Parent = RobloxGui
  3870. else
  3871. StatusGui.Name = randomString()
  3872. StatusGui.Parent = game:GetService("CoreGui")
  3873. end
  3874. end
  3875. end
  3876. end
  3877.  
  3878. do
  3879. local SafeMode = OPTIONS.SafeMode
  3880. if SafeMode then
  3881. task.spawn(function()
  3882. local LocalPlayer = GetLocalPlayer()
  3883.  
  3884. local PlayerScripts = LocalPlayer:FindFirstChildOfClass("PlayerScripts")
  3885. if PlayerScripts then
  3886. local function construct_InstanceOverride(instance)
  3887. local children = instance:GetChildren()
  3888. InstancesOverrides[instance] = {
  3889. __Children = children,
  3890. }
  3891. for _, child in children do
  3892. construct_InstanceOverride(child)
  3893. end
  3894. end
  3895. construct_InstanceOverride(PlayerScripts)
  3896.  
  3897. InstancesOverrides[LocalPlayer] = {
  3898. __Children = LocalPlayer:GetChildren(),
  3899. Properties = { Name = "[" .. LocalPlayer.ClassName .. "] " .. LocalPlayer.Name },
  3900. }
  3901. end
  3902.  
  3903. LocalPlayer:Kick("\n[SAFEMODE] Saving in Progress..\nPlease do NOT leave")
  3904. wait_for_render()
  3905. task.delay(10, service.GuiService.ClearError, service.GuiService)
  3906. end)
  3907.  
  3908. service.RunService:Set3dRenderingEnabled(false)
  3909. end
  3910.  
  3911. local anti_idle
  3912. if OPTIONS.AntiIdle then
  3913. task.spawn(function()
  3914. local Idled = GetLocalPlayer().Idled
  3915. if getconnections then
  3916. for _, c in getconnections(Idled) do
  3917. if not pcall(function()
  3918. c:Disable()
  3919. end) then
  3920. pcall(function()
  3921. c:Disconnect()
  3922. end)
  3923. end
  3924. end
  3925. end
  3926. anti_idle = Idled:Connect(function()
  3927. service.VirtualInputManager:SendMouseWheelEvent(
  3928. service.UserInputService:GetMouseLocation().X,
  3929. service.UserInputService:GetMouseLocation().Y,
  3930. true,
  3931. game
  3932. )
  3933. end)
  3934. end)
  3935. end
  3936.  
  3937. elapse_t = os.clock()
  3938.  
  3939. local ok, err = xpcall(save_game, function(err)
  3940. return debug.traceback(err)
  3941. end)
  3942.  
  3943. if SafeMode then
  3944. service.GuiService:ClearError()
  3945. service.RunService:Set3dRenderingEnabled(true)
  3946. end
  3947.  
  3948. if old_gethiddenproperty then
  3949. gethiddenproperty = old_gethiddenproperty
  3950. end
  3951.  
  3952. if anti_idle then
  3953. anti_idle:Disconnect()
  3954. end
  3955. if Connections then
  3956. for _, connection in Connections do
  3957. connection:Disconnect()
  3958. end
  3959. end
  3960. GLOBAL_ENV[placename] = nil
  3961.  
  3962. elapse_t = os.clock() - elapse_t
  3963. local Log10 = math.log10(elapse_t)
  3964. local ExtraTime = 10
  3965.  
  3966. if StatusText then
  3967. task.spawn(function()
  3968. if ok then
  3969. StatusText.Text = string.format("Saved! Time %.3f seconds; Size %s", elapse_t, get_size_format())
  3970. StatusText.TextColor3 = Color3.new(0, 1)
  3971. task.wait(Log10 * 2 + ExtraTime)
  3972. else
  3973. if Loading then
  3974. task.cancel(Loading)
  3975. Loading = nil
  3976. end
  3977. StatusText.Text = "Failed! Check F9 console for more info"
  3978. StatusText.TextColor3 = Color3.new(1)
  3979. warn("Error found while saving:")
  3980. warn(err)
  3981. task.wait(Log10 + ExtraTime)
  3982. end
  3983. StatusText:Destroy()
  3984. end)
  3985. end
  3986.  
  3987. if OPTIONS.ShutdownWhenDone and ok then
  3988. task.wait(Log10 * 2 + ExtraTime)
  3989. game:Shutdown()
  3990. end
  3991. end
  3992. end
  3993.  
  3994. return synsaveinstance
Add Comment
Please, Sign In to add comment