NoTextForSpeech

asdasasd

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