NoTextForSpeech

Untitled

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