NoTextForSpeech

ss

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