NoTextForSpeech

ss

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