NoTextForSpeech

testcode

Jan 15th, 2025 (edited)
13
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 97.45 KB | None | 0 0
  1. --!strict
  2.  
  3. --[[
  4. wdec - Luau decompiler
  5. written by luavm on discord
  6. ]]
  7.  
  8. --// Decompiler fixed by @legalcarbomb on Discord.
  9. --@return string
  10.  
  11. -- // This was fixed using Fiu (Decompiler still has a lot of flaws.)
  12. --[[
  13.  
  14. 1. Opcodes are not updated
  15. 2. Oplist is not updated
  16. 3. Opcode handling is not updated (maybe even constant handling)
  17.  
  18. ]]
  19.  
  20. --TODO: Update Oplist (will do later) to v6
  21. --TODO: Update the BuiltIns table
  22.  
  23. --- woffle if you want it taken down (for some reason) lmk
  24.  
  25. --[[
  26. CHANGELOG:
  27. Updated Oplist,
  28. Updated BuiltIns
  29. ]]
  30.  
  31. ---
  32. game_httpget = function(s)
  33. return game:HttpGet(s)
  34. end
  35. local httpget = httpget or game_httpget or http_get -- add your httpget func here
  36. local InspectUrl = "https://raw.githubusercontent.com/kikito/inspect.lua/refs/heads/master/inspect.lua"
  37. local inspect = loadstring(httpget(InspectUrl))()
  38.  
  39. local fmt = string.format -- string.format is annoying to write
  40. local getscriptbytecode = getscriptbytecode
  41. local request = request
  42.  
  43. type luau_op = {
  44. name: string,
  45. aux: boolean
  46. }
  47. --I removed DEP_FORGLOOP_INEXT, DEP_JUMPIFEQK and DEP_JUMPIFNOTEQK (deprecated)
  48. local opcodes: { luau_op } = { -- Opcode table. Every opcode is ordered as their Opcode number (not Roblox encoded)
  49. {name = "NOP", aux = false},
  50. {name = "BREAK", aux = false},
  51. {name = "LOADNIL", aux = false},
  52. {name = "LOADB", aux = false},
  53. {name = "LOADN", aux = false},
  54. {name = "LOADK", aux = false},
  55. {name = "MOVE", aux = false},
  56. {name = "GETGLOBAL", aux = true},
  57. {name = "SETGLOBAL", aux = true},
  58. {name = "GETUPVAL", aux = false},
  59. {name = "SETUPVAL", aux = false},
  60. {name = "CLOSEUPVALS", aux = false},
  61. {name = "GETIMPORT", aux = true},
  62. {name = "GETTABLE", aux = false},
  63. {name = "SETTABLE", aux = false},
  64. {name = "GETTABLEKS", aux = true},
  65. {name = "SETTABLEKS", aux = true},
  66. {name = "GETTABLEN", aux = false},
  67. {name = "SETTABLEN", aux = false},
  68. {name = "NEWCLOSURE", aux = false},
  69. {name = "NAMECALL", aux = true},
  70. {name = "CALL", aux = false},
  71. {name = "RETURN", aux = false},
  72. {name = "JUMP", aux = false},
  73. {name = "JUMPBACK", aux = false},
  74. {name = "JUMPIF", aux = false},
  75. {name = "JUMPIFNOT", aux = false},
  76. {name = "JUMPIFEQ", aux = true},
  77. {name = "JUMPIFLE", aux = true},
  78. {name = "JUMPIFLT", aux = true},
  79. {name = "JUMPIFNOTEQ", aux = true},
  80. {name = "JUMPIFNOTLE", aux = true},
  81. {name = "JUMPIFNOTLT", aux = true},
  82. {name = "ADD", aux = false},
  83. {name = "SUB", aux = false},
  84. {name = "MUL", aux = false},
  85. {name = "DIV", aux = false},
  86. {name = "MOD", aux = false},
  87. {name = "POW", aux = false},
  88. {name = "ADDK", aux = false},
  89. {name = "SUBK", aux = false},
  90. {name = "MULK", aux = false},
  91. {name = "DIVK", aux = false},
  92. {name = "MODK", aux = false},
  93. {name = "POWK", aux = false},
  94. {name = "AND", aux = false},
  95. {name = "OR", aux = false},
  96. {name = "ANDK", aux = false},
  97. {name = "ORK", aux = false},
  98. {name = "CONCAT", aux = false},
  99. {name = "NOT", aux = false},
  100. {name = "MINUS", aux = false},
  101. {name = "LENGTH", aux = false},
  102. {name = "NEWTABLE", aux = true},
  103. {name = "DUPTABLE", aux = false},
  104. {name = "SETLIST", aux = true},
  105. {name = "FORNPREP", aux = false},
  106. {name = "FORNLOOP", aux = false},
  107. {name = "FORGLOOP", aux = true},
  108. {name = "FORGPREP_INEXT", aux = false},
  109. --{name = "DEP_FORGLOOP_INEXT", aux = false}, -- Deprecated, removed in Bytecode Version 3
  110. {name = "FASTCALL3", aux = true},
  111. {name = "FORGPREP_NEXT", aux = false},
  112. {name = "NATIVECALL", aux = false},
  113. {name = "GETVARARGS", aux = false},
  114. {name = "DUPCLOSURE", aux = false},
  115. {name = "PREPVARARGS", aux = false},
  116. {name = "LOADKX", aux = true},
  117. {name = "JUMPX", aux = false},
  118. {name = "FASTCALL", aux = false},
  119. {name = "COVERAGE", aux = false},
  120. {name = "CAPTURE", aux = false},
  121. --{name = "DEP_JUMPIFEQK", aux = false}, -- Deprecated
  122. --{name = "DEP_JUMPIFNOTEQK", aux = false}, -- Deprecated
  123. {name = "SUBRK", aux = false},
  124. {name = "DIVRK", aux = false},
  125. {name = "FASTCALL1", aux = false},
  126. {name = "FASTCALL2", aux = true},
  127. {name = "FASTCALL2K", aux = true},
  128. {name = "FORGPREP", aux = false},
  129. {name = "JUMPXEQKNIL", aux = true},
  130. {name = "JUMPXEQKB", aux = true},
  131. {name = "JUMPXEQKN", aux = true},
  132. {name = "JUMPXEQKS", aux = true},
  133. {name = "IDIV", aux = false},
  134. {name = "IDIVK", aux = false},
  135. --{name = "_COUNT", aux = false} -- not a valid opcode by itself
  136. }
  137.  
  138. local function GetOp(opcode: number): luau_op
  139. return opcodes[opcode + 1]
  140. end
  141.  
  142. type closure = typeof(function() end)
  143.  
  144. type luau_instruction = { -- d and e are for ease of coding
  145. name : string,
  146. a : number,
  147. b : number,
  148. c : number,
  149. d : number,
  150. e : number,
  151. aux : number | nil
  152. }
  153.  
  154. type luau_constant = {
  155. type: number,
  156. value: number | boolean | { number }
  157. }
  158.  
  159. type luau_proto = {
  160. maxstacksize: number,
  161. numparams: number,
  162. numupvalues: number,
  163. isvararg: boolean,
  164. instructions: { luau_instruction },
  165. constants: { luau_constant },
  166. child_protos: { number },
  167. name: string?,
  168. types: {
  169. flags: number,
  170. data: { number }
  171. }
  172. }
  173.  
  174. type luau_bytecode = { -- TODO: Exclude strings and somehow inline into deserializer
  175. version : number,
  176. protos : { luau_proto },
  177. strings : { string },
  178. main_proto_id: number
  179. }
  180.  
  181. local luau_bytecode_max = 6
  182. local luau_bytecode_min = 3
  183.  
  184. type luau_bytes = { number } -- I like custom named types for these things
  185.  
  186. type luau_constant_type = number
  187.  
  188. local function errorf(format, ...) -- C
  189. error(fmt(format, ...))
  190. end
  191.  
  192. --[[local function BytesToTable(bytes): luau_bytes
  193. local tbl = {}
  194. for i = 1, string.len(bytes) do
  195. table.insert(tbl, string.byte(string.sub(bytes, i, i)))
  196. end
  197. return tbl
  198. end--]] -- Deprecated, replaced with buffer
  199.  
  200. local function RobloxOp(x: number): number -- Turns a Roblox encrypted opcode into a luau opcode. Ex: 105 -> 5
  201. return x * 203 % 256
  202. end
  203.  
  204. local function deserialize(s_bytes: string, is_roblox: boolean): luau_bytecode?
  205. local b_bytes: buffer = buffer.fromstring(s_bytes)
  206. print(string.format('%s', s_bytes))
  207. print(b_bytes)
  208.  
  209. local bytes_index = 0
  210.  
  211. local function GetByte(): number
  212. local byte = buffer.readu8(b_bytes, bytes_index)
  213. bytes_index += 1
  214. return byte
  215. end
  216.  
  217. local function GetInt(): number
  218. --[[return bit32.bor(
  219. bit32.lshift(GetByte(), 0), -- Redundant lshift but it looks better
  220. bit32.lshift(GetByte(), 8),
  221. bit32.lshift(GetByte(), 16),
  222. bit32.lshift(GetByte(), 24)
  223. )--]] -- Deprecated, replaced with readu32
  224. local int = buffer.readu32(b_bytes, bytes_index)
  225. bytes_index += 4
  226. return int
  227. end
  228.  
  229. local function GetVarInt(): number
  230. local result = 0
  231. for i = 0, 4 do
  232. local value = GetByte()
  233. result = bit32.bor(bit32.lshift(bit32.band(value, 0x7F), i * 7))
  234. if not bit32.btest(value, 0x80) then
  235. break
  236. end
  237. end
  238. return result
  239. end
  240.  
  241. local function GetString()
  242. local string_sz = GetVarInt()
  243. if string_sz == 0 then
  244. return ""
  245. end
  246. local string = buffer.readstring(b_bytes, bytes_index, string_sz)
  247. bytes_index += string_sz
  248. return string
  249. end
  250.  
  251. local bytecode_version = GetByte() -- Should always be 3, 4, 5 or 6 apparently
  252. print(bytecode_version)
  253. if bytecode_version > luau_bytecode_max or bytecode_version < luau_bytecode_min then
  254. errorf("Incompatible bytecode version. (%d)", bytecode_version) -- Should never happen
  255. return nil
  256. end
  257.  
  258. local types_version = 0
  259. if bytecode_version >= 4 then
  260. types_version = GetByte()
  261. print(types_version)
  262. end
  263.  
  264. local string_table = {}
  265. local strings_sz = GetVarInt()
  266. print(strings_sz)
  267. for i = 1, strings_sz do -- Luau stores strings as a massive array before all the actual code, so we must first read them all. At some point I plan to inline the strings into custom opcodes to reduce code complexity
  268. string_table[i] = GetString()
  269. end
  270. print(inspect(string_table))
  271. local _userdata_types = {}
  272. while true do
  273. local index = GetByte()
  274. if index == 0 then
  275. break
  276. end
  277. local _name_ref = GetVarInt()
  278. _userdata_types[index] = _name_ref
  279. end
  280. local protos: { luau_proto } = {}
  281. local protos_sz = GetVarInt()
  282. for i = 1, protos_sz do -- Iterate over every proto, including main.
  283. local proto_id = i
  284. local maxstacksize: number = GetByte()
  285. local numparams: number = GetByte()
  286. local numupvalues: number = GetByte()
  287. local isvararg: boolean = GetByte() ~= 0
  288. local types = {}
  289. -- we can recover type data if the bytecode version is 4
  290. if bytecode_version >= 4 then
  291. types.flags = GetByte()
  292. types.data = {}
  293. local type_sz = GetVarInt()
  294. for i = 1, type_sz do
  295. table.insert(types.data, GetByte())
  296. end
  297. end
  298.  
  299. local instructions_sz: number = GetVarInt()
  300. local instructions: { luau_instruction } = {}
  301. local i: number = 0
  302. local n_aux_instructions: number = 0
  303. while i < instructions_sz do
  304. local opcode: number = GetByte()
  305. if is_roblox then
  306. opcode = RobloxOp(opcode)
  307. end
  308. local a: number = GetByte()
  309. local b: number = GetByte()
  310. local c: number = GetByte()
  311. local op: luau_op = GetOp(opcode)
  312. if not op then break end
  313. local aux: number? = nil
  314. if op.aux then
  315. aux = GetInt()
  316. i += 1
  317. n_aux_instructions += 1 -- We keep track of the number of double-width instructions because the number of instructions = instruction_sz - n_aux_instructions; useful for reading debug information (which is never present either way)
  318. end
  319. local function tosigned(int: number, bits: number)
  320. local max = 2^(bits - 1)
  321. if int >= max then
  322. int = int - 2^bits
  323. end
  324. return int
  325. end
  326.  
  327. local d: number = tosigned(bit32.bor(b, bit32.lshift(c, 8)), 16) -- d and e are for ease of coding
  328. local e: number = tosigned(bit32.bor(a, bit32.lshift(b, 8), bit32.lshift(c, 16)), 24)
  329.  
  330. table.insert(instructions, { -- luau_instrctuon
  331. name = op.name,
  332. a = a,
  333. b = b,
  334. c = c,
  335. d = d,
  336. e = e,
  337. aux = aux
  338. })
  339. i += 1
  340. end
  341.  
  342. local constants_sz: number = GetVarInt()
  343. local constants: { luau_constant } = {}
  344. for _ = 1, constants_sz do
  345. local const_type: luau_constant_type = GetByte()
  346. if const_type == 0 then -- NIL
  347. table.insert(constants, {
  348. type = const_type,
  349. value = 0
  350. })
  351. elseif const_type == 1 then -- BOOLEAN
  352. table.insert(constants, {
  353. type = const_type,
  354. value = GetByte() ~= 0
  355. })
  356. elseif const_type == 2 then -- NUMBER
  357. local str: string = ""
  358. for _ = 1, 8 do
  359. str ..= string.char(GetByte())
  360. end
  361. table.insert(constants, {
  362. type = const_type,
  363. value = string.unpack("<d", str)
  364. })
  365. elseif const_type == 3 then -- STRING
  366. table.insert(constants, {
  367. type = const_type,
  368. value = GetVarInt()
  369. })
  370. elseif const_type == 4 then -- IMPORT
  371. table.insert(constants, {
  372. type = const_type,
  373. value = GetInt()
  374. })
  375. elseif const_type == 5 then -- TABLE
  376. local tbl: { number } = {}
  377. local tblSz = GetVarInt()
  378. for _ = 1, tblSz do
  379. table.insert(tbl, GetVarInt()) -- Where VarInt = Key
  380. end
  381. table.insert(constants, {
  382. type = const_type,
  383. value = tbl
  384. })
  385. elseif const_type == 6 then -- CLOSURE
  386. table.insert(constants, {
  387. type = const_type,
  388. value = GetVarInt()
  389. })
  390. end
  391. end
  392.  
  393. local child_protos: { number } = {}
  394. for _ = 1, GetVarInt() do -- Iterate over number of child protos
  395. table.insert(child_protos, GetVarInt()) -- Add index of child proto into the child proto table
  396. end
  397.  
  398. -- The following code is here to skip over debug bytes in the bytecode. This debug information is usually pretty useless so I just discard it.
  399. -- The code is pretty verbose so if you want, you can copy-paste it into your project to read debug data.
  400. -- Most of this can be fetched using the `debug` library provided by most executors
  401.  
  402. local _debug_linedefined: number = GetVarInt()
  403. local _debug_name: number = GetVarInt()
  404.  
  405. local proto: luau_proto = {
  406. maxstacksize = maxstacksize,
  407. numparams = numparams,
  408. numupvalues = numupvalues,
  409. isvararg = isvararg,
  410. instructions = instructions,
  411. constants = constants,
  412. child_protos = child_protos,
  413. name = _debug_name ~= nil and string_table[_debug_name] or nil,
  414. types = types
  415. }
  416.  
  417. protos[proto_id] = proto
  418.  
  419. --[[local _debug_haslines: boolean = GetByte() ~= 0
  420. if _debug_haslines then
  421. local span: number = GetVarInt()
  422. for _ = 1, instructions_sz do
  423. GetByte()
  424. end
  425. for _ = 1, bit32.rshift(instructions_sz - 1, span) + 1 do
  426. GetInt()
  427. end
  428. end--]]
  429.  
  430. local _debug_lineinfoenabled = GetByte() ~= 0
  431. local i_lineinfo = nil
  432.  
  433. if _debug_lineinfoenabled then
  434. local linegaplog2 = GetByte()
  435. local intervals = bit32.rshift((instructions_sz - 1), linegaplog2) + 1
  436. local lineinfo = table.create(instructions_sz)
  437. local abslineinfo = table.create(intervals)
  438. local lastoffset = 0
  439. for _ = 1, instructions_sz do
  440. lastoffset = GetByte()
  441. table.insert(lineinfo, lastoffset)
  442. end
  443. local lastline = 0
  444. for _ = 1, intervals do
  445. lastline += GetInt()
  446. table.insert(abslineinfo, (lastline%(2^32)))
  447. end
  448. instruction_line_info = table.create(instructions_sz)
  449. for i = 1, instructions_sz do
  450. table.insert(instruction_line_info, abslineinfo[bit32.rshift(i - 1, linegaplog2) + 1] + lineinfo[i])
  451. end
  452. end
  453.  
  454. local _debug_hasdebug = GetByte() ~= 0
  455. if _debug_hasdebug then
  456. for _ = 1, GetVarInt() do -- Locals
  457. local _lname = GetVarInt()
  458. local _lstartpc = GetVarInt()
  459. local _lendpc = GetVarInt()
  460. local _lreg = GetByte()
  461. end
  462. for _ = 1, GetVarInt() do -- Upvalues
  463. local _lname = GetVarInt()
  464. end
  465. end
  466. end
  467.  
  468. local bytecode: luau_bytecode = {
  469. version = bytecode_version,
  470. strings = string_table,
  471. protos = protos,
  472. main_proto_id = GetVarInt()
  473. }
  474. print(inspect(bytecode))
  475. return bytecode
  476. end
  477.  
  478. type Register = {
  479. Type: "Register",
  480. Value: number
  481. }
  482.  
  483. type Number = {
  484. Type: "Number",
  485. Value: number
  486. }
  487.  
  488. type String = {
  489. Type: "String",
  490. Value: string
  491. }
  492.  
  493. type Nil = {
  494. Type: "Nil",
  495. Value: nil
  496. }
  497.  
  498. type Boolean = {
  499. Type: "Boolean",
  500. Value: boolean
  501. }
  502.  
  503. type LBC_Import = {
  504. Type: "LBC_Import",
  505. Value: number -- no clue how to decode this :sob:
  506. }
  507.  
  508. type LBC_Table = { -- Constant table
  509. Type: "LBC_Table",
  510. Value: { number }
  511. }
  512.  
  513. type Table = { -- Dynamic table
  514. Type: "Table",
  515. Value: { Value }
  516. }
  517.  
  518. type LBC_Closure = {
  519. Type: "LBC_Closure",
  520. Value: number -- No clue how to decode thsi either :pray:
  521. }
  522.  
  523. type Closure = {
  524. Type: "Closure",
  525. Value: { Instruction },
  526. Params: { Local },
  527. IsVarArg: boolean,
  528. Name: string,
  529. UpValues: { Value },
  530. IsGlobal: boolean,
  531. ParamTypes: { number }
  532. }
  533.  
  534. type TableIndex = {
  535. Type: "TableIndex",
  536. Table: Value,
  537. Index: Value
  538. }
  539.  
  540. type Local = {
  541. Type: "Local",
  542. Register: number,
  543. Value: Value,
  544. Latest: Value,
  545. Id: string,
  546. Scope: luau_proto,
  547. Lifetime: number?,
  548. CreatedByCall: boolean? -- if a local is created by `local v0 = someFunc()`, then reassigning that local is very unlikely, so we create a new local instead
  549. }
  550.  
  551. type Expression = {
  552. Type: "Expression",
  553. Op: string,
  554. Lhs: Value,
  555. Rhs: Value
  556. }
  557.  
  558. type VarArgs = { -- Decompiles to `...`
  559. Type: "VarArgs"
  560. }
  561.  
  562. type Unary = {
  563. Type: "Unary",
  564. Op: string,
  565. Value: Value
  566. }
  567.  
  568. type Strings = { -- Needed for CONCAT
  569. Type: "Strings",
  570. Value: { Value }
  571. }
  572.  
  573. type Global = {
  574. Type: "Global",
  575. Value: string
  576. }
  577.  
  578. type InlineNamecall = {
  579. Type: "InlineNamecall",
  580. Args: { Value },
  581. Object: Value,
  582. Target: String
  583. }
  584.  
  585. type InlineCall = {
  586. Type: "InlineCall",
  587. Args: { Value },
  588. Target: Value
  589. }
  590.  
  591. type Value = Register | Number | String | Nil | Boolean | LBC_Import | LBC_Table | Table | LBC_Closure | Closure | Local | TableIndex | Expression | Unary | Strings | VarArgs | Global | InlineNamecall | InlineCall
  592.  
  593. type Instruction = {
  594. Name: string,
  595. ArgA: Value?,
  596. ArgB: Value?,
  597. ArgC: Value?,
  598. ArgD: Value?,
  599. ArgE: Value?,
  600. ArgAux: Value?,
  601. Special: any
  602. }
  603.  
  604. ---
  605.  
  606. type VarAssignNode = {
  607. Type: "VarAssignNode",
  608. Target: Local,
  609. Value: ValueNode
  610. }
  611.  
  612. type VarReassignNode = {
  613. Type: "VarReassignNode",
  614. Target: Local,
  615. Value: ValueNode
  616. }
  617.  
  618. type GlobalDefinitionNode = {
  619. Type: "GlobalDefinitionNode",
  620. Target: string,
  621. Value: ValueNode
  622. }
  623.  
  624. type ClosureNode = {
  625. Type: "ClosureNode",
  626. Name: string,
  627. Params: { Local },
  628. IsVarArg: boolean,
  629. Body: { Node },
  630. ParamTypes: { number }
  631. }
  632.  
  633. type FunctionCallNode = {
  634. Type: "FunctionCallNode",
  635. Target: ValueNode,
  636. Args: { ValueNode },
  637. RetVals: { Local }
  638. }
  639.  
  640. type ReturnNode = {
  641. Type: "ReturnNode",
  642. Values: { ValueNode }
  643. }
  644.  
  645. type TableAssignNode = {
  646. Type: "TableAssignNode",
  647. Table: ValueNode,
  648. Index: ValueNode,
  649. Source: ValueNode
  650. }
  651.  
  652. type NamecallNode = {
  653. Type: "NamecallNode",
  654. Args: { ValueNode },
  655. RetVals: { Local },
  656. Object: ValueNode,
  657. Target: StringNode
  658. }
  659.  
  660. type BreakNode = {
  661. Type: "BreakNode"
  662. }
  663.  
  664. type ContinueNode = {
  665. Type: "ContinueNode"
  666. }
  667.  
  668. type IfNode = {
  669. Type: "IfNode",
  670. Condition: ValueNode,
  671. Body: { Node },
  672. ElseBody: { Node },
  673. }
  674.  
  675. type WhileNode = {
  676. Type: "WhileNode",
  677. Condition: ValueNode,
  678. Body: { Node }
  679. }
  680.  
  681. type ForRangeNode = { --- for x = y, z[, æ] do ... end
  682. Type: "ForRangeNode",
  683. Index: ValueNode,
  684. Limit: ValueNode,
  685. Step: ValueNode,
  686. Iterator: Local,
  687. Body: { Node }
  688. }
  689.  
  690. type ForValueNode = { --- for x, ... in y, z[, æ] do ... end
  691. Type: "ForValueNode",
  692. Generator: ValueNode,
  693. State: ValueNode,
  694. Index: ValueNode, -- Mostly NilNode
  695. Variables: { Local },
  696. Body: { Node }
  697. }
  698.  
  699. -- Values
  700.  
  701. type NumberNode = {
  702. Type: "NumberNode",
  703. Value: number
  704. }
  705.  
  706. type StringNode = {
  707. Type: "StringNode",
  708. Value: string
  709. }
  710.  
  711. type StringsNode = {
  712. Type: "StringsNode",
  713. Value: { ValueNode }
  714. }
  715.  
  716. type NilNode = {
  717. Type: "NilNode",
  718. }
  719.  
  720. type BooleanNode = {
  721. Type: "BooleanNode",
  722. Value: boolean
  723. }
  724.  
  725. type ExpressionNode = {
  726. Type: "ExpressionNode",
  727. Op: string,
  728. Lhs: ValueNode,
  729. Rhs: ValueNode
  730. }
  731.  
  732. type UnaryNode = {
  733. Type: "UnaryNode",
  734. Op: string,
  735. Value: ValueNode,
  736. }
  737.  
  738. type VarArgsNode = {
  739. Type: "VarArgsNode"
  740. }
  741.  
  742. type GlobalNode = {
  743. Type: "GlobalNode",
  744. Value: string
  745. }
  746.  
  747. type TableIndexNode = {
  748. Type: "TableIndexNode",
  749. Table: ValueNode,
  750. Index: ValueNode
  751. }
  752.  
  753. type TableNode = {
  754. Type: "TableNode",
  755. Body: { ValueNode }
  756. }
  757.  
  758. type InlineNamecallNode = { -- Allow for things such as `game:GetService("Players").LocalPlayer.Name` etc
  759. Type: "InlineNamecallNode",
  760. Args: { ValueNode },
  761. Object: ValueNode,
  762. Target: StringNode
  763. }
  764.  
  765. type InlineCallNode = { -- MULTRET call (`print(add(1, 2))`)
  766. Type: "InlineCallNode",
  767. Args: { ValueNode },
  768. Target: ValueNode
  769. }
  770.  
  771. type ValueNode = NumberNode | StringNode | NilNode | BooleanNode | ExpressionNode | UnaryNode | VarArgsNode | ClosureNode | Local | TableIndexNode | GlobalNode | StringsNode | TableNode | InlineNamecallNode | InlineCallNode
  772. type Node = VarAssignNode | VarReassignNode | GlobalDefinitionNode | ClosureNode | ValueNode | FunctionCallNode | ReturnNode | TableAssignNode | NamecallNode | BreakNode | ContinueNode | IfNode | WhileNode | ForRangeNode | ForValueNode
  773.  
  774. type AST = Node
  775.  
  776. ---
  777.  
  778. type BuiltInFunction = {
  779. Path: { string }
  780. }
  781.  
  782. local builtins: { BuiltInFunction } = { -- ORDER SENSITIVE
  783. { Path = { "assert" }},
  784. -- math
  785. { Path = { "math", "abs" }},
  786. { Path = { "math", "acos" }},
  787. { Path = { "math", "asin" }},
  788. { Path = { "math", "atan2" }},
  789. { Path = { "math", "atan" }},
  790. { Path = { "math", "ceil" }},
  791. { Path = { "math", "cosh" }},
  792. { Path = { "math", "cos" }},
  793. { Path = { "math", "deg" }},
  794. { Path = { "math", "exp" }},
  795. { Path = { "math", "floor" }},
  796. { Path = { "math", "fmod" }},
  797. { Path = { "math", "frexp" }},
  798. { Path = { "math", "ldexp" }},
  799. { Path = { "math", "log10" }},
  800. { Path = { "math", "log" }},
  801. { Path = { "math", "max" }},
  802. { Path = { "math", "min" }},
  803. { Path = { "math", "modf" }},
  804. { Path = { "math", "pow" }},
  805. { Path = { "math", "rad" }},
  806. { Path = { "math", "sinh" }},
  807. { Path = { "math", "sin" }},
  808. { Path = { "math", "sqrt" }},
  809. { Path = { "math", "tanh" }},
  810. { Path = { "math", "tan" }},
  811. -- bit32
  812. { Path = { "bit32", "arshift" }},
  813. { Path = { "bit32", "band" }},
  814. { Path = { "bit32", "bnot" }},
  815. { Path = { "bit32", "bor" }},
  816. { Path = { "bit32", "bxor" }},
  817. { Path = { "bit32", "btest" }},
  818. { Path = { "bit32", "extract" }},
  819. { Path = { "bit32", "lrotate" }},
  820. { Path = { "bit32", "lshift" }},
  821. { Path = { "bit32", "replace" }},
  822. { Path = { "bit32", "rrotate" }},
  823. { Path = { "bit32", "rshift" }},
  824.  
  825. { Path = { "type" }},
  826.  
  827. -- string
  828. { Path = { "string", "byte" }},
  829. { Path = { "string", "char" }},
  830. { Path = { "string", "len" }},
  831.  
  832. { Path = { "typeof" }},
  833.  
  834. -- string
  835. { Path = { "string", "sub" }},
  836.  
  837. -- math
  838. { Path = { "math", "clamp" }},
  839. { Path = { "math", "sign" }},
  840. { Path = { "math", "round" }},
  841.  
  842. -- raw*
  843. { Path = { "rawset" }},
  844. { Path = { "rawget" }},
  845. { Path = { "rawequal" }},
  846.  
  847. -- table
  848. { Path = { "table", "insert" }},
  849. { Path = { "table", "unpack" }},
  850.  
  851. -- vector constructor
  852. { Path = { "Vector3", "new" }},
  853.  
  854. -- bit32.count
  855. { Path = { "bit32", "countlz" }},
  856. { Path = { "bit32", "countrz" }},
  857.  
  858. -- select(_, ...)
  859. { Path = { "select" }},
  860.  
  861. { Path = { "rawlen" }},
  862.  
  863. { Path = { "bit32", "extract" }},
  864.  
  865. { Path = { "getmetatable" }},
  866. { Path = { "setmetatable" }},
  867.  
  868. { Path = { "tonumber" }},
  869. { Path = { "tostring" }},
  870. -- bit32.byteswap(n)
  871. { Path = { "bit32", "byteswap" }},
  872. -- buffer
  873. { Path = { "buffer", "readi8" }},
  874. { Path = { "buffer", "readu8" }},
  875. { Path = { "buffer", "writeu8" }},
  876. { Path = { "buffer", "readi16" }},
  877. { Path = { "buffer", "readu16" }},
  878. { Path = { "buffer", "writeu16" }},
  879. { Path = { "buffer", "readi32" }},
  880. { Path = { "buffer", "readu32" }},
  881. { Path = { "buffer", "writeu32" }},
  882. { Path = { "buffer", "readf32" }},
  883. { Path = { "buffer", "writef64" }},
  884. { Path = { "buffer", "readf64" }},
  885. }
  886.  
  887. local function ConvertBuiltin(id: number): GlobalNode | TableIndexNode
  888. local path = builtins[id].Path
  889. if #path == 1 then
  890. return {
  891. Type = "GlobalNode",
  892. Value = path[1]
  893. }
  894. end
  895. return {
  896. Type = "TableIndexNode",
  897. Table = {
  898. Type = "GlobalNode",
  899. Value = path[1]
  900. },
  901. Index = {
  902. Type = "StringNode",
  903. Value = path[2]
  904. }
  905. }
  906. end
  907.  
  908. local function wdec_decompile(bytecode: luau_bytecode): string
  909. local start_time = tick()
  910. local function ReAssigns(instruction: luau_instruction, target: number): boolean -- Function to check if target register `target` is written to by `instruction`
  911. if not table.find({ -- Below is a list of instructions which can possibly write to a register
  912. -- TODO: finish this list; itup shit if it isn't
  913. "GETGLOBAL", "GETUPVAL", "NEWCLOSURE",
  914. "ADD", "SUB", "MUL", "DIV", "ADDK",
  915. "SUBK", "MULK", "DIVK", "MOD", "POW",
  916. "MODK", "POWK", "AND", "OR", "ANDK",
  917. "ORK", "CONCAT", "NOT", "MINUS", "LENGTH",
  918. "NEWTABLE", "DUPTABLE", "GETVARARGS",
  919. "LOADKX", "IDIV", "IDIVK", "LOADN",
  920. "LOADK", "LOADB", "MOVE", "LOADNIL",
  921. "DUPCLOSURE", "CALL", "SETLIST",
  922. "FORNPREP", "GETTABLEKS", "SUBRK", "DIVRK"
  923. }, instruction.name) then
  924. return false -- return false if the instruction doesn't write (JUMP, etc)
  925. end
  926. if instruction.name == "CALL" then
  927. return instruction.c - 1 > 0 and target >= instruction.a and target <= instruction.a + instruction.c - 1
  928. end
  929. if instruction.name == "FORNPREP" then
  930. return target == instruction.a + 2
  931. end
  932. if table.find({
  933. "ADD", "SUB", "MUL", "DIV", "ADDK",
  934. "SUBK", "MULK", "DIVK", "MOD", "POW",
  935. "MODK", "POWK", "AND", "OR", "ANDK", "ORK", "SUBRK", "DIVRK"
  936. }, instruction.name) then
  937. if instruction.a == instruction.b and target == instruction.a then
  938. return false -- Here it technically gets re-assigned, BUT it's still the same variable
  939. end
  940. end
  941. return instruction.a == target -- Target register is always instr.a
  942. end
  943. local function References(instruction: luau_instruction, target: number): boolean -- Function to check whether `instruction` references (retrieves the value of) register `target`
  944. if table.find({ -- Below is a list of functions which reference only one register and it references it in instr.b
  945. "MOVE", "GETTABLEKS", "SETTABLEKS", "GETTABLEN", "NAMECALL", "ADDK",
  946. "SUBK", "MULK", "DIVK", "MODK", "POWK", "ANDK", "ORK", "NOT", "MINUS",
  947. "LENGTH", "CAPTURE", "FASTCALL1", "FASTCALL2K", "IDIVK", "JUMPXEQKNIL",
  948. "JUMPXEQKB", "JUMPXEQKN", "JUMPXEQKS", "SUBRK", "DIVRK"
  949. }, instruction.name) then
  950. return instruction.b == target -- Return whether it's referenced
  951. elseif instruction.name == "CALL" then
  952. return target == instruction.b or (instruction.c ~= 0 and target == instruction.a + 1 + instruction.c)
  953. elseif table.find({
  954. "JUMPIFEQ", "JUMPIFLE", "JUMPIFLT",
  955. "JUMPIFNOTEQ", "JUMPIFNOTLE", "JUMPIFNOTLT"
  956. }, instruction.name) then
  957. return instruction.a == target or instruction.aux == target
  958. elseif table.find({
  959. "JUMPIF", "JUMPIFNOT",
  960. }, instruction.name) then
  961. return instruction.a == target
  962. elseif instruction.name == "FASTCALL2" then
  963. return instruction.aux == target
  964. elseif table.find({ -- Below is a list of instructions which reference two registers
  965. "GETTABLE", "SETTABLE", "ADD", "SUB", "MUL", "DIV", "MOD", "POW", "AND", "OR", "IDIV"
  966. }, instruction.name) then
  967. return (instruction.a == instruction.b and instruction.a == target) or instruction.b == target or instruction.c == target -- return if the register is referenced
  968. elseif instruction.name == "CONCAT" then
  969. return target >= instruction.b and target <= instruction.c
  970. elseif instruction.name == "SETLIST" then
  971. return target >= instruction.b and target <= instruction.b + instruction.c
  972. end
  973. return false
  974. end
  975. local function WritesTo(instruction: luau_instruction, _table: number): boolean
  976. if not table.find({
  977. "SETTABLE", "SETTABLEKS", "SETTABLEN"
  978. }, instruction.name) then
  979. return false
  980. end
  981. return _table == instruction.b
  982. end
  983. local function Captures(instruction: luau_instruction, target: number): boolean
  984. return instruction.name == "CAPTURE" and instruction.b == target
  985. end
  986.  
  987. local function GetConstant(proto: luau_proto, index: number, isglobal: boolean?): Value -- Simple function to retrieve the constant at index `index` of proto `proto`. It also fetches strings and produces Refounc-pass friendly values
  988. local const: luau_constant = proto.constants[index + 1]
  989. if const.type == 0 then
  990. return {
  991. Type = "Nil"
  992. }
  993. elseif const.type == 1 then
  994. return {
  995. Type = "Boolean",
  996. Value = const.value :: boolean
  997. }
  998. elseif const.type == 2 then
  999. return {
  1000. Type = "Number",
  1001. Value = const.value :: number
  1002. }
  1003. elseif const.type == 3 then
  1004. if not isglobal then
  1005. return {
  1006. Type = "String",
  1007. Value = bytecode.strings[const.value :: number]
  1008. }
  1009. else
  1010. return {
  1011. Type = "Global",
  1012. Value = bytecode.strings[const.value :: number]
  1013. }
  1014. end
  1015. elseif const.type == 4 then
  1016. return {
  1017. Type = "LBC_Import",
  1018. Value = const.value :: number
  1019. }
  1020. elseif const.type == 5 then
  1021. return {
  1022. Type = "LBC_Table",
  1023. Value = const.value :: { number }
  1024. }
  1025. elseif const.type == 6 then
  1026. return {
  1027. Type = "LBC_Closure",
  1028. Value = const.value :: number
  1029. }
  1030. end
  1031. return {
  1032. Type = "Nil"
  1033. }
  1034. end
  1035.  
  1036. local function RefcountProto(proto: luau_proto, upvalues: { Local }, passed_regs: { [number]: Value }?): { Instruction } -- Function to filter out any immediate values from actual varables, allowing for very simplistic decompilation
  1037. local instructions: { Instruction } = {}
  1038. local idx = 1
  1039. local vars: { Local } = {}
  1040. local regs: { [number]: Value } = passed_regs or {}
  1041. local regs_stack: { { [number]: Value } } = {}
  1042. local multret_start: number = -1 -- Instructions with MULTRET (CALL, RETURN, etc) will always tell u the starting register, so we can use that to emit MULTRET stuff
  1043. local function Variable(instr: Instruction, value: Value, target: number, force_emit: boolean?): boolean
  1044. local refs = 0
  1045. local loop_depth = 0
  1046.  
  1047. if not force_emit then
  1048. local writes = 0
  1049. local ends: { number } = {}
  1050. for i = value.Type == "InlineNamecall" and idx or idx + 1, #proto.instructions do
  1051. if Captures(proto.instructions[i], target) then
  1052. force_emit = true
  1053. break
  1054. end
  1055. if WritesTo(proto.instructions[i], target) then
  1056. writes += 1
  1057. if writes >= 2 then
  1058. force_emit = true
  1059. break
  1060. end
  1061. end
  1062. if loop_depth == 0 and ReAssigns(proto.instructions[i], target) then
  1063. break
  1064. elseif ReAssigns(proto.instructions[i], target) then
  1065. -- Check if a variable assigned to inside a scope is used outside the scope, if not, don't emit (unless other conditions met)
  1066. local exitted_scope = false
  1067. local new_i = 0
  1068. for j = i, #proto.instructions do
  1069. for _, x in next, ends do
  1070. if j == x then
  1071. exitted_scope = true
  1072. new_i = j
  1073. break
  1074. end
  1075. end
  1076. if exitted_scope then break end
  1077. end
  1078. local outside_refs = 0
  1079. for j = new_i, #proto.instructions do
  1080. if References(proto.instructions[j], target) then
  1081. outside_refs += 1
  1082. end
  1083. end
  1084. if outside_refs <= 1 then
  1085. break
  1086. end
  1087. end
  1088. if References(proto.instructions[i], target) then
  1089. refs += 1
  1090. end
  1091. -- TODO: Special case for JUMPX
  1092. if table.find({"JUMP", "JUMPIF", "JUMPIFNOT", "JUMPIFEQ", "JUMPIFNOTEQ", "JUMPIFLT", "JUMPIFLE", "JUMPIFNOTLT", "JUMPIFNOTLE", "FORGPREP", "FORGPREP_NEXT", "FORGPREP_INEXT", "FORNPREP"}, proto.instructions[i].name) then
  1093. loop_depth += 1
  1094. local bytes_counted: number = 0
  1095. local ninstructions: number = 0
  1096. while bytes_counted < proto.instructions[i].d do
  1097. bytes_counted += 1
  1098. ninstructions += 1
  1099. if proto.instructions[idx + ninstructions].aux ~= nil then
  1100. bytes_counted += 1
  1101. end
  1102. end
  1103. table.insert(ends, i + ninstructions)
  1104. end
  1105. for _, j in next, ends do
  1106. if i == j then
  1107. loop_depth -= 1
  1108. end
  1109. end
  1110. end
  1111. end
  1112.  
  1113. if (value.Type == "InlineNamecall" or value.Type == "InlineCall") and refs == 0 then
  1114. force_emit = true
  1115. end
  1116.  
  1117. -- TODO: Check for self reassigns and ALWAYS emit them
  1118. -- Ex: a = a + 1 (or a += 1)
  1119.  
  1120. if refs > 1 or force_emit then
  1121. if not regs[target] or regs[target].Type ~= "Local" or (regs[target] :: Local).Scope ~= proto or (regs[target] and regs[target].Type == "Local" and (regs[target] :: Local).CreatedByCall) then
  1122. regs[target] = {
  1123. Type = "Local",
  1124. Id = "",
  1125. Value = value,
  1126. Latest = value,
  1127. Register = target,
  1128. Scope = proto
  1129. }
  1130. instr.ArgA = regs[target]
  1131. table.insert(instructions, instr)
  1132. elseif regs[target] and regs[target].Type == "Local" then
  1133. instr.ArgA = regs[target]
  1134. table.insert(instructions, instr)
  1135. end
  1136. else
  1137. regs[target] = value
  1138. table.insert(instructions, {
  1139. Name = "NOP"
  1140. })
  1141. end
  1142. return (refs > 1 or force_emit) :: boolean
  1143. end
  1144.  
  1145. local function CloneRegs(nbytes: number)
  1146. -- This function will replace the regs table with an exact clone for nbytes bytes, as
  1147. -- it separates newly created variables within the scope from variables outside the scope, while
  1148. -- still alowing for re-assigns
  1149.  
  1150. -- below code converts the number of bytes to a number of instructions by counting instructions with an AUX as 2 bytes.
  1151. local bytes_counted: number = 0
  1152. local ninstructions: number = 0
  1153. while bytes_counted <= nbytes and idx + ninstructions < #proto.instructions do
  1154. bytes_counted += 1
  1155. ninstructions += 1
  1156. if proto.instructions[idx + ninstructions] and proto.instructions[idx + ninstructions].aux ~= nil then
  1157. bytes_counted += 1
  1158. end
  1159. end
  1160.  
  1161. local regs_clone: { [number] : Value } = {}
  1162. for k, v in next, regs do
  1163. regs_clone[k] = v
  1164. end
  1165. if not regs_stack[idx + ninstructions] then
  1166. regs_stack[idx + ninstructions] = regs_clone
  1167. end
  1168. end
  1169.  
  1170. while idx <= #proto.instructions do
  1171. for index, old_regs in next, regs_stack do
  1172. if idx == index and old_regs then
  1173. table.clear(regs)
  1174. for k, v in next, old_regs do
  1175. regs[k] = v
  1176. end
  1177. regs_stack[idx] = nil
  1178. break
  1179. end
  1180. end
  1181. local function GetUpvalues(): { Local }
  1182. local l_upvalues: { Local } = {}
  1183. while proto.instructions[idx + 1].name == "CAPTURE" do
  1184. if proto.instructions[idx + 1].a <= 1 then
  1185. table.insert(l_upvalues, regs[proto.instructions[idx + 1].b] :: Local)
  1186. elseif proto.instructions[idx + 1].a == 2 then
  1187. table.insert(l_upvalues, upvalues[proto.instructions[idx + 1].b + 1])
  1188. end
  1189. table.insert(instructions, { Name = "NOP" })
  1190. idx += 1
  1191. end
  1192. return l_upvalues
  1193. end
  1194. local current: luau_instruction = proto.instructions[idx]
  1195. if current.name == "LOADN" then
  1196. Variable({ -- this is a VERY gross format to call a function lmfao
  1197. Name = "LOADN",
  1198. ArgB = {
  1199. Type = "Number",
  1200. Value = current.d
  1201. }
  1202. }, {
  1203. Type = "Number",
  1204. Value = current.d
  1205. }, current.a)
  1206. elseif current.name == "LOADB" then
  1207. Variable({
  1208. Name = "LOADB",
  1209. ArgB = {
  1210. Type = "Boolean",
  1211. Value = current.b == 1
  1212. },
  1213. ArgC = {
  1214. Type = "Number",
  1215. Value = current.c
  1216. }
  1217. }, {
  1218. Type = "Boolean",
  1219. Value = current.b == 1
  1220. }, current.a)
  1221. elseif current.name == "LOADK" then
  1222. Variable({
  1223. Name = "LOADK",
  1224. ArgB = GetConstant(proto, current.d)
  1225. }, GetConstant(proto, current.d), current.a)
  1226. elseif current.name == "LOADNIL" then
  1227. Variable({
  1228. Name = "LOADNIL",
  1229. ArgB = {Type = "Nil"}
  1230. }, {Type = "Nil"}, current.a)
  1231. elseif current.name == "MOVE" then
  1232. Variable({
  1233. Name = "MOVE",
  1234. ArgB = regs[current.b]
  1235. }, regs[current.b], current.a)
  1236. elseif current.name == "GETGLOBAL" then
  1237. Variable({
  1238. Name = "GETGLOBAL",
  1239. ArgAux = GetConstant(proto, current.aux :: number, true)
  1240. }, GetConstant(proto, current.aux :: number, true), current.a)
  1241. elseif current.name == "SETGLOBAL" then
  1242. table.insert(instructions, {
  1243. Name = "SETGLOBAL",
  1244. ArgA = regs[current.a],
  1245. ArgAux = GetConstant(proto, current.aux :: number)
  1246. })
  1247. elseif current.name == "GETUPVAL" then
  1248. Variable({
  1249. Name = "GETUPVAL",
  1250. ArgB = upvalues[current.b + 1]
  1251. }, upvalues[current.b + 1], current.a)
  1252. elseif current.name == "SETUPVAL" then
  1253. table.insert(instructions, {
  1254. Name = "SETUPVAL",
  1255. ArgA = regs[current.a],
  1256. ArgB = upvalues[current.b + 1]
  1257. })
  1258. elseif current.name == "GETIMPORT" then
  1259. local path_length: number = bit32.rshift(current.aux :: number, 30)
  1260. local path = {table.unpack({bit32.band(bit32.rshift(current.aux :: number, 20), 0b1111111111), bit32.band(bit32.rshift(current.aux :: number, 10), 0b1111111111), bit32.band(bit32.rshift(current.aux :: number, 0), 0b1111111111)}, 1, path_length)}
  1261.  
  1262. local import_path: Value
  1263.  
  1264. if path_length == 1 then
  1265. import_path = {
  1266. Type = "Global",
  1267. Value = (GetConstant(proto, path[1]) :: String).Value
  1268. }
  1269. elseif path_length == 2 then
  1270. import_path = {
  1271. Type = "TableIndex",
  1272. Table = {
  1273. Type = "Global",
  1274. Value = (GetConstant(proto, path[1]) :: String).Value
  1275. },
  1276. Index = GetConstant(proto, path[2]) :: String
  1277. }
  1278. else
  1279. import_path = {
  1280. Type = "TableIndex",
  1281. Table = {
  1282. Type = "TableIndex",
  1283. Table = {
  1284. Type = "Global",
  1285. Value = (GetConstant(proto, path[1]) :: String).Value
  1286. },
  1287. Index = GetConstant(proto, path[2]) :: String
  1288. },
  1289. Index = GetConstant(proto, path[3]) :: String
  1290. }
  1291. end
  1292. Variable({
  1293. Name = "GETIMPORT",
  1294. Special = import_path
  1295. }, import_path, current.a)
  1296. elseif current.name == "GETTABLE" then -- TODO: Fix up table shit with chaining. Nvm I think it works????
  1297. Variable({
  1298. Name = "GETTABLE",
  1299. Special = {
  1300. Type = "TableIndex",
  1301. Table = regs[current.b],
  1302. Index = regs[current.c]
  1303. }
  1304. }, {
  1305. Type = "TableIndex",
  1306. Table = regs[current.b],
  1307. Index = regs[current.c]
  1308. }, current.a)
  1309. elseif current.name == "SETTABLE" then
  1310. table.insert(instructions, {
  1311. Name = "SETTABLE",
  1312. ArgA = regs[current.a],
  1313. ArgB = regs[current.b],
  1314. ArgC = regs[current.c]
  1315. })
  1316. elseif current.name == "GETTABLEKS" then
  1317. Variable({
  1318. Name = "GETTABLE",
  1319. Special = {
  1320. Type = "TableIndex",
  1321. Table = regs[current.b],
  1322. Index = GetConstant(proto, current.aux :: number)
  1323. }
  1324. }, {
  1325. Type = "TableIndex",
  1326. Table = regs[current.b],
  1327. Index = GetConstant(proto, current.aux :: number)
  1328. }, current.a)
  1329. elseif current.name == "SETTABLEKS" then
  1330. table.insert(instructions, {
  1331. Name = "SETTABLE",
  1332. ArgA = regs[current.a],
  1333. ArgB = regs[current.b],
  1334. ArgC = GetConstant(proto, current.aux :: number)
  1335. })
  1336. elseif current.name == "GETTABLEN" then
  1337. Variable({
  1338. Name = "GETTABLE",
  1339. Special = {
  1340. Type = "TableIndex",
  1341. Table = regs[current.b],
  1342. Index = {
  1343. Type = "Number",
  1344. Value = current.c
  1345. }
  1346. }
  1347. }, {
  1348. Type = "TableIndex",
  1349. Table = regs[current.b],
  1350. Index ={
  1351. Type = "Number",
  1352. Value = current.c
  1353. }
  1354. }, current.a)
  1355. elseif current.name == "SETTABLEN" then
  1356. table.insert(instructions, {
  1357. Name = "SETTABLEN",
  1358. ArgA = regs[current.a],
  1359. ArgB = regs[current.b],
  1360. ArgC = {
  1361. Type = "Number",
  1362. Value = current.c
  1363. }
  1364. })
  1365. elseif current.name == "NEWCLOSURE" then
  1366. local _proto = bytecode.protos[proto.child_protos[current.d + 1] + 1]
  1367. local params: { Local } = {}
  1368. local val: Closure = {
  1369. Type = "Closure",
  1370. Value = {},
  1371. Params = params,
  1372. IsVarArg = _proto.isvararg,
  1373. Name = _proto.name or "",
  1374. UpValues = upvalues,
  1375. IsGlobal = false,
  1376. ParamTypes = _proto.types.data
  1377. }
  1378. Variable({
  1379. Name = "NEWCLOSURE",
  1380. ArgD = val
  1381. }, val, current.a, _proto.name ~= nil)
  1382. local upvals: { Local } = GetUpvalues()
  1383. local regs_params: { [number]: Value } = {}
  1384. for i = 1, _proto.numparams do
  1385. table.insert(params, {
  1386. Type = "Local",
  1387. Id = "",
  1388. Register = i - 1,
  1389. Value = { Type = "Nil" },
  1390. Latest = { Type = "Nil" },
  1391. Scope = proto
  1392. })
  1393. regs_params[i - 1] = params[#params]
  1394. end
  1395. val.Value = RefcountProto(_proto, upvals, regs_params)
  1396.  
  1397. if proto.instructions[idx + 1].name == "SETGLOBAL" then
  1398. local ins: luau_instruction = proto.instructions[idx + 1]
  1399. if ins.a == current.a then
  1400. val.IsGlobal = true
  1401. idx += 1
  1402. end
  1403. end
  1404. elseif current.name == "NAMECALL" then
  1405. local call_instr: luau_instruction = proto.instructions[idx + 1]
  1406. idx += 1 -- Consume the call
  1407. table.insert(instructions, { Name = "NOP" }) -- Add a NOP to balance out the missing CALL
  1408. local args: { Value } = {}
  1409. local retvals: { Local } = {}
  1410. for i = call_instr.a + 2, call_instr.a + call_instr.b - 1 do -- We add one to the args start because the first argument will be `self`
  1411. table.insert(args, regs[i])
  1412. end
  1413. if call_instr.b == 0 then
  1414. for i = call_instr.a + 2, call_instr.a + multret_start do
  1415. table.insert(args, regs[i])
  1416. end
  1417. end
  1418. for i = 1, call_instr.c - 1 do
  1419. table.insert(retvals, {
  1420. Type = "Local",
  1421. Id = "",
  1422. Register = call_instr.a + i - 1,
  1423. Value = { Type = "Nil" },
  1424. Latest = { Type = "Nil" },
  1425. Scope = proto,
  1426. CreatedByCall = true
  1427. })
  1428. table.insert(vars, retvals[#retvals])
  1429. end
  1430. if call_instr.c == 0 then
  1431. multret_start = current.a
  1432. regs[current.a] = {
  1433. Type = "InlineNamecall",
  1434. Args = args,
  1435. Target = GetConstant(proto, current.aux :: number) :: String,
  1436. Object = regs[current.b]
  1437. }
  1438. if not regs[call_instr.a] then
  1439. regs[call_instr.a] = {
  1440. Type = "Local",
  1441. Register = call_instr.a,
  1442. Value = regs[current.a - 1],
  1443. Latest = regs[current.a - 1],
  1444. Id = "",
  1445. Scope = proto
  1446. }
  1447. table.insert(vars, regs[call_instr.a] :: Local)
  1448. end
  1449. end
  1450. if call_instr.c ~= 0 then
  1451. local emitted: boolean = true
  1452. if call_instr.c == 2 then
  1453. emitted = Variable({
  1454. Name = "NAMECALL",
  1455. Special = { args, retvals },
  1456. ArgB = regs[current.b],
  1457. ArgAux = GetConstant(proto, current.aux :: number)
  1458. }, {
  1459. Type = "InlineNamecall",
  1460. Args = args,
  1461. Target = GetConstant(proto, current.aux :: number) :: String,
  1462. Object = regs[current.b]
  1463. }, current.a)
  1464. else
  1465. table.insert(instructions, {
  1466. Name = "NAMECALL",
  1467. Special = { args, retvals },
  1468. ArgB = regs[current.b],
  1469. ArgAux = GetConstant(proto, current.aux :: number)
  1470. })
  1471. end
  1472. if emitted then
  1473. for i, retval in next, retvals do -- explained below
  1474. regs[current.a + i - 1] = retval
  1475. table.insert(vars, regs[current.a + i - 1] :: Local)
  1476. end
  1477. end
  1478. end
  1479. elseif current.name == "CALL" then
  1480. local args: { Value } = {}
  1481. local retvals: { Local } = {}
  1482. for i = current.a + 1, current.a + current.b - 1 do
  1483. table.insert(args, regs[i])
  1484. end
  1485. if current.b == 0 then
  1486. for i = current.a + 1, multret_start do
  1487. table.insert(args, regs[i])
  1488. end
  1489. end
  1490. for i = 1, current.c - 1 do
  1491. table.insert(retvals, {
  1492. Type = "Local",
  1493. Id = "",
  1494. Register = current.a + i - 1,
  1495. Value = { Type = "Nil" },
  1496. Latest = { Type = "Nil" },
  1497. Scope = proto,
  1498. CreatedByCall = true
  1499. })
  1500. end
  1501. local emitted: boolean = true
  1502. local target = regs[current.a]
  1503. if current.c > 0 then
  1504. if current.c == 2 then
  1505. emitted = Variable({
  1506. Name = "CALL",
  1507. ArgA = target,
  1508. Special = { args, retvals },
  1509. }, {
  1510. Type = "InlineCall",
  1511. Args = args,
  1512. Target = target
  1513. }, current.a)
  1514. if emitted then -- Set the ArgA back to the function target
  1515. instructions[#instructions].ArgA = target
  1516. end
  1517. else
  1518. table.insert(instructions, {
  1519. Name = "CALL",
  1520. ArgA = target,
  1521. Special = { args, retvals },
  1522. })
  1523. end
  1524. else
  1525. table.insert(instructions, { -- Create a NOP as an inline call will be emitted by a MULTRET instr
  1526. Name = "NOP"
  1527. })
  1528. multret_start = current.a
  1529. regs[current.a] = {
  1530. Type = "InlineCall",
  1531. Args = args,
  1532. Target = regs[current.a]
  1533. }
  1534. end
  1535. if emitted then
  1536. for i, retval in next, retvals do -- Return values of functions are written to where the function lives, so we have to replace the function (and its arguments) with the return values so it can be referenced later
  1537. regs[current.a + i - 1] = retval
  1538. table.insert(vars, regs[current.a + i - 1] :: Local)
  1539. end
  1540. end
  1541. elseif current.name == "RETURN" then
  1542. local values: { Value } = {}
  1543. for i = 1, current.b - 1 do
  1544. table.insert(values, regs[current.a + i - 1])
  1545. end
  1546. if current.b == 0 then
  1547. for i = current.a, multret_start do
  1548. table.insert(values, regs[i])
  1549. end
  1550. end
  1551. table.insert(instructions, {
  1552. Name = "RETURN",
  1553. Special = values
  1554. })
  1555. elseif current.name == "JUMP" then
  1556. CloneRegs(current.d)
  1557. table.insert(instructions, {
  1558. Name = "JUMP",
  1559. ArgD = {
  1560. Type = "Number",
  1561. Value = current.d
  1562. }
  1563. })
  1564. elseif current.name == "JUMPBACK" then
  1565. table.insert(instructions, {
  1566. Name = "JUMPBACK",
  1567. ArgD = {
  1568. Type = "Number",
  1569. Value = current.d
  1570. }
  1571. })
  1572. elseif current.name == "JUMPIF" then
  1573. CloneRegs(current.d)
  1574. table.insert(instructions, {
  1575. Name = "JUMPIF",
  1576. ArgA = regs[current.a],
  1577. ArgD = {
  1578. Type = "Number",
  1579. Value = current.d
  1580. }
  1581. })
  1582. elseif current.name == "JUMPIFNOT" then
  1583. CloneRegs(current.d)
  1584. table.insert(instructions, {
  1585. Name = "JUMPIFNOT",
  1586. ArgA = regs[current.a],
  1587. ArgD = {
  1588. Type = "Number",
  1589. Value = current.d
  1590. }
  1591. })
  1592. elseif table.find({"JUMPIFEQ", "JUMPIFLE", "JUMPIFLT", "JUMPIFNOTEQ", "JUMPIFNOTLE", "JUMPIFNOTLT"}, current.name) then
  1593. CloneRegs(current.d)
  1594. table.insert(instructions, {
  1595. Name = current.name,
  1596. ArgA = regs[current.a],
  1597. ArgD = {
  1598. Type = "Number",
  1599. Value = current.d
  1600. },
  1601. ArgAux = regs[current.aux :: number]
  1602. })
  1603. elseif table.find({"ADD", "SUB", "MUL", "DIV", "MOD", "POW"}, current.name) then
  1604. Variable({
  1605. Name = current.name,
  1606. Special = {
  1607. Type = "Expression",
  1608. Op = ({ADD="+", SUB="-", MUL="*", DIV="/", MOD="%", POW="^"})[current.name],
  1609. Lhs = regs[current.b],
  1610. Rhs = regs[current.c]
  1611. }
  1612. }, {
  1613. Type = "Expression",
  1614. Op = ({ADD="+", SUB="-", MUL="*", DIV="/", MOD="%", POW="^"})[current.name],
  1615. Lhs = regs[current.b],
  1616. Rhs = regs[current.c]
  1617. }, current.a)
  1618. elseif table.find({"ADDK", "SUBK", "MULK", "DIVK", "MODK", "POWK", "SUBRK", "DIVRK"}, current.name) then
  1619. Variable({
  1620. Name = current.name,
  1621. Special = {
  1622. Type = "Expression",
  1623. Op = ({ADDK="+", SUBK="-", MULK="*", DIVK="/", MODK="%", POWK="^", SUBRK="-", DIVRK="/"})[current.name],
  1624. Lhs = regs[current.b],
  1625. Rhs = GetConstant(proto, current.c)
  1626. }
  1627. }, {
  1628. Type = "Expression",
  1629. Op = ({ADDK="+", SUBK="-", MULK="*", DIVK="/", MODK="%", POWK="^", SUBRK="-", DIVRK="/"})[current.name],
  1630. Lhs = regs[current.b],
  1631. Rhs = GetConstant(proto, current.c)
  1632. }, current.a)
  1633. elseif table.find({"AND", "OR"}, current.name) then
  1634. Variable({
  1635. Name = current.name,
  1636. Special = {
  1637. Type = "Expression",
  1638. Op = ({AND="and", OR="or"})[current.name],
  1639. Lhs = regs[current.b],
  1640. Rhs = regs[current.c]
  1641. }
  1642. }, {
  1643. Type = "Expression",
  1644. Op = ({AND="and", OR="or"})[current.name],
  1645. Lhs = regs[current.b],
  1646. Rhs = regs[current.c]
  1647. }, current.a)
  1648. elseif table.find({"ANDK", "ORK"}, current.name) then
  1649. Variable({
  1650. Name = current.name,
  1651. Special = {
  1652. Type = "Expression",
  1653. Op = ({ANDK="and", ORK="or"})[current.name],
  1654. Lhs = regs[current.b],
  1655. Rhs = regs[current.c]
  1656. }
  1657. }, {
  1658. Type = "Expression",
  1659. Op = ({ANDK="and", ORK="or"})[current.name],
  1660. Lhs = regs[current.b],
  1661. Rhs = regs[current.c]
  1662. }, current.a)
  1663. elseif table.find({"NOT", "MINUS", "LENGTH"}, current.name) then
  1664. Variable({
  1665. Name = current.name,
  1666. Special = {
  1667. Type = "Unary",
  1668. Op = ({NOT="not ", MINUS="-", LENGTH="#"})[current.name],
  1669. Value = regs[current.b]
  1670. }
  1671. }, {
  1672. Type = "Unary",
  1673. Op = ({NOT="not ", MINUS="-", LENGTH="#"})[current.name],
  1674. Value = regs[current.b]
  1675. }, current.a)
  1676. elseif current.name == "CONCAT" then
  1677. local values: { Value } = {}
  1678. for i = current.b, current.c do
  1679. table.insert(values, regs[i])
  1680. end
  1681. Variable({
  1682. Name = "CONCAT",
  1683. Special = {
  1684. Type = "Strings",
  1685. Value = values
  1686. }
  1687. }, {
  1688. Type = "Strings",
  1689. Value = values
  1690. }, current.a)
  1691. elseif current.name == "NEWTABLE" then -- Actual table is defined in SETLIST
  1692. if not current.aux or current.aux == 0 or regs[current.a] then
  1693. Variable({
  1694. Name = "NEWTABLE",
  1695. Special = {
  1696. Type = "Table",
  1697. Value = {}
  1698. },
  1699. ArgAux = {
  1700. Type = "Number",
  1701. Value = current.aux :: number
  1702. }
  1703. }, {
  1704. Type = "Table",
  1705. Value = {}
  1706. }, current.a)
  1707. end
  1708. elseif current.name == "DUPTABLE" then
  1709. if regs[current.a] and regs[current.a].Type == "Local" then
  1710. table.insert(instructions, {
  1711. Name = "DUPTABLE",
  1712. ArgA = regs[current.a],
  1713. ArgD = GetConstant(proto, current.d)
  1714. })
  1715. else
  1716. Variable({
  1717. Name = "DUPTABLE",
  1718. ArgD = GetConstant(proto, current.d)
  1719. }, GetConstant(proto, current.d), current.a)
  1720. end
  1721. elseif current.name == "SETLIST" then
  1722. local values: { Value } = {}
  1723. for i = 1, current.c - 1 do
  1724. table.insert(values, regs[current.b + i - 1])
  1725. end
  1726. if current.c == 0 then -- MULTRET
  1727. for i = current.b, multret_start do
  1728. table.insert(values, regs[i])
  1729. end
  1730. end
  1731. Variable({ -- target is passed to SETLIST too so realisically NEWTABLE is useless?
  1732. Name = "SETLIST",
  1733. ArgAux = {
  1734. Type = "Number",
  1735. Value = current.aux :: number
  1736. },
  1737. Special = {
  1738. Type = "Table",
  1739. Value = values
  1740. }
  1741. }, {
  1742. Type = "Table",
  1743. Value = values
  1744. }, current.a)
  1745. elseif current.name == "FORNPREP" then
  1746. CloneRegs(current.d)
  1747. local limit, step, index = regs[current.a], regs[current.a + 1], regs[current.a + 2]
  1748. local iter: Local = {
  1749. Type = "Local",
  1750. Id = "",
  1751. Latest = index,
  1752. Value = index,
  1753. Register = current.a + 2,
  1754. Scope = proto
  1755. }
  1756. table.insert(instructions, {
  1757. Name = "FORNPREP",
  1758. ArgA = regs[current.a + 3],
  1759. ArgD = {
  1760. Type = "Number",
  1761. Value = current.d
  1762. },
  1763. Special = {
  1764. index, limit, step, iter -- Arranged in the way you'd naturally write them (1, 10, 2 as index=1, limit=10, step=2)
  1765. }
  1766. })
  1767. regs[current.a + 2] = iter
  1768. table.insert(vars, regs[current.a + 2] :: Local)
  1769. elseif table.find({"FORGPREP", "FORGPREP_NEXT", "FORGPREP_INEXT"}, current.name) then
  1770. CloneRegs(current.d)
  1771. local generator, state, index = regs[current.a], regs[current.a + 1], regs[current.a + 2] -- Index is pretty much always a NIL I think
  1772. local variables: { Local } = {}
  1773. local for_end: luau_instruction
  1774. local l = 1
  1775. local offset = 0
  1776. local naux = 0
  1777. for i = idx + 1, #proto.instructions do
  1778. if proto.instructions[i].aux ~= nil then
  1779. naux += 1
  1780. end
  1781. if table.find({"FORGPREP", "FORGPREP_NEXT", "FORGPREP_INEXT"}, proto.instructions[i].name) then
  1782. l += 1
  1783. end
  1784. if proto.instructions[i].name == "FORGLOOP" then
  1785. l -= 1
  1786. for_end = proto.instructions[i]
  1787. offset = i - idx -- + naux
  1788. end
  1789. if l == 0 then
  1790. break
  1791. end
  1792. end
  1793. for i = 1, bit32.band(for_end.aux :: number, 0b11111111) do
  1794. regs[current.a + 2 + i] = {
  1795. Type = "Local",
  1796. Id = "",
  1797. Latest = index,
  1798. Value = index,
  1799. Register = current.a + 2 + i,
  1800. Scope = proto
  1801. }
  1802. table.insert(variables, regs[current.a + 2 + i] :: Local)
  1803. table.insert(vars, regs[current.a + 2 + i] :: Local)
  1804. end
  1805. table.insert(instructions, {
  1806. Name = "FORGPREP",
  1807. ArgD = {
  1808. Type = "Number",
  1809. Value = offset -- Pass a custom offset here as it is the correct one
  1810. },
  1811. Special = {
  1812. generator, -- for somereason, the LSP I use hates when this is at the end of specials?????
  1813. state, -- ^^ I assume it was tryin to cast Special into a { ValueNode } ?
  1814. index,
  1815. variables
  1816. }
  1817. })
  1818. elseif current.name == "GETVARARGS" then
  1819. multret_start = current.a
  1820. Variable({
  1821. Name = "GETVARARGS",
  1822. Special = {
  1823. Type = "VarArgs"
  1824. }
  1825. }, {
  1826. Type = "VarArgs"
  1827. }, current.a)
  1828. elseif current.name == "DUPCLOSURE" then -- I honestly have 0 idea why thethis isn't just NEWCLOSURE. DUPCLOSURE is literally NEWCLOSURE except the proto index is in the constant table ????? As if scripts have 32k protos ????
  1829. local _proto = bytecode.protos[proto.constants[current.d + 1].value :: number + 1]
  1830. local params: { Local } = {}
  1831. local val: Closure = {
  1832. Type = "Closure",
  1833. Value = {},
  1834. Params = params,
  1835. IsVarArg = _proto.isvararg,
  1836. Name = _proto.name or "",
  1837. UpValues = upvalues,
  1838. IsGlobal = false,
  1839. ParamTypes = _proto.types.data
  1840. }
  1841. Variable({
  1842. Name = "NEWCLOSURE",
  1843. ArgD = val
  1844. }, val, current.a, _proto.name ~= nil)
  1845. local upvals: { Local } = GetUpvalues()
  1846. local regs_params: { [number]: Value } = {}
  1847. for i = 1, _proto.numparams do
  1848. table.insert(params, {
  1849. Type = "Local",
  1850. Id = "",
  1851. Register = i - 1,
  1852. Value = { Type = "Nil" },
  1853. Latest = { Type = "Nil" },
  1854. Scope = proto
  1855. })
  1856. regs_params[i - 1] = params[#params]
  1857. end
  1858. val.Value = RefcountProto(_proto, upvals, regs_params)
  1859. elseif current.name == "LOADKX" then
  1860. Variable({
  1861. Name = "LOADKX",
  1862. ArgAux = GetConstant(proto, current.aux :: number)
  1863. }, GetConstant(proto, current.aux :: number), current.a)
  1864. elseif current.name == "JUMPX" then
  1865. table.insert(instructions, {
  1866. Name = "JUMPX",
  1867. ArgE = {
  1868. Type = "Number",
  1869. Value = current.e
  1870. }
  1871. })
  1872. elseif current.name == "FASTCALL" then
  1873. local following_call: luau_instruction = proto.instructions[idx + current.c]
  1874. local args: { Value } = {}
  1875. local retvals: { Local } = {}
  1876. for i = following_call.a + 1, following_call.a + following_call.b - 1 do
  1877. table.insert(args, regs[i])
  1878. end
  1879. if following_call.b == 0 then
  1880. for i = following_call.a + 1, multret_start do
  1881. table.insert(args, regs[i])
  1882. end
  1883. end
  1884. for i = 1, following_call.c - 1 do
  1885. table.insert(retvals, {
  1886. Type = "Local",
  1887. Id = "",
  1888. Register = following_call.a + i - 1,
  1889. Value = { Type = "Nil" },
  1890. Latest = { Type = "Nil" },
  1891. Scope = proto,
  1892. CreatedByCall = true
  1893. })
  1894. end
  1895. local emitted: boolean = true
  1896. if following_call.c == 0 then
  1897. multret_start = following_call.a
  1898. regs[following_call.a] = {
  1899. Type = "InlineCall",
  1900. Args = args,
  1901. Target = {
  1902. Type = "Number",
  1903. Value = current.a
  1904. }
  1905. }
  1906. table.insert(instructions, {
  1907. Name = "NOP"
  1908. })
  1909. elseif following_call.c == 2 then
  1910. emitted = Variable({
  1911. Name = "CALL",
  1912. ArgA = {
  1913. Type = "Number",
  1914. Value = current.a
  1915. },
  1916. Special = { args, retvals },
  1917. ArgC = {
  1918. Type = "Number",
  1919. Value = following_call.c
  1920. }
  1921. }, {
  1922. Type = "InlineCall",
  1923. Args = args,
  1924. Target = {
  1925. Type = "Number",
  1926. Value = current.a
  1927. }
  1928. }, following_call.a)
  1929. if emitted then -- Set the ArgA back to the function target
  1930. instructions[#instructions].ArgA = {
  1931. Type = "Number",
  1932. Value = current.a
  1933. }
  1934. end
  1935. else
  1936. table.insert(instructions, {
  1937. Name = "CALL",
  1938. ArgA = { -- NOTE: Decompiler should detect that thsi is a number and convert it to a builtin call
  1939. Type = "Number",
  1940. Value = current.a
  1941. },
  1942. Special = { args, retvals },
  1943. ArgC = {
  1944. Type = "Number",
  1945. Value = following_call.c
  1946. }
  1947. })
  1948. end
  1949. if emitted then
  1950. for i, retval in next, retvals do
  1951. regs[following_call.a + i - 1] = retval
  1952. table.insert(vars, regs[following_call.a + i - 1] :: Local)
  1953. end
  1954. end
  1955. idx += current.c -- Skip GETUPVAL/MOVE/GETIMPORT + CALL
  1956. elseif current.name == "FASTCALL1" then
  1957. local following_call: luau_instruction = proto.instructions[idx + current.c]
  1958. local args: { Value } = { regs[current.b] }
  1959. local retvals: { Local } = {}
  1960. for i = 1, following_call.c - 1 do
  1961. table.insert(retvals, {
  1962. Type = "Local",
  1963. Id = "",
  1964. Register = following_call.a + i - 1,
  1965. Value = { Type = "Nil" },
  1966. Latest = { Type = "Nil" },
  1967. Scope = proto,
  1968. CreatedByCall = true
  1969. })
  1970. end
  1971. local emitted: boolean = true
  1972. if following_call.c == 0 then
  1973. multret_start = following_call.a
  1974. regs[following_call.a] = {
  1975. Type = "InlineCall",
  1976. Args = args,
  1977. Target = {
  1978. Type = "Number",
  1979. Value = current.a
  1980. }
  1981. }
  1982. table.insert(instructions, {
  1983. Name = "NOP"
  1984. })
  1985. elseif following_call.c == 2 then
  1986. emitted = Variable({
  1987. Name = "CALL",
  1988. ArgA = {
  1989. Type = "Number",
  1990. Value = current.a
  1991. },
  1992. Special = { args, retvals },
  1993. ArgC = {
  1994. Type = "Number",
  1995. Value = following_call.c
  1996. }
  1997. }, {
  1998. Type = "InlineCall",
  1999. Args = args,
  2000. Target = {
  2001. Type = "Number",
  2002. Value = current.a
  2003. }
  2004. }, following_call.a)
  2005. if emitted then -- Set the ArgA back to the function target
  2006. instructions[#instructions].ArgA = {
  2007. Type = "Number",
  2008. Value = current.a
  2009. }
  2010. end
  2011. else
  2012. table.insert(instructions, {
  2013. Name = "CALL",
  2014. ArgA = { -- NOTE: Decompiler should detect that thsi is a number and convert it to a builtin call
  2015. Type = "Number",
  2016. Value = current.a
  2017. },
  2018. Special = {args, retvals},
  2019. ArgC = {
  2020. Type = "Number",
  2021. Value = following_call.c
  2022. }
  2023. })
  2024. end
  2025. if emitted then
  2026. for i, retval in next, retvals do
  2027. regs[following_call.a + i - 1] = retval
  2028. table.insert(vars, regs[following_call.a + i - 1] :: Local)
  2029. end
  2030. end
  2031. idx += current.c -- Skip GETUPVAL/MOVE/GETIMPORT + CALL
  2032. elseif current.name == "FASTCALL2" then
  2033. local following_call: luau_instruction = proto.instructions[idx + current.c]
  2034. local retvals: { Local } = {}
  2035. for i = 1, following_call.c - 1 do
  2036. table.insert(retvals, {
  2037. Type = "Local",
  2038. Id = "",
  2039. Register = following_call.a + i - 1,
  2040. Value = { Type = "Nil" },
  2041. Latest = { Type = "Nil" },
  2042. Scope = proto,
  2043. CreatedByCall = true
  2044. })
  2045. end
  2046. local emitted: boolean = true
  2047. if following_call.c == 0 then
  2048. multret_start = following_call.a
  2049. regs[following_call.a] = {
  2050. Type = "InlineCall",
  2051. Args = {regs[current.b], regs[current.aux :: number]},
  2052. Target = {
  2053. Type = "Number",
  2054. Value = current.a
  2055. }
  2056. }
  2057. table.insert(instructions, {
  2058. Name = "NOP"
  2059. })
  2060. elseif following_call.c == 2 then
  2061. emitted = Variable({
  2062. Name = "CALL",
  2063. ArgA = {
  2064. Type = "Number",
  2065. Value = current.a
  2066. },
  2067. Special = { {regs[current.b], regs[current.aux :: number]}, retvals },
  2068. ArgC = {
  2069. Type = "Number",
  2070. Value = following_call.c
  2071. }
  2072. }, {
  2073. Type = "InlineCall",
  2074. Args = {regs[current.b], regs[current.aux :: number]},
  2075. Target = {
  2076. Type = "Number",
  2077. Value = current.a
  2078. }
  2079. }, following_call.a)
  2080. if emitted then -- Set the ArgA back to the function target
  2081. instructions[#instructions].ArgA = {
  2082. Type = "Number",
  2083. Value = current.a
  2084. }
  2085. end
  2086. else
  2087. table.insert(instructions, {
  2088. Name = "CALL",
  2089. ArgA = { -- NOTE: Decompiler should detect that thsi is a number and convert it to a builtin call
  2090. Type = "Number",
  2091. Value = current.a
  2092. },
  2093. Special = {{regs[current.b], regs[current.aux :: number]}, retvals},
  2094. ArgC = {
  2095. Type = "Number",
  2096. Value = following_call.c
  2097. }
  2098. })
  2099. end
  2100. if emitted then
  2101. for i, retval in next, retvals do
  2102. regs[following_call.a + i - 1] = retval
  2103. table.insert(vars, regs[following_call.a + i - 1] :: Local)
  2104. end
  2105. end
  2106. idx += current.c -- Skip GETUPVAL/MOVE/GETIMPORT + CALL
  2107. elseif current.name == "FASTCALL2K" then
  2108. local following_call: luau_instruction = proto.instructions[idx + current.c - 1]
  2109. local retvals: { Local } = {}
  2110. for i = 1, following_call.c - 1 do
  2111. table.insert(retvals, {
  2112. Type = "Local",
  2113. Id = "",
  2114. Register = following_call.a + i - 1,
  2115. Value = { Type = "Nil" },
  2116. Latest = { Type = "Nil" },
  2117. Scope = proto,
  2118. CreatedByCall = true
  2119. })
  2120. end
  2121. local emitted: boolean = true
  2122. if following_call.c == 0 then
  2123. multret_start = following_call.a
  2124. regs[following_call.a] = {
  2125. Type = "InlineCall",
  2126. Args = {regs[current.b], GetConstant(proto, current.aux :: number)},
  2127. Target = {
  2128. Type = "Number",
  2129. Value = current.a
  2130. }
  2131. }
  2132. table.insert(instructions, {
  2133. Name = "NOP"
  2134. })
  2135. elseif following_call.c == 2 then
  2136. emitted = Variable({
  2137. Name = "CALL",
  2138. ArgA = {
  2139. Type = "Number",
  2140. Value = current.a
  2141. },
  2142. Special = { {regs[current.b], regs[current.aux :: number]}, retvals },
  2143. ArgC = {
  2144. Type = "Number",
  2145. Value = following_call.c
  2146. }
  2147. }, {
  2148. Type = "InlineCall",
  2149. Args = {regs[current.b], regs[current.aux :: number]},
  2150. Target = {
  2151. Type = "Number",
  2152. Value = current.a
  2153. }
  2154. }, following_call.a)
  2155. if emitted then -- Set the ArgA back to the function target
  2156. instructions[#instructions].ArgA = {
  2157. Type = "Number",
  2158. Value = current.a
  2159. }
  2160. end
  2161. else
  2162. table.insert(instructions, {
  2163. Name = "CALL",
  2164. ArgA = { -- NOTE: Decompiler should detect that thsi is a number and convert it to a builtin call
  2165. Type = "Number",
  2166. Value = current.a
  2167. },
  2168. Special = {{regs[current.b], GetConstant(proto, current.aux :: number)}, retvals},
  2169. ArgC = {
  2170. Type = "Number",
  2171. Value = following_call.c
  2172. }
  2173. })
  2174. end
  2175. if emitted then
  2176. for i, retval in next, retvals do
  2177. regs[following_call.a + i - 1] = retval
  2178. table.insert(vars, regs[following_call.a + i - 1] :: Local)
  2179. end
  2180. end
  2181. idx += current.c - 1 -- Not sure why we subtract one here, the docs don't mention it
  2182. elseif table.find({"JUMPXEQKNIL", "JUMPXEQKB"}, current.name) then
  2183. table.insert(instructions, {
  2184. Name = current.name,
  2185. ArgA = regs[current.a],
  2186. ArgD = {
  2187. Type = "Number",
  2188. Value = current.d
  2189. },
  2190. Special = {
  2191. Type = "Expression",
  2192. Lhs = regs[current.a],
  2193. Op = bit32.band(current.aux :: number, 2147483648) and "~=" or "==",
  2194. Rhs = current.name == "JUMPXEQKNIL" and { Type = "Nil" } :: Nil or {
  2195. Type = "Boolean",
  2196. Value = bit32.band(current.aux :: number, 1) ~= 0
  2197. } :: Boolean
  2198. } :: Expression
  2199. })
  2200. elseif table.find({"JUMPXEQKN", "JUMPXEQKS"}, current.name) then
  2201. table.insert(instructions, {
  2202. Name = current.name,
  2203. ArgD = {
  2204. Type = "Number",
  2205. Value = current.d
  2206. },
  2207. Special = {
  2208. Type = "Expression",
  2209. Lhs = regs[current.a],
  2210. Op = bit32.band(current.aux :: number, 2147483648) and "~=" or "==",
  2211. Rhs = GetConstant(proto, bit32.band(current.aux :: number, 16777215))
  2212. } :: Expression
  2213. })
  2214. elseif current.name == "IDIV" then
  2215. Variable({
  2216. Name = "IDIV",
  2217. Special = {
  2218. Type = "Expression",
  2219. Op = "//",
  2220. Lhs = regs[current.b],
  2221. Rhs = regs[current.c]
  2222. }
  2223. }, {
  2224. Type = "Expression",
  2225. Op = "//",
  2226. Lhs = regs[current.b],
  2227. Rhs = regs[current.c]
  2228. }, current.a)
  2229. elseif current.name == "IDIVK" then
  2230. Variable({
  2231. Name = "IDIVK",
  2232. Special = {
  2233. Type = "Expression",
  2234. Op = "//",
  2235. Lhs = regs[current.b],
  2236. Rhs = GetConstant(proto, current.c)
  2237. }
  2238. }, {
  2239. Type = "Expression",
  2240. Op = "//",
  2241. Lhs = regs[current.b],
  2242. Rhs = GetConstant(proto, current.c)
  2243. }, current.a)
  2244. else -- Here will mostly be FORNLOOP, FORGLOOP, and VARARG stuff. None of which we can recover any meaningful data from
  2245. table.insert(instructions, {
  2246. Name = "NOP"
  2247. })
  2248. end
  2249. if current.aux ~= nil then -- JUMP offsets don't account for AUX instructions. We just add 'em so the JUMPs become instruction offsets instead of byte offsets
  2250. table.insert(instructions, {
  2251. Name = "NOP"
  2252. })
  2253. end
  2254. idx += 1
  2255. end
  2256. return instructions
  2257. end
  2258.  
  2259. local function DecompileClosure(closure: Closure): ClosureNode
  2260. local idx = 1
  2261.  
  2262. local node: ClosureNode = {
  2263. Type = "ClosureNode",
  2264. Name = closure.Name,
  2265. Params = closure.Params,
  2266. IsVarArg = closure.IsVarArg,
  2267. Body = {},
  2268. ParamTypes = closure.ParamTypes
  2269. }
  2270.  
  2271. local defined_locals: { Local } = {}
  2272.  
  2273. local function DecompileValue(value: Value?): ValueNode
  2274. if not value then
  2275. return { Type = "NilNode" } -- Should never happen, this is mostly to satisfy the static analyzer
  2276. end
  2277. if value.Type == "Boolean" then
  2278. return {
  2279. Type = "BooleanNode",
  2280. Value = value.Value
  2281. }
  2282. elseif value.Type == "Number" then
  2283. return {
  2284. Type = "NumberNode",
  2285. Value = value.Value
  2286. }
  2287. elseif value.Type == "Nil" then
  2288. return {
  2289. Type = "NilNode"
  2290. }
  2291. elseif value.Type == "Expression" then
  2292. return {
  2293. Type = "ExpressionNode",
  2294. Lhs = DecompileValue(value.Lhs),
  2295. Rhs = DecompileValue(value.Rhs),
  2296. Op = value.Op
  2297. }
  2298. elseif value.Type == "Unary" then
  2299. return {
  2300. Type = "UnaryNode",
  2301. Value = DecompileValue(value.Value),
  2302. Op = value.Op
  2303. }
  2304. elseif value.Type == "Local" then
  2305. return value -- Here we just pass the local as a pseudo-node because I am lazy and it doesn't matter
  2306. elseif value.Type == "String" then
  2307. return {
  2308. Type = "StringNode",
  2309. Value = value.Value
  2310. }
  2311. elseif value.Type == "Strings" then
  2312. local values: { ValueNode } = {}
  2313. for _, value in value.Value do
  2314. table.insert(values, DecompileValue(value))
  2315. end
  2316. return {
  2317. Type = "StringsNode",
  2318. Value = values
  2319. }
  2320. elseif value.Type == "Global" then
  2321. return {
  2322. Type = "GlobalNode",
  2323. Value = value.Value
  2324. }
  2325. elseif value.Type == "Closure" then
  2326. return DecompileClosure(value)
  2327. elseif value.Type == "TableIndex" then
  2328. return {
  2329. Type = "TableIndexNode",
  2330. Table = DecompileValue(value.Table),
  2331. Index = DecompileValue(value.Index)
  2332. }
  2333. elseif value.Type == "Table" then
  2334. local values: { ValueNode } = {}
  2335. for _, v in next, value.Value do
  2336. table.insert(values, DecompileValue(v))
  2337. end
  2338. return {
  2339. Type = "TableNode",
  2340. Body = values
  2341. }
  2342. elseif value.Type == "VarArgs" then
  2343. return {
  2344. Type = "VarArgsNode"
  2345. }
  2346. elseif value.Type == "InlineNamecall" then
  2347. local args: { ValueNode } = {}
  2348. for _, arg in next, value.Args do
  2349. table.insert(args, DecompileValue(arg))
  2350. end
  2351. return {
  2352. Type = "InlineNamecallNode",
  2353. Args = args,
  2354. Object = DecompileValue(value.Object),
  2355. Target = DecompileValue(value.Target) :: StringNode
  2356. }
  2357. elseif value.Type == "InlineCall" then
  2358. local args: { ValueNode } = {}
  2359. for _, arg in next, value.Args do
  2360. table.insert(args, DecompileValue(arg))
  2361. end
  2362. local target: ValueNode = DecompileValue(value.Target)
  2363. if value.Target.Type == "Number" then
  2364. target = ConvertBuiltin(value.Target.Value)
  2365. end
  2366. return {
  2367. Type = "InlineCallNode",
  2368. Args = args,
  2369. Target = target
  2370. }
  2371. end
  2372. return {
  2373. Type = "NilNode" -- TODO: add remainding nodes.
  2374. }
  2375. end
  2376.  
  2377. local function Define(_local: Local, value: ValueNode): VarAssignNode | VarReassignNode
  2378. if table.find(defined_locals, _local) then
  2379. return {
  2380. Type = "VarReassignNode",
  2381. Target = _local,
  2382. Value = value
  2383. }
  2384. end
  2385. table.insert(defined_locals, _local)
  2386. return {
  2387. Type = "VarAssignNode",
  2388. Target = _local,
  2389. Value = value
  2390. }
  2391. end
  2392.  
  2393. local function BreaksScope(instructions: number): boolean -- Checks if a JUMP* instruction would continue the scope of a loop
  2394. return false -- table.find({"JUMPBACK", "FORNLOOP", "FORGLOOP"}, closure.Value[idx + instructions - 2].Name) ~= nil
  2395. end
  2396.  
  2397. local function ContinuesScope(instructions: number): boolean -- Checks if a JUMP* instruction would continue the scope of a loop
  2398. return false -- table.find({"JUMPBACK", "FORNLOOP", "FORGLOOP"}, closure.Value[idx + instructions - 3].Name) ~= nil
  2399. end
  2400.  
  2401. local function DecompileInstruction(instruction: Instruction): Node?
  2402. if instruction.Name == "LOADNIL" then
  2403. return (Define(instruction.ArgA :: Local, DecompileValue(instruction.ArgB)))
  2404. elseif instruction.Name == "LOADN" then
  2405. return (Define(instruction.ArgA :: Local, DecompileValue(instruction.ArgB)))
  2406. elseif instruction.Name == "LOADB" then
  2407. return (Define(instruction.ArgA :: Local, DecompileValue(instruction.ArgB)))
  2408. elseif instruction.Name == "LOADK" then
  2409. return (Define(instruction.ArgA :: Local, DecompileValue(instruction.ArgB)))
  2410. elseif instruction.Name == "MOVE" then
  2411. return (Define(instruction.ArgA :: Local, DecompileValue(instruction.ArgB)))
  2412. elseif instruction.Name == "GETGLOBAL" then
  2413. return (Define(instruction.ArgA :: Local, DecompileValue(instruction.ArgAux)))
  2414. elseif instruction.Name == "SETGLOBAL" then
  2415. return ({
  2416. Type = "GlobalDefinitionNode",
  2417. Target = (instruction.ArgAux :: String).Value,
  2418. Value = DecompileValue(instruction.ArgA)
  2419. })
  2420. elseif instruction.Name == "GETUPVAL" then
  2421. return (Define(instruction.ArgA :: Local, DecompileValue(instruction.ArgB)))
  2422. elseif instruction.Name == "SETUPVAL" then
  2423. return ({
  2424. Type = "VarReassignNode",
  2425. Target = instruction.ArgB :: Local,
  2426. Value = DecompileValue(instruction.ArgA)
  2427. })
  2428. elseif instruction.Name == "NEWCLOSURE" then
  2429. if (instruction.ArgD :: Closure).IsGlobal then
  2430. return ({
  2431. Type = "GlobalDefinitionNode",
  2432. Target = "",
  2433. Value = DecompileValue(instruction.ArgD)
  2434. })
  2435. end
  2436. return (Define(instruction.ArgA :: Local, DecompileValue(instruction.ArgD)))
  2437. elseif instruction.Name == "CALL" then
  2438. local args: { ValueNode } = {}
  2439. for _, arg in next, instruction.Special[1] do
  2440. table.insert(args, DecompileValue(arg))
  2441. end
  2442. for _, var in next, instruction.Special[2] :: { Local } do
  2443. table.insert(defined_locals, var)
  2444. end
  2445. local target: ValueNode = DecompileValue(instruction.ArgA)
  2446. if (instruction.ArgA :: Value).Type == "Number" then
  2447. target = ConvertBuiltin((instruction.ArgA :: Number).Value)
  2448. end
  2449. return ({
  2450. Type = "FunctionCallNode",
  2451. Args = args,
  2452. RetVals = instruction.Special[2],
  2453. Target = target
  2454. })
  2455. elseif table.find({"ADD", "SUB", "MUL", "DIV", "MOD", "POW", "ADDK", "SUBK", "MULK", "DIVK", "MODK", "POWK", "IDIV", "IDIVK", "AND", "OR", "ANDK", "ORK", "SUBRK", "DIVRK"}, instruction.Name) then
  2456. return (Define(instruction.ArgA :: Local, DecompileValue(instruction.Special)))
  2457. elseif instruction.Name == "RETURN" then
  2458. local values: { ValueNode } = {}
  2459. for _, value in next, instruction.Special do
  2460. table.insert(values, DecompileValue(value))
  2461. end
  2462. return ({
  2463. Type = "ReturnNode",
  2464. Values = values
  2465. })
  2466. elseif instruction.Name == "GETTABLE" then
  2467. return (Define(instruction.ArgA :: Local, DecompileValue(instruction.Special)))
  2468. elseif instruction.Name == "SETTABLE" then
  2469. return ({
  2470. Type = "TableAssignNode",
  2471. Table = DecompileValue(instruction.ArgB),
  2472. Index = DecompileValue(instruction.ArgC),
  2473. Source = DecompileValue(instruction.ArgA),
  2474. })
  2475. elseif instruction.Name == "NAMECALL" then
  2476. local args: { ValueNode } = {}
  2477. for _, arg in next, instruction.Special[1] do
  2478. table.insert(args, DecompileValue(arg))
  2479. end
  2480. for _, var in next, instruction.Special[2] :: { Local } do
  2481. table.insert(defined_locals, var)
  2482. end
  2483. return ({
  2484. Type = "NamecallNode",
  2485. Args = args,
  2486. RetVals = instruction.Special[2],
  2487. Object = DecompileValue(instruction.ArgB),
  2488. Target = DecompileValue(instruction.ArgAux) :: StringNode
  2489. })
  2490. elseif instruction.Name == "JUMP" then
  2491. if BreaksScope((instruction.ArgD :: Number).Value) then
  2492. return ({
  2493. Type = "BreakNode"
  2494. })
  2495. elseif ContinuesScope((instruction.ArgD :: Number).Value) then
  2496. return ({
  2497. Type = "ContinueNode"
  2498. })
  2499. end
  2500. elseif table.find({"JUMPIF", "JUMPIFNOT", "JUMPIFEQ", "JUMPIFLE", "JUMPIFLT", "JUMPIFNOTEQ", "JUMPIFNOTLE", "JUMPIFNOTLT", "JUMPXEQKNIL", "JUMPXEQKB", "JUMPXEQKN", "JUMPXEQKS"}, instruction.Name) then
  2501. local opposites = {
  2502. JUMPIFEQ = "~=",
  2503. JUMPIFNOTEQ = "==",
  2504. JUMPIFLT = ">",
  2505. JUMPIFLE = ">=",
  2506. JUMPIFNOTLE = "<=",
  2507. JUMPIFNOTLT = "<",
  2508. }
  2509. local operators = {
  2510. JUMPIFEQ = "==",
  2511. JUMPIFNOTEQ = "~=",
  2512. JUMPIFLT = "<",
  2513. JUMPIFLE = "<=",
  2514. JUMPIFNOTLE = ">=",
  2515. JUMPIFNOTLT = ">",
  2516. }
  2517. local flipped = {
  2518. ["=="] = "~=",
  2519. ["<"] = ">",
  2520. ["<="] = ">=",
  2521. [">"] = "<",
  2522. [">="] = "<=",
  2523. }
  2524.  
  2525. local function GetSingularCondition(_instr: Instruction, flip: boolean): ValueNode
  2526. if _instr.Name == "JUMPIF" then
  2527. local cond = DecompileValue(_instr.ArgA)
  2528. if flip then
  2529. cond = {
  2530. Type = "UnaryNode",
  2531. Value = cond,
  2532. Op = "not "
  2533. }
  2534. end
  2535. return cond
  2536. elseif _instr.Name == "JUMPIFNOT" then
  2537. local cond = DecompileValue(_instr.ArgA)
  2538. if not flip then
  2539. cond = {
  2540. Type = "UnaryNode",
  2541. Value = cond,
  2542. Op = "not "
  2543. }
  2544. end
  2545. return cond
  2546. elseif table.find({"JUMPIFEQ", "JUMPIFLE", "JUMPIFLT", "JUMPIFNOTEQ", "JUMPIFNOTLE", "JUMPIFNOTLT"}, _instr.Name) then
  2547. return {
  2548. Type = "ExpressionNode",
  2549. Lhs = DecompileValue(_instr.ArgA),
  2550. Op = (flip and opposites or operators)[_instr.Name],
  2551. Rhs = DecompileValue(_instr.ArgAux)
  2552. }
  2553. else
  2554. return DecompileValue(_instr.Special)
  2555. end
  2556. end
  2557.  
  2558. local condition: ValueNode = GetSingularCondition(instruction, true)
  2559. local body: { Node } = {}
  2560. local elsebody: { Node } = {}
  2561.  
  2562. local body_idx: number = idx
  2563. local body_end_idx: number = idx + (instruction.ArgD :: Number).Value
  2564.  
  2565. local is_while: boolean = closure.Value[body_end_idx] and closure.Value[body_end_idx].Name == "JUMPBACK" and body_end_idx + (closure.Value[body_end_idx].ArgD :: Number).Value <= idx
  2566.  
  2567. if BreaksScope(body_end_idx) then
  2568. table.insert(body, {
  2569. Type = "BreakNode"
  2570. })
  2571. elseif ContinuesScope(body_end_idx) then
  2572. table.insert(body, {
  2573. Type = "ContinueNode"
  2574. })
  2575. else
  2576. idx = body_idx + 1
  2577. local did_jump = false
  2578. while idx < body_end_idx + 1 do
  2579. if closure.Value[idx] and closure.Value[idx].Name == "JUMP" and idx == body_end_idx then
  2580. did_jump = true
  2581. local _end_idx = idx + (closure.Value[idx].ArgD :: Number).Value
  2582. while idx < _end_idx do
  2583. idx += 1
  2584. local _node: Node? = DecompileInstruction(closure.Value[idx])
  2585. if _node then
  2586. table.insert(elsebody, _node)
  2587. end
  2588. end
  2589. break
  2590. end
  2591. local _node: Node? = closure.Value[idx] and DecompileInstruction(closure.Value[idx]) or nil
  2592. if _node then
  2593. table.insert(body, _node)
  2594. end
  2595. idx += 1
  2596. end
  2597. if not did_jump then
  2598. idx -= 1
  2599. end
  2600. end
  2601. if is_while then
  2602. return ({
  2603. Type = "WhileNode",
  2604. Condition = condition,
  2605. Body = body
  2606. })
  2607. end
  2608. return ({
  2609. Type = "IfNode",
  2610. Body = body,
  2611. ElseBody = elsebody,
  2612. Condition = condition
  2613. })
  2614. elseif table.find({"NOT", "MINUS", "LENGTH"}, instruction.Name) then
  2615. return (Define(instruction.ArgA :: Local, DecompileValue(instruction.Special)))
  2616. elseif instruction.Name == "CONCAT" then
  2617. return (Define(instruction.ArgA :: Local, DecompileValue(instruction.Special)))
  2618. elseif instruction.Name == "NEWTABLE" then
  2619. return (Define(instruction.ArgA :: Local, {
  2620. Type = "TableNode",
  2621. Body = {}
  2622. }))
  2623. elseif instruction.Name == "SETLIST" then
  2624. local values: { ValueNode } = {}
  2625. for _, value in next, instruction.Special.Value do
  2626. table.insert(values, DecompileValue(value))
  2627. end
  2628. return (Define(instruction.ArgA :: Local, {
  2629. Type = "TableNode",
  2630. Body = values
  2631. }))
  2632. elseif instruction.Name == "DUPTABLE" then
  2633. return (Define(instruction.ArgA :: Local, {
  2634. Type = "TableNode",
  2635. Body = {}
  2636. }))
  2637. elseif instruction.Name == "FORNPREP" then
  2638. local body: { Node } = {}
  2639. local end_idx = idx + (instruction.ArgD :: Number).Value
  2640. while idx < end_idx do
  2641. idx += 1
  2642. local node: Node? = DecompileInstruction(closure.Value[idx])
  2643. if node then
  2644. table.insert(body, node)
  2645. end
  2646. end
  2647. return ({
  2648. Type = "ForRangeNode",
  2649. Index = DecompileValue(instruction.Special[1]),
  2650. Limit = DecompileValue(instruction.Special[2]),
  2651. Step = DecompileValue(instruction.Special[3]),
  2652. Iterator = instruction.Special[4],
  2653. Body = body
  2654. })
  2655. elseif instruction.Name == "FORGPREP" then
  2656. local body: { Node } = {}
  2657. local end_idx = idx + (instruction.ArgD :: Number).Value
  2658. while idx <= end_idx do
  2659. idx += 1
  2660. local node: Node? = DecompileInstruction(closure.Value[idx])
  2661. if node then
  2662. table.insert(body, node)
  2663. end
  2664. end
  2665. return ({
  2666. Type = "ForValueNode",
  2667. Generator = DecompileValue(instruction.Special[2]),
  2668. State = DecompileValue(instruction.Special[3]),
  2669. Index = DecompileValue(instruction.Special[4]),
  2670. Variables = instruction.Special[1],
  2671. Body = body
  2672. })
  2673. elseif instruction.Name == "GETVARARGS" then
  2674. return (Define(instruction.ArgA :: Local, {Type = "VarArgsNode"}))
  2675. elseif instruction.Name == "LOADKX" then
  2676. return (Define(instruction.ArgA :: Local, DecompileValue(instruction.ArgAux)))
  2677. elseif instruction.Name == "JUMPX" then
  2678. if BreaksScope((instruction.ArgD :: Number).Value) then
  2679. return ({
  2680. Type = "BreakNode"
  2681. })
  2682. elseif ContinuesScope((instruction.ArgD :: Number).Value) then
  2683. return ({
  2684. Type = "ContinueNode"
  2685. })
  2686. end
  2687. elseif instruction.Name == "GETIMPORT" then
  2688. return (Define(instruction.ArgA :: Local, DecompileValue(instruction.Special)))
  2689. elseif instruction.Name == "NOP" or instruction.Name == "JUMPBACK" then
  2690. else
  2691. print(fmt("UNHANDLED: %s", instruction.Name))
  2692. end
  2693. return
  2694. end
  2695.  
  2696. while idx < #closure.Value + 1 do
  2697. local instruction: Instruction = closure.Value[idx]
  2698. local _node: Node? = DecompileInstruction(instruction)
  2699. if _node then
  2700. table.insert(node.Body, _node)
  2701. end
  2702. idx += 1
  2703. end
  2704.  
  2705. return node
  2706. end
  2707.  
  2708. local function ResolveType(_type: number): string
  2709. local optional = bit32.band(_type, bit32.lshift(1, 7))
  2710. local types = {
  2711. [0] = "nil",
  2712. [1] = "boolean",
  2713. [2] = "number",
  2714. [3] = "string",
  2715. [4] = "{}",
  2716. [5] = "__wdec_type_function",
  2717. [6] = "__wdec_type_thread",
  2718. [7] = "__wdec_type_userdata",
  2719. [8] = "Vector3",
  2720.  
  2721. [15] = "any",
  2722. [256] = "__wdec_type_invalid"
  2723. }
  2724. return types[bit32.band(_type, 0b1111111)] .. optional
  2725. end
  2726.  
  2727. local cid = 0
  2728. local pid = 0
  2729. local function TranspileClosure(closure: ClosureNode, depth: number): string
  2730. local transpiled_closure_src: string = ""
  2731.  
  2732. local function Indent(depth): string
  2733. return string.rep(" ", depth)
  2734. end
  2735.  
  2736. local function TranspileValue(value: ValueNode, depth: number, parent_expr: ExpressionNode?): string
  2737. if value.Type == "BooleanNode" then
  2738. return value.Value and "true" or "false"
  2739. elseif value.Type == "ClosureNode" then -- If a closure is used as a value, it does NOT have a name
  2740. local header: string = "function("
  2741. for i, v in next, value.Params do
  2742. header ..= fmt("p%d", pid)
  2743. if #value.ParamTypes > 2 then
  2744. header ..= ": " .. ResolveType(value.ParamTypes[i + 2])
  2745. end
  2746. if i < #value.Params then
  2747. header ..= ", "
  2748. end
  2749. v.Id = fmt("p%d", pid)
  2750. pid += 1
  2751. end
  2752. if value.IsVarArg then
  2753. if #value.Params > 0 then
  2754. header ..= ", "
  2755. end
  2756. header ..= "..."
  2757. end
  2758. header ..= ")\n"
  2759. return header .. TranspileClosure(value, depth + 1) .. Indent(depth) .. "end"
  2760. elseif value.Type == "ExpressionNode" then
  2761. local operator_precedence = { -- TODO: Find out if these actually are correct (I think they are)
  2762. ["or"] = 0,
  2763. ["and"] = 1,
  2764. ["=="] = 2,
  2765. ["~="] = 2,
  2766. ["<"] = 2,
  2767. [">"] = 2,
  2768. ["<="] = 2,
  2769. [">="] = 2,
  2770. ["+"] = 3,
  2771. ["-"] = 3,
  2772. ["*"] = 4,
  2773. ["/"] = 4,
  2774. ["//"] = 4,
  2775. ["%"] = 4,
  2776. ["^"] = 5,
  2777. }
  2778. local format: string = ""
  2779. if parent_expr and operator_precedence[value.Op] < operator_precedence[parent_expr.Op] then
  2780. format ..= "(%s"
  2781. else
  2782. format ..= "%s"
  2783. end
  2784. format ..= " %s "
  2785. if parent_expr and operator_precedence[value.Op] < operator_precedence[parent_expr.Op] then
  2786. format ..= "%s)"
  2787. else
  2788. format ..= "%s"
  2789. end
  2790. return fmt(format, TranspileValue(value.Lhs, depth, value), value.Op, TranspileValue(value.Rhs, depth, value))
  2791. elseif value.Type == "Local" then
  2792. return value.Id
  2793. elseif value.Type == "NilNode" then
  2794. return "nil"
  2795. elseif value.Type == "NumberNode" then
  2796. return tostring(value.Value)
  2797. elseif value.Type == "StringNode" then -- Raw strings are not supported.
  2798. local s, _ = string.gsub(fmt("\"%s\"", value.Value), "\n", "\\n")
  2799. local newstring, _ = string.gsub(s, "\t", "\\t")
  2800. return newstring
  2801. elseif value.Type == "GlobalNode" then
  2802. return value.Value
  2803. elseif value.Type == "UnaryNode" then
  2804. return fmt("%s%s", value.Op, TranspileValue(value.Value, depth))
  2805. elseif value.Type == "VarArgsNode" then
  2806. return "..."
  2807. elseif value.Type == "TableIndexNode" then
  2808. if value.Index.Type == "StringNode" and not string.find(value.Index.Value, " ") then
  2809. return fmt("%s.%s", TranspileValue(value.Table, depth), value.Index.Value)
  2810. end
  2811. return fmt("%s[%s]", TranspileValue(value.Table, depth), TranspileValue(value.Index, depth))
  2812. elseif value.Type == "StringsNode" then
  2813. local cons: string = ""
  2814. for i, _value in next, value.Value do
  2815. cons ..= TranspileValue(_value, depth)
  2816. if i < #value.Value then
  2817. cons ..= " .. "
  2818. end
  2819. end
  2820. return cons
  2821. elseif value.Type == "TableNode" then -- TODO: Format tables nicer
  2822. local cons: string = ""
  2823. local values: { string } = {}
  2824. local total_length: number = 0
  2825. for i, _value in next, value.Body do
  2826. table.insert(values, TranspileValue(_value, depth))
  2827. total_length += string.len(values[#values])
  2828. end
  2829. for i, _value in next, value.Body do
  2830. cons ..= TranspileValue(_value, depth)
  2831. if i < #value.Body then
  2832. cons ..= fmt(",%s", total_length <= 120 and " " or "\n" .. Indent(depth + 1))
  2833. end
  2834. end
  2835. return fmt("{%s%s%s}", total_length <= 120 and "" or "\n" .. Indent(depth + 1), cons, total_length <= 120 and "" or "\n" .. Indent(depth))
  2836. elseif value.Type == "InlineNamecallNode" then
  2837. local cons: string = TranspileValue(value.Object, depth) .. ":" .. value.Target.Value .. "("
  2838. for i, arg in next, value.Args do
  2839. cons ..= TranspileValue(arg, depth)
  2840. if i < #value.Args then
  2841. cons ..= ", "
  2842. end
  2843. end
  2844. return cons .. ")"
  2845. elseif value.Type == "InlineCallNode" then
  2846. local cons: string = TranspileValue(value.Target, depth) .. "("
  2847. for i, arg in next, value.Args do
  2848. cons ..= TranspileValue(arg, depth)
  2849. if i < #value.Args then
  2850. cons ..= ", "
  2851. end
  2852. end
  2853. print('INLI AAAA')
  2854. return cons .. ")"
  2855. end
  2856. return fmt("nil --[[ Unhandled type: %s ]]", value.Type)
  2857. end
  2858.  
  2859. local function TranspileNode(node: Node, depth: number): string
  2860. if node.Type == "VarAssignNode" then
  2861. local id = fmt("v%d", cid)
  2862.  
  2863. if node.Value.Type == "TableIndexNode" then
  2864. local final: Node = node.Value
  2865. while final.Type == "TableIndexNode" do
  2866. final = node.Value.Index
  2867. end
  2868. if final.Type == "StringNode" and not final.Value:find(" ") then
  2869. id ..= "_" .. final.Value
  2870. end
  2871. end
  2872.  
  2873. node.Target.Id = id
  2874. cid += 1
  2875. if node.Value.Type == "ClosureNode" and node.Value.Name ~= "" then -- Special case for functions
  2876. node.Target.Id = node.Value.Name
  2877. local cons: string = Indent(depth) .. fmt("local function %s(", node.Value.Name)
  2878. for i, v in next, node.Value.Params do
  2879. cons ..= fmt("p%d", pid)
  2880. if #node.Value.ParamTypes > 2 then
  2881. cons ..= ": " .. ResolveType(node.Value.ParamTypes[i + 2])
  2882. end
  2883. if i < #node.Value.Params then
  2884. cons ..= ", "
  2885. end
  2886. v.Id = fmt("p%d", pid)
  2887. pid += 1
  2888. end
  2889. if node.Value.IsVarArg then
  2890. if #node.Value.Params > 0 then
  2891. cons ..= ", "
  2892. end
  2893. cons ..= "..."
  2894. end
  2895. cons ..= ")\n"
  2896. return cons .. TranspileClosure(node.Value, depth + 1) .. Indent(depth) .. "end"
  2897. end
  2898. return Indent(depth) .. fmt("local %s = %s", id, TranspileValue(node.Value, depth))
  2899. elseif node.Type == "VarReassignNode" then
  2900. if node.Value.Type == "ClosureNode" and node.Value.Name ~= "" then -- Special case for functions
  2901. node.Target.Id = node.Value.Name
  2902. local cons: string = Indent(depth) .. fmt("local function %s(", node.Value.Name)
  2903. for i, v in next, node.Value.Params do
  2904. cons ..= fmt("p%d", pid)
  2905. if #node.Value.ParamTypes > 2 then
  2906. cons ..= ": " .. ResolveType(node.Value.ParamTypes[i + 2])
  2907. end
  2908. if i < #node.Value.Params then
  2909. cons ..= ", "
  2910. end
  2911. v.Id = fmt("p%d", pid)
  2912. pid += 1
  2913. end
  2914. if node.Value.IsVarArg then
  2915. if #node.Value.Params > 0 then
  2916. cons ..= ", "
  2917. end
  2918. cons ..= "..."
  2919. end
  2920. cons ..= ")\n"
  2921. return cons .. TranspileClosure(node.Value, depth + 1) .. Indent(depth) .. "end"
  2922. end
  2923. return Indent(depth) .. fmt("%s = %s", node.Target.Id, TranspileValue(node.Value, depth))
  2924. elseif node.Type == "FunctionCallNode" then
  2925. local cons: string = ""
  2926. if node.Target.Type == "ClosureNode" then
  2927. cons = "("
  2928. end
  2929. print('INLI AAAA btw at the functi0n call l2892')
  2930. print(node)
  2931. local target = TranspileValue(node.Target, depth)
  2932. if #node.RetVals > 0 then
  2933. cons ..= "local "
  2934. for i, val in next, node.RetVals do
  2935. if target == "require" and #node.RetVals == 1 then
  2936. -- here we want to get the name of the module, which could be something like script.Parent.SomeModule or script.Parent:WaitForChild("SomeModule")
  2937. local module: string = ""
  2938. if #node.Args == 1 and node.Args[1].Type == "InlineNamecallNode" then
  2939. if (((node.Args[1] :: InlineNamecallNode).Target).Value == "FindFirstChild" or ((node.Args[1] :: InlineNamecallNode).Target).Value == "FindFirstChildOfClass" or ((node.Args[1] :: InlineNamecallNode).Target).Value == "WaitForChild" or ((node.Args[1] :: InlineNamecallNode).Target).Value == "GetService") and (node.Args[1] :: InlineNamecallNode).Args[1].Type == "StringNode" and not ((node.Args[1] :: InlineNamecallNode).Args[1] :: StringNode).Value:find(" ") then
  2940. module = "_" .. ((node.Args[1] :: InlineNamecallNode).Args[1] :: StringNode).Value
  2941. end
  2942. elseif #node.Args == 1 and node.Args[1].Type == "TableIndexNode" then
  2943. if (node.Args[1] :: TableIndexNode).Index.Type == "StringNode" and not ((node.Args[1] :: TableIndexNode).Index :: StringNode).Value:find(" ") then
  2944. module = "_" .. ((node.Args[1] :: TableIndexNode).Index :: StringNode).Value
  2945. end
  2946. end
  2947. cons ..= fmt("v%d_module%s", cid, module)
  2948. val.Id = fmt("v%d_module%s", cid, module)
  2949. elseif target == "pairs" or target == "ipairs" then
  2950. local id = fmt("v%d_", cid)
  2951. if i == 1 then
  2952. id ..= "generator"
  2953. elseif i == 2 then
  2954. id ..= "state"
  2955. elseif i == 3 then
  2956. id ..= "index"
  2957. end
  2958. cons ..= id
  2959. val.Id = id
  2960. elseif not string.match(target, "%W") then -- isalnum()
  2961. cons ..= fmt("v%d_%s_ret%d", cid, target, i)
  2962. val.Id = fmt("v%d_%s_ret%d", cid, target, i)
  2963. else
  2964. cons ..= fmt("v%d", cid)
  2965. val.Id = fmt("v%d", cid)
  2966. end
  2967. cid += 1
  2968. if i < #node.RetVals then
  2969. cons ..= ", "
  2970. end
  2971. end
  2972. cons ..= " = "
  2973. end
  2974. cons ..= target .. if node.Target.Type == "ClosureNode" then ")" .. "(" else "" .. "("
  2975. for i, arg in next, node.Args do
  2976. cons ..= TranspileValue(arg, depth)
  2977. if i < #node.Args then
  2978. cons ..= ", "
  2979. end
  2980. end
  2981. cons ..= ")"
  2982. return Indent(depth) .. cons
  2983. elseif node.Type == "NamecallNode" then
  2984. local cons: string = ""
  2985. if #node.RetVals > 0 then
  2986. cons ..= "local "
  2987. if #node.RetVals == 1 and (node.Target.Value == "FindFirstChild" or node.Target.Value == "FindFirstChildOfClass" or node.Target.Value == "WaitForChild" or node.Target.Value == "GetService") and node.Args[1].Type == "StringNode" and not (node.Args[1] :: StringNode).Value:find(" ") then
  2988. cons ..= fmt("v%d", cid) .. "_" .. (node.Args[1] :: StringNode).Value
  2989. node.RetVals[1].Id = fmt("v%d", cid) .. "_" .. (node.Args[1] :: StringNode).Value
  2990. cid += 1
  2991. else
  2992. for i, val in next, node.RetVals do
  2993. cons ..= fmt("v%d_%s_ret%d", cid, node.Target.Value, i)
  2994. val.Id = fmt("v%d_%s_ret%d", cid, node.Target.Value, i)
  2995. cid += 1
  2996. if i < #node.RetVals then
  2997. cons ..= ", "
  2998. end
  2999. end
  3000. end
  3001. cons ..= " = "
  3002. end
  3003. cons ..= TranspileValue(node.Object, depth) .. ":" .. node.Target.Value .. "("
  3004. for i, arg in next, node.Args do
  3005. cons ..= TranspileValue(arg, depth)
  3006. if i < #node.Args then
  3007. cons ..= ", "
  3008. end
  3009. end
  3010. cons ..= ")"
  3011. return Indent(depth) .. cons
  3012. elseif node.Type == "ReturnNode" then
  3013. local cons: string = "return"
  3014. if #node.Values > 0 then
  3015. cons ..= " "
  3016. for i, value in next, node.Values do
  3017. cons ..= TranspileValue(value, depth)
  3018. if i < #node.Values then
  3019. cons ..= ", "
  3020. end
  3021. end
  3022. end
  3023. return Indent(depth) .. cons
  3024. elseif node.Type == "TableAssignNode" then
  3025. if node.Index.Type == "StringNode" and not string.find(node.Index.Value, " ") then
  3026. return Indent(depth) .. fmt("%s.%s = %s", TranspileValue(node.Table, depth), node.Index.Value, TranspileValue(node.Source, depth))
  3027. end
  3028. return Indent(depth) .. fmt("%s[%s] = %s", TranspileValue(node.Table, depth), TranspileValue(node.Index, depth), TranspileValue(node.Source, depth))
  3029. elseif node.Type == "IfNode" then
  3030. while #node.Body == 1 and node.Body[1].Type == "IfNode" do
  3031. node.Condition = {
  3032. Type = "ExpressionNode",
  3033. Lhs = node.Condition,
  3034. Rhs = (node.Body[1] :: IfNode).Condition,
  3035. Op = "and"
  3036. }
  3037. node.Body = (node.Body[1] :: IfNode).Body
  3038. end
  3039. local cons: string = Indent(depth) .. fmt("if %s then\n", TranspileValue(node.Condition, depth))
  3040. for i, _node in next, node.Body do
  3041. cons ..= TranspileNode(_node, depth + 1)
  3042. if i < #node.Body then
  3043. cons ..= "\n"
  3044. end
  3045. end
  3046. local function Resolvebranches(_node: IfNode)
  3047. if #_node.ElseBody == 1 and _node.ElseBody[1].Type == "IfNode" then
  3048. cons ..= "\n" .. Indent(depth) .. fmt("elseif %s then\n", TranspileValue((_node.ElseBody[1] :: IfNode).Condition, depth))
  3049. for i, __node in next, (_node.ElseBody[1] :: IfNode).Body do
  3050. cons ..= TranspileNode(__node, depth + 1)
  3051. if i < #_node.Body then
  3052. cons ..= "\n"
  3053. end
  3054. end
  3055. Resolvebranches(_node.ElseBody[1] :: IfNode)
  3056. table.remove(_node.ElseBody, 1)
  3057. end
  3058. end
  3059. Resolvebranches(node)
  3060. if #node.ElseBody > 0 then
  3061. cons ..= "\n" .. Indent(depth) .. "else\n"
  3062. for i, _node in next, node.ElseBody do
  3063. cons ..= TranspileNode(_node, depth + 1)
  3064. if i < #node.ElseBody then
  3065. cons ..= "\n"
  3066. end
  3067. end
  3068. end
  3069. return cons .. "\n" .. Indent(depth) .. "end"
  3070. elseif node.Type == "WhileNode" then
  3071. local cons: string = Indent(depth) .. fmt("while %s do\n", TranspileValue(node.Condition, depth))
  3072. for i, _node in next, node.Body do
  3073. cons ..= TranspileNode(_node, depth + 1)
  3074. if i < #node.Body then
  3075. cons ..= "\n"
  3076. end
  3077. end
  3078. return cons .. "\n" .. Indent(depth) .. "end"
  3079. elseif node.Type == "ForRangeNode" then
  3080. if node.Iterator.Id == "" then
  3081. node.Iterator.Id = fmt("v%d", cid)
  3082. cid += 1
  3083. end
  3084. local cons: string = Indent(depth) .. fmt("for %s = %s, %s, %s do\n", node.Iterator.Id, TranspileValue(node.Index, depth), TranspileValue(node.Limit, depth), TranspileValue(node.Step, depth))
  3085. for i, _node in next, node.Body do
  3086. cons ..= TranspileNode(_node, depth + 1)
  3087. if i < #node.Body then
  3088. cons ..= "\n"
  3089. end
  3090. end
  3091. return cons .. "\n" .. Indent(depth) .. "end"
  3092. elseif node.Type == "ForValueNode" then
  3093. local vars: string = ""
  3094. for i, var in next, node.Variables do
  3095. var.Id = fmt("v%d", cid)
  3096. cid += 1
  3097. vars ..= var.Id
  3098. if i < #node.Variables then
  3099. vars ..= ", "
  3100. end
  3101. end
  3102. local cons: string = ""
  3103. -- lol silly workaround
  3104. if node.Index.Type ~= "NilNode" then
  3105. cons = Indent(depth) .. fmt("for %s in %s, %s, %s do\n", vars, TranspileValue(node.Generator, depth), TranspileValue(node.State, depth), TranspileValue(node.Index, depth))
  3106. else
  3107. cons = Indent(depth) .. fmt("for %s in %s, %s do\n", vars, TranspileValue(node.Generator, depth), TranspileValue(node.State, depth))
  3108. end
  3109. for i, _node in next, node.Body do
  3110. cons ..= TranspileNode(_node, depth + 1)
  3111. if i < #node.Body then
  3112. cons ..= "\n"
  3113. end
  3114. end
  3115. return cons .. "\n" .. Indent(depth) .. "end"
  3116. elseif node.Type == "GlobalDefinitionNode" then
  3117. if node.Value.Type == "ClosureNode" then
  3118. local cons: string = Indent(depth) .. fmt("function %s(", node.Value.Name)
  3119. for i, v in next, node.Value.Params do
  3120. cons ..= fmt("v%d", cid)
  3121. if i < #node.Value.Params then
  3122. cons ..= ", "
  3123. end
  3124. v.Id = fmt("v%d", cid)
  3125. cid += 1
  3126. end
  3127. if node.Value.IsVarArg then
  3128. if #node.Value.Params > 0 then
  3129. cons ..= ", "
  3130. end
  3131. cons ..= "..."
  3132. end
  3133. cons ..= ")\n"
  3134. return Indent(depth) .. cons .. TranspileClosure(node.Value, depth + 1) .. Indent(depth) .. "end"
  3135. end
  3136. return Indent(depth) .. fmt("%s = %s", node.Target, TranspileValue(node.Value, depth))
  3137. end
  3138. return Indent(depth) .. fmt("-- UNSUPPORTED: %s", node.Type)
  3139. end
  3140.  
  3141. for i, node in next, closure.Body do
  3142. if i == #closure.Body and node.Type == "ReturnNode" and #node.Values == 0 then
  3143. break -- remove trailing return if it is not needed
  3144. end
  3145. transpiled_closure_src ..= fmt("%s\n", TranspileNode(node, depth))
  3146. end
  3147. return transpiled_closure_src
  3148. end
  3149.  
  3150. local main_proto = bytecode.protos[bytecode.main_proto_id + 1]
  3151. local main_proto_instrs: { Instruction } = RefcountProto(main_proto, {})
  3152. -- create a dummy closure for main proto
  3153. local main_closure: Closure = {
  3154. Type = "Closure",
  3155. Value = main_proto_instrs,
  3156. Params = {},
  3157. IsVarArg = false,
  3158. Name = "main",
  3159. UpValues = {},
  3160. IsGlobal = false
  3161. }
  3162.  
  3163. local ast: ClosureNode = DecompileClosure(main_closure)
  3164. local source: string = TranspileClosure(ast, 0)
  3165. local endTime = tick()
  3166. return string.sub(source, 1, string.len(source) - 1), endTime - start_time
  3167. end
  3168.  
  3169. local base64 = (function()
  3170. local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' -- You will need this for encoding/decoding
  3171. -- encoding
  3172. local function enc(data)
  3173. return ((data:gsub('.', function(x)
  3174. local r,b='',x:byte()
  3175. for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
  3176. return r;
  3177. end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
  3178. if (#x < 6) then return '' end
  3179. local c=0
  3180. for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
  3181. return b:sub(c+1,c+1)
  3182. end)..({ '', '==', '=' })[#data%3+1])
  3183. end
  3184.  
  3185. -- decoding
  3186. local function dec(data)
  3187. data = string.gsub(data, '[^'..b..'=]', '')
  3188. return (data:gsub('.', function(x)
  3189. if (x == '=') then return '' end
  3190. local r,f='',(b:find(x)-1)
  3191. for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
  3192. return r;
  3193. end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
  3194. if (#x ~= 8) then return '' end
  3195. local c=0
  3196. for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
  3197. return string.char(c)
  3198. end))
  3199. end
  3200.  
  3201. return {
  3202. encode = enc,
  3203. decode = dec
  3204. }
  3205. end)()
  3206.  
  3207.  
  3208. --[[
  3209. if identifyexecutor then
  3210. return {function(script: LocalScript | ModuleScript)
  3211. local bytes: string = getscriptbytecode(script)
  3212. local bytecode: luau_bytecode = deserialize(bytes, true)
  3213. return "-- Decompiled with SirHurt // https://sirhurt.net/?ref=woffle // written by @luavm, et al.\n\n" .. wdec_decompile(bytecode)
  3214. end}
  3215. else
  3216. -- No credits header for non-roblox decompiled scripts cuz why bother
  3217. return {function(bytes: string)
  3218. local bytecode = deserialize(bytes, true)
  3219. return wdec_decompile(bytecode)
  3220. end}
  3221. end
  3222. ]]
  3223. function decompile(script: LocalScript | ModuleScript)
  3224. local CreditHeader = '--Decompiled with wdec // Made by @luavm // fixed by @legalcarbomb (still needs tweaking)\n'
  3225. local _, bytecode = pcall(getscriptbytecode, script)
  3226. assert(_, 'Failed to retrieve bytecode from script: ' .. script.Name .. '\n\n Cause: ' .. bytecode)
  3227. local _, deserialized = pcall(deserialize, bytecode, true)
  3228. local result, timeTaken = wdec_decompile(deserialized)
  3229. return CreditHeader .. "--Time Taken: " .. timeTaken .. "\n" .. result
  3230. end
  3231. local env = (getgenv and getgenv()) or (getfenv and getfenv()) or shared or _G
  3232. env.decompile = decompile
  3233. function bytecode_decompile(bytecode)
  3234. local CreditHeader = '--Decompiled with wdec // Made by @luavm // fixed by @legalcarbomb (still needs tweaking)\n'
  3235. --//assert(_, 'Failed to retrieve bytecode from script: ' .. script .. '\n\n Cause: ' .. bytecode)
  3236. local _, deserialized = pcall(deserialize, bytecode, false)
  3237. local result, timeTaken = wdec_decompile(deserialized)
  3238. return CreditHeader .. "--Time Taken: " .. timeTaken .. "\n" .. result
  3239. end
  3240. --local LuauCeption = loadstring(game.HttpService:GetAsync("https://github.com/RealEthanPlayzDev/LuauCeption/releases/download/0.645/Luau.LuauCeption.Compiler.0.645.luau", true))() -- Bytecode Compiler
  3241. --Usage: bytecode_decompile(LuauCeption.luau_compile())
Add Comment
Please, Sign In to add comment