Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --!strict
- --[[
- wdec - Luau decompiler
- written by luavm on discord
- ]]
- --// Decompiler fixed by @legalcarbomb on Discord.
- --@return string
- -- // This was fixed using Fiu (Decompiler still has a lot of flaws.)
- --[[
- 1. Opcodes are not updated
- 2. Oplist is not updated
- 3. Opcode handling is not updated (maybe even constant handling)
- ]]
- --TODO: Update Oplist (will do later) to v6
- --TODO: Update the BuiltIns table
- --- woffle if you want it taken down (for some reason) lmk
- --[[
- CHANGELOG:
- Updated Oplist,
- Updated BuiltIns
- ]]
- ---
- game_httpget = function(s)
- return game:HttpGet(s)
- end
- local httpget = httpget or game_httpget or http_get -- add your httpget func here
- local InspectUrl = "https://raw.githubusercontent.com/kikito/inspect.lua/refs/heads/master/inspect.lua"
- local inspect = loadstring(httpget(InspectUrl))()
- local fmt = string.format -- string.format is annoying to write
- local getscriptbytecode = getscriptbytecode
- local request = request
- type luau_op = {
- name: string,
- aux: boolean
- }
- --I removed DEP_FORGLOOP_INEXT, DEP_JUMPIFEQK and DEP_JUMPIFNOTEQK (deprecated)
- local opcodes = {
- {name = "NOP", aux = false},
- {name = "BREAK", aux = false},
- {name = "LOADNIL", aux = false},
- {name = "LOADB", aux = true},
- {name = "LOADN", aux = false},
- {name = "LOADK", aux = false},
- {name = "MOVE", aux = false},
- {name = "GETGLOBAL", aux = true},
- {name = "SETGLOBAL", aux = true},
- {name = "GETUPVAL", aux = false},
- {name = "SETUPVAL", aux = false},
- {name = "CLOSEUPVALS", aux = false},
- {name = "GETIMPORT", aux = true},
- {name = "GETTABLE", aux = false},
- {name = "SETTABLE", aux = false},
- {name = "GETTABLEKS", aux = true},
- {name = "SETTABLEKS", aux = true},
- {name = "GETTABLEN", aux = false},
- {name = "SETTABLEN", aux = false},
- {name = "NEWCLOSURE", aux = false},
- {name = "NAMECALL", aux = true},
- {name = "CALL", aux = false},
- {name = "RETURN", aux = false},
- {name = "JUMP", aux = false},
- {name = "JUMPBACK", aux = false},
- {name = "JUMPIF", aux = true},
- {name = "JUMPIFNOT", aux = true},
- {name = "JUMPIFEQ", aux = true},
- {name = "JUMPIFLE", aux = true},
- {name = "JUMPIFLT", aux = true},
- {name = "JUMPIFNOTEQ", aux = true},
- {name = "JUMPIFNOTLE", aux = true},
- {name = "JUMPIFNOTLT", aux = true},
- {name = "ADD", aux = false},
- {name = "SUB", aux = false},
- {name = "MUL", aux = false},
- {name = "DIV", aux = false},
- {name = "MOD", aux = false},
- {name = "POW", aux = false},
- {name = "ADDK", aux = false},
- {name = "SUBK", aux = false},
- {name = "MULK", aux = false},
- {name = "DIVK", aux = false},
- {name = "MODK", aux = false},
- {name = "POWK", aux = false},
- {name = "AND", aux = false},
- {name = "OR", aux = false},
- {name = "ANDK", aux = false},
- {name = "ORK", aux = false},
- {name = "CONCAT", aux = false},
- {name = "NOT", aux = false},
- {name = "MINUS", aux = false},
- {name = "LENGTH", aux = false},
- {name = "NEWTABLE", aux = true},
- {name = "DUPTABLE", aux = false},
- {name = "SETLIST", aux = true},
- {name = "FORNPREP", aux = true},
- {name = "FORNLOOP", aux = true},
- {name = "FORGLOOP", aux = true},
- {name = "FORGPREP_INEXT", aux = false},
- {name = "FASTCALL3", aux = true},
- {name = "FORGPREP_NEXT", aux = false},
- {name = "NATIVECALL", aux = false},
- {name = "GETVARARGS", aux = false},
- {name = "DUPCLOSURE", aux = false},
- {name = "PREPVARARGS", aux = false},
- {name = "LOADKX", aux = true},
- {name = "JUMPX", aux = false},
- {name = "FASTCALL", aux = false},
- {name = "COVERAGE", aux = false},
- {name = "CAPTURE", aux = false},
- {name = "SUBRK", aux = false},
- {name = "DIVRK", aux = false},
- {name = "FASTCALL1", aux = false},
- {name = "FASTCALL2", aux = true},
- }
- local function GetOp(opcode: number): luau_op
- return opcodes[opcode + 1]
- end
- type closure = typeof(function() end)
- type luau_instruction = { -- d and e are for ease of coding
- name : string,
- a : number,
- b : number,
- c : number,
- d : number,
- e : number,
- aux : number | nil
- }
- type luau_constant = {
- type: number,
- value: number | boolean | { number }
- }
- type luau_proto = {
- maxstacksize: number,
- numparams: number,
- numupvalues: number,
- isvararg: boolean,
- instructions: { luau_instruction },
- constants: { luau_constant },
- child_protos: { number },
- name: string?,
- types: {
- flags: number,
- data: { number }
- }
- }
- type luau_bytecode = { -- TODO: Exclude strings and somehow inline into deserializer
- version : number,
- protos : { luau_proto },
- strings : { string },
- main_proto_id: number
- }
- local luau_bytecode_max = 6
- local luau_bytecode_min = 3
- type luau_bytes = { number } -- I like custom named types for these things
- type luau_constant_type = number
- local function errorf(format, ...) -- C
- error(fmt(format, ...))
- end
- --[[local function BytesToTable(bytes): luau_bytes
- local tbl = {}
- for i = 1, string.len(bytes) do
- table.insert(tbl, string.byte(string.sub(bytes, i, i)))
- end
- return tbl
- end--]] -- Deprecated, replaced with buffer
- local function RobloxOp(x: number): number -- Turns a Roblox encrypted opcode into a luau opcode. Ex: 105 -> 5
- return x * 203 % 256
- end
- local function deserialize(s_bytes: string, is_roblox: boolean): luau_bytecode?
- local b_bytes: buffer = buffer.fromstring(s_bytes)
- print(string.format('%s', s_bytes))
- print(b_bytes)
- local bytes_index = 0
- local function GetByte(): number
- local byte = buffer.readu8(b_bytes, bytes_index)
- bytes_index += 1
- return byte
- end
- local function GetInt(): number
- --[[return bit32.bor(
- bit32.lshift(GetByte(), 0), -- Redundant lshift but it looks better
- bit32.lshift(GetByte(), 8),
- bit32.lshift(GetByte(), 16),
- bit32.lshift(GetByte(), 24)
- )--]] -- Deprecated, replaced with readu32
- local int = buffer.readu32(b_bytes, bytes_index)
- bytes_index += 4
- return int
- end
- local function GetVarInt(): number
- local result = 0
- for i = 0, 4 do
- local value = GetByte()
- result = bit32.bor(bit32.lshift(bit32.band(value, 0x7F), i * 7))
- if not bit32.btest(value, 0x80) then
- break
- end
- end
- return result
- end
- local function GetString()
- local string_sz = GetVarInt()
- if string_sz == 0 then
- return ""
- end
- local string = buffer.readstring(b_bytes, bytes_index, string_sz)
- bytes_index += string_sz
- return string
- end
- local bytecode_version = GetByte() -- Should always be 3, 4, 5 or 6 apparently
- print(bytecode_version)
- if bytecode_version > luau_bytecode_max or bytecode_version < luau_bytecode_min then
- errorf("Incompatible bytecode version. (%d)", bytecode_version) -- Should never happen
- return nil
- end
- local types_version = 0
- if bytecode_version >= 4 then
- types_version = GetByte()
- print(types_version)
- end
- local string_table = {}
- local strings_sz = GetVarInt()
- print(strings_sz)
- 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
- string_table[i] = GetString()
- end
- print(inspect(string_table))
- local _userdata_types = {}
- while true do
- local index = GetByte()
- if index == 0 then
- break
- end
- local _name_ref = GetVarInt()
- _userdata_types[index] = _name_ref
- end
- local protos: { luau_proto } = {}
- local protos_sz = GetVarInt()
- for i = 1, protos_sz do -- Iterate over every proto, including main.
- local proto_id = i
- local maxstacksize: number = GetByte()
- local numparams: number = GetByte()
- local numupvalues: number = GetByte()
- local isvararg: boolean = GetByte() ~= 0
- local types = {}
- -- we can recover type data if the bytecode version is 4
- if bytecode_version >= 4 then
- types.flags = GetByte()
- types.data = {}
- local type_sz = GetVarInt()
- for i = 1, type_sz do
- table.insert(types.data, GetByte())
- end
- end
- local instructions_sz: number = GetVarInt()
- local instructions: { luau_instruction } = {}
- local i: number = 0
- local n_aux_instructions: number = 0
- while i < instructions_sz do
- local opcode: number = GetByte()
- if is_roblox then
- opcode = RobloxOp(opcode)
- end
- local a: number = GetByte()
- local b: number = GetByte()
- local c: number = GetByte()
- local op: luau_op = GetOp(opcode)
- if not op then break end
- local aux: number? = nil
- if op.aux then
- aux = GetInt()
- i += 1
- 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)
- end
- local function tosigned(int: number, bits: number)
- local max = 2^(bits - 1)
- if int >= max then
- int = int - 2^bits
- end
- return int
- end
- local d: number = tosigned(bit32.bor(b, bit32.lshift(c, 8)), 16) -- d and e are for ease of coding
- local e: number = tosigned(bit32.bor(a, bit32.lshift(b, 8), bit32.lshift(c, 16)), 24)
- table.insert(instructions, { -- luau_instrctuon
- name = op.name,
- a = a,
- b = b,
- c = c,
- d = d,
- e = e,
- aux = aux
- })
- i += 1
- end
- local constants_sz: number = GetVarInt()
- local constants: { luau_constant } = {}
- for _ = 1, constants_sz do
- local const_type: luau_constant_type = GetByte()
- if const_type == 0 then -- NIL
- table.insert(constants, {
- type = const_type,
- value = 0
- })
- elseif const_type == 1 then -- BOOLEAN
- table.insert(constants, {
- type = const_type,
- value = GetByte() ~= 0
- })
- elseif const_type == 2 then -- NUMBER
- local str: string = ""
- for _ = 1, 8 do
- str ..= string.char(GetByte())
- end
- table.insert(constants, {
- type = const_type,
- value = string.unpack("<d", str)
- })
- elseif const_type == 3 then -- STRING
- table.insert(constants, {
- type = const_type,
- value = GetVarInt()
- })
- elseif const_type == 4 then -- IMPORT
- table.insert(constants, {
- type = const_type,
- value = GetInt()
- })
- elseif const_type == 5 then -- TABLE
- local tbl: { number } = {}
- local tblSz = GetVarInt()
- for _ = 1, tblSz do
- table.insert(tbl, GetVarInt()) -- Where VarInt = Key
- end
- table.insert(constants, {
- type = const_type,
- value = tbl
- })
- elseif const_type == 6 then -- CLOSURE
- table.insert(constants, {
- type = const_type,
- value = GetVarInt()
- })
- end
- end
- local child_protos: { number } = {}
- for _ = 1, GetVarInt() do -- Iterate over number of child protos
- table.insert(child_protos, GetVarInt()) -- Add index of child proto into the child proto table
- end
- -- 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.
- -- The code is pretty verbose so if you want, you can copy-paste it into your project to read debug data.
- -- Most of this can be fetched using the `debug` library provided by most executors
- local _debug_linedefined: number = GetVarInt()
- local _debug_name: number = GetVarInt()
- local proto: luau_proto = {
- maxstacksize = maxstacksize,
- numparams = numparams,
- numupvalues = numupvalues,
- isvararg = isvararg,
- instructions = instructions,
- constants = constants,
- child_protos = child_protos,
- name = _debug_name ~= nil and string_table[_debug_name] or nil,
- types = types
- }
- protos[proto_id] = proto
- --[[local _debug_haslines: boolean = GetByte() ~= 0
- if _debug_haslines then
- local span: number = GetVarInt()
- for _ = 1, instructions_sz do
- GetByte()
- end
- for _ = 1, bit32.rshift(instructions_sz - 1, span) + 1 do
- GetInt()
- end
- end--]]
- local _debug_lineinfoenabled = GetByte() ~= 0
- local i_lineinfo = nil
- if _debug_lineinfoenabled then
- local linegaplog2 = GetByte()
- local intervals = bit32.rshift((instructions_sz - 1), linegaplog2) + 1
- local lineinfo = table.create(instructions_sz)
- local abslineinfo = table.create(intervals)
- local lastoffset = 0
- for _ = 1, instructions_sz do
- lastoffset = GetByte()
- table.insert(lineinfo, lastoffset)
- end
- local lastline = 0
- for _ = 1, intervals do
- lastline += GetInt()
- table.insert(abslineinfo, (lastline%(2^32)))
- end
- instruction_line_info = table.create(instructions_sz)
- for i = 1, instructions_sz do
- table.insert(instruction_line_info, abslineinfo[bit32.rshift(i - 1, linegaplog2) + 1] + lineinfo[i])
- end
- end
- local _debug_hasdebug = GetByte() ~= 0
- if _debug_hasdebug then
- for _ = 1, GetVarInt() do -- Locals
- local _lname = GetVarInt()
- local _lstartpc = GetVarInt()
- local _lendpc = GetVarInt()
- local _lreg = GetByte()
- end
- for _ = 1, GetVarInt() do -- Upvalues
- local _lname = GetVarInt()
- end
- end
- end
- local bytecode: luau_bytecode = {
- version = bytecode_version,
- strings = string_table,
- protos = protos,
- main_proto_id = GetVarInt()
- }
- print(inspect(bytecode))
- return bytecode
- end
- type Register = {
- Type: "Register",
- Value: number
- }
- type Number = {
- Type: "Number",
- Value: number
- }
- type String = {
- Type: "String",
- Value: string
- }
- type Nil = {
- Type: "Nil",
- Value: nil
- }
- type Boolean = {
- Type: "Boolean",
- Value: boolean
- }
- type LBC_Import = {
- Type: "LBC_Import",
- Value: number -- no clue how to decode this :sob:
- }
- type LBC_Table = { -- Constant table
- Type: "LBC_Table",
- Value: { number }
- }
- type Table = { -- Dynamic table
- Type: "Table",
- Value: { Value }
- }
- type LBC_Closure = {
- Type: "LBC_Closure",
- Value: number -- No clue how to decode thsi either :pray:
- }
- type Closure = {
- Type: "Closure",
- Value: { Instruction },
- Params: { Local },
- IsVarArg: boolean,
- Name: string,
- UpValues: { Value },
- IsGlobal: boolean,
- ParamTypes: { number }
- }
- type TableIndex = {
- Type: "TableIndex",
- Table: Value,
- Index: Value
- }
- type Local = {
- Type: "Local",
- Register: number,
- Value: Value,
- Latest: Value,
- Id: string,
- Scope: luau_proto,
- Lifetime: number?,
- 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
- }
- type Expression = {
- Type: "Expression",
- Op: string,
- Lhs: Value,
- Rhs: Value
- }
- type VarArgs = { -- Decompiles to `...`
- Type: "VarArgs"
- }
- type Unary = {
- Type: "Unary",
- Op: string,
- Value: Value
- }
- type Strings = { -- Needed for CONCAT
- Type: "Strings",
- Value: { Value }
- }
- type Global = {
- Type: "Global",
- Value: string
- }
- type InlineNamecall = {
- Type: "InlineNamecall",
- Args: { Value },
- Object: Value,
- Target: String
- }
- type InlineCall = {
- Type: "InlineCall",
- Args: { Value },
- Target: Value
- }
- type Value = Register | Number | String | Nil | Boolean | LBC_Import | LBC_Table | Table | LBC_Closure | Closure | Local | TableIndex | Expression | Unary | Strings | VarArgs | Global | InlineNamecall | InlineCall
- type Instruction = {
- Name: string,
- ArgA: Value?,
- ArgB: Value?,
- ArgC: Value?,
- ArgD: Value?,
- ArgE: Value?,
- ArgAux: Value?,
- Special: any
- }
- ---
- type VarAssignNode = {
- Type: "VarAssignNode",
- Target: Local,
- Value: ValueNode
- }
- type VarReassignNode = {
- Type: "VarReassignNode",
- Target: Local,
- Value: ValueNode
- }
- type GlobalDefinitionNode = {
- Type: "GlobalDefinitionNode",
- Target: string,
- Value: ValueNode
- }
- type ClosureNode = {
- Type: "ClosureNode",
- Name: string,
- Params: { Local },
- IsVarArg: boolean,
- Body: { Node },
- ParamTypes: { number }
- }
- type FunctionCallNode = {
- Type: "FunctionCallNode",
- Target: ValueNode,
- Args: { ValueNode },
- RetVals: { Local }
- }
- type ReturnNode = {
- Type: "ReturnNode",
- Values: { ValueNode }
- }
- type TableAssignNode = {
- Type: "TableAssignNode",
- Table: ValueNode,
- Index: ValueNode,
- Source: ValueNode
- }
- type NamecallNode = {
- Type: "NamecallNode",
- Args: { ValueNode },
- RetVals: { Local },
- Object: ValueNode,
- Target: StringNode
- }
- type BreakNode = {
- Type: "BreakNode"
- }
- type ContinueNode = {
- Type: "ContinueNode"
- }
- type IfNode = {
- Type: "IfNode",
- Condition: ValueNode,
- Body: { Node },
- ElseBody: { Node },
- }
- type WhileNode = {
- Type: "WhileNode",
- Condition: ValueNode,
- Body: { Node }
- }
- type ForRangeNode = { --- for x = y, z[, æ] do ... end
- Type: "ForRangeNode",
- Index: ValueNode,
- Limit: ValueNode,
- Step: ValueNode,
- Iterator: Local,
- Body: { Node }
- }
- type ForValueNode = { --- for x, ... in y, z[, æ] do ... end
- Type: "ForValueNode",
- Generator: ValueNode,
- State: ValueNode,
- Index: ValueNode, -- Mostly NilNode
- Variables: { Local },
- Body: { Node }
- }
- -- Values
- type NumberNode = {
- Type: "NumberNode",
- Value: number
- }
- type StringNode = {
- Type: "StringNode",
- Value: string
- }
- type StringsNode = {
- Type: "StringsNode",
- Value: { ValueNode }
- }
- type NilNode = {
- Type: "NilNode",
- }
- type BooleanNode = {
- Type: "BooleanNode",
- Value: boolean
- }
- type ExpressionNode = {
- Type: "ExpressionNode",
- Op: string,
- Lhs: ValueNode,
- Rhs: ValueNode
- }
- type UnaryNode = {
- Type: "UnaryNode",
- Op: string,
- Value: ValueNode,
- }
- type VarArgsNode = {
- Type: "VarArgsNode"
- }
- type GlobalNode = {
- Type: "GlobalNode",
- Value: string
- }
- type TableIndexNode = {
- Type: "TableIndexNode",
- Table: ValueNode,
- Index: ValueNode
- }
- type TableNode = {
- Type: "TableNode",
- Body: { ValueNode }
- }
- type InlineNamecallNode = { -- Allow for things such as `game:GetService("Players").LocalPlayer.Name` etc
- Type: "InlineNamecallNode",
- Args: { ValueNode },
- Object: ValueNode,
- Target: StringNode
- }
- type InlineCallNode = { -- MULTRET call (`print(add(1, 2))`)
- Type: "InlineCallNode",
- Args: { ValueNode },
- Target: ValueNode
- }
- type ValueNode = NumberNode | StringNode | NilNode | BooleanNode | ExpressionNode | UnaryNode | VarArgsNode | ClosureNode | Local | TableIndexNode | GlobalNode | StringsNode | TableNode | InlineNamecallNode | InlineCallNode
- type Node = VarAssignNode | VarReassignNode | GlobalDefinitionNode | ClosureNode | ValueNode | FunctionCallNode | ReturnNode | TableAssignNode | NamecallNode | BreakNode | ContinueNode | IfNode | WhileNode | ForRangeNode | ForValueNode
- type AST = Node
- ---
- type BuiltInFunction = {
- Path: { string }
- }
- local builtins = {
- { Path = { "assert" } },
- { Path = { "type" } },
- { Path = { "typeof" } },
- { Path = { "rawset" } },
- { Path = { "rawget" } },
- { Path = { "rawequal" } },
- { Path = { "rawlen" } },
- { Path = { "unpack" } },
- { Path = { "select" } },
- { Path = { "getmetatable" } },
- { Path = { "setmetatable" } },
- -- math functions
- { Path = { "math", "abs" } },
- { Path = { "math", "acos" } },
- { Path = { "math", "asin" } },
- { Path = { "math", "atan2" } },
- { Path = { "math", "atan" } },
- { Path = { "math", "ceil" } },
- { Path = { "math", "cosh" } },
- { Path = { "math", "cos" } },
- { Path = { "math", "deg" } },
- { Path = { "math", "exp" } },
- { Path = { "math", "floor" } },
- { Path = { "math", "fmod" } },
- { Path = { "math", "frexp" } },
- { Path = { "math", "ldexp" } },
- { Path = { "math", "log10" } },
- { Path = { "math", "log" } },
- { Path = { "math", "max" } },
- { Path = { "math", "min" } },
- { Path = { "math", "modf" } },
- { Path = { "math", "pow" } },
- { Path = { "math", "rad" } },
- { Path = { "math", "sinh" } },
- { Path = { "math", "sin" } },
- { Path = { "math", "sqrt" } },
- { Path = { "math", "tanh" } },
- { Path = { "math", "tan" } },
- { Path = { "math", "clamp" } },
- { Path = { "math", "sign" } },
- { Path = { "math", "round" } },
- { Path = { "math", "lerp" } },
- -- bit32 functions
- { Path = { "bit32", "arshift" } },
- { Path = { "bit32", "band" } },
- { Path = { "bit32", "bnot" } },
- { Path = { "bit32", "bor" } },
- { Path = { "bit32", "bxor" } },
- { Path = { "bit32", "btest" } },
- { Path = { "bit32", "extract" } },
- { Path = { "bit32", "lrotate" } },
- { Path = { "bit32", "lshift" } },
- { Path = { "bit32", "replace" } },
- { Path = { "bit32", "rrotate" } },
- { Path = { "bit32", "rshift" } },
- { Path = { "bit32", "countlz" } },
- { Path = { "bit32", "countrz" } },
- { Path = { "bit32", "byteswap" } },
- -- string functions
- { Path = { "string", "byte" } },
- { Path = { "string", "char" } },
- { Path = { "string", "len" } },
- { Path = { "string", "sub" } },
- -- table functions
- { Path = { "table", "insert" } },
- { Path = { "table", "unpack" } },
- -- buffer functions
- { Path = { "buffer", "readi8" } },
- { Path = { "buffer", "readu8" } },
- { Path = { "buffer", "writei8" } },
- { Path = { "buffer", "writeu8" } },
- { Path = { "buffer", "readi16" } },
- { Path = { "buffer", "readu16" } },
- { Path = { "buffer", "writei16" } },
- { Path = { "buffer", "writeu16" } },
- { Path = { "buffer", "readi32" } },
- { Path = { "buffer", "readu32" } },
- { Path = { "buffer", "writei32" } },
- { Path = { "buffer", "writeu32" } },
- { Path = { "buffer", "readf32" } },
- { Path = { "buffer", "writef32" } },
- { Path = { "buffer", "readf64" } },
- { Path = { "buffer", "writef64" } },
- -- vector functions
- { Path = { "vector", "create" } },
- { Path = { "vector", "magnitude" } },
- { Path = { "vector", "normalize" } },
- { Path = { "vector", "cross" } },
- { Path = { "vector", "dot" } },
- { Path = { "vector", "floor" } },
- { Path = { "vector", "ceil" } },
- { Path = { "vector", "abs" } },
- { Path = { "vector", "sign" } },
- { Path = { "vector", "clamp" } },
- { Path = { "vector", "min" } },
- { Path = { "vector", "max" } }
- }
- local function ConvertBuiltin(id: number): GlobalNode | TableIndexNode
- local path = builtins[id].Path
- if #path == 1 then
- return {
- Type = "GlobalNode",
- Value = path[1]
- }
- end
- return {
- Type = "TableIndexNode",
- Table = {
- Type = "GlobalNode",
- Value = path[1]
- },
- Index = {
- Type = "StringNode",
- Value = path[2]
- }
- }
- end
- local function wdec_decompile(bytecode: luau_bytecode): string
- local start_time = tick()
- local function ReAssigns(instruction: luau_instruction, target: number): boolean -- Function to check if target register `target` is written to by `instruction`
- 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
- if instruction.name == "CALL" then
- return instruction.c - 1 > 0 and target >= instruction.a and target <= instruction.a + instruction.c - 1
- end
- if instruction.name == "FORNPREP" then
- return target == instruction.a + 2
- end
- if table.find({
- "ADD", "SUB", "MUL", "DIV", "ADDK",
- "SUBK", "MULK", "DIVK", "MOD", "POW",
- "MODK", "POWK", "AND", "OR", "ANDK", "ORK"
- }, instruction.name) then
- if instruction.a == instruction.b and target == instruction.a then
- return false -- Here it technically gets re-assigned, BUT it's still the same variable
- end
- end
- return instruction.a == target -- Target register is always instr.a
- end
- local function References(instruction: luau_instruction, target: number): boolean -- Function to check whether `instruction` references (retrieves the value of) register `target`
- if table.find({ -- Below is a list of functions which reference only one register and it references it in instr.b
- "MOVE", "GETTABLEKS", "SETTABLEKS", "GETTABLEN", "NAMECALL", "ADDK",
- "SUBK", "MULK", "DIVK", "MODK", "POWK", "ANDK", "ORK", "NOT", "MINUS",
- "LENGTH", "CAPTURE", "FASTCALL1", "FASTCALL2K", "IDIVK", "JUMPXEQKNIL",
- "JUMPXEQKB", "JUMPXEQKN", "JUMPXEQKS"
- }, instruction.name) then
- return instruction.b == target -- Return whether it's referenced
- elseif instruction.name == "CALL" then
- return target == instruction.b or (instruction.c ~= 0 and target == instruction.a + 1 + instruction.c)
- elseif table.find({
- "JUMPIFEQ", "JUMPIFLE", "JUMPIFLT",
- "JUMPIFNOTEQ", "JUMPIFNOTLE", "JUMPIFNOTLT"
- }, instruction.name) then
- return instruction.a == target or instruction.aux == target
- elseif table.find({
- "JUMPIF", "JUMPIFNOT",
- }, instruction.name) then
- return instruction.a == target
- elseif instruction.name == "FASTCALL2" then
- return instruction.aux == target
- elseif table.find({ -- Below is a list of instructions which reference two registers
- "GETTABLE", "SETTABLE", "ADD", "SUB", "MUL", "DIV", "MOD", "POW", "AND", "OR", "IDIV"
- }, instruction.name) then
- return (instruction.a == instruction.b and instruction.a == target) or instruction.b == target or instruction.c == target -- return if the register is referenced
- elseif instruction.name == "CONCAT" then
- return target >= instruction.b and target <= instruction.c
- elseif instruction.name == "SETLIST" then
- return target >= instruction.b and target <= instruction.b + instruction.c
- end
- return false
- end
- local function WritesTo(instruction: luau_instruction, _table: number): boolean
- if not table.find({
- "SETTABLE", "SETTABLEKS", "SETTABLEN"
- }, instruction.name) then
- return false
- end
- return _table == instruction.b
- end
- local function Captures(instruction: luau_instruction, target: number): boolean
- return instruction.name == "CAPTURE" and instruction.b == target
- end
- 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
- local const: luau_constant = proto.constants[index + 1]
- if const.type == 0 then
- return {
- Type = "Nil"
- }
- elseif const.type == 1 then
- return {
- Type = "Boolean",
- Value = const.value :: boolean
- }
- elseif const.type == 2 then
- return {
- Type = "Number",
- Value = const.value :: number
- }
- elseif const.type == 3 then
- if not isglobal then
- return {
- Type = "String",
- Value = bytecode.strings[const.value :: number]
- }
- else
- return {
- Type = "Global",
- Value = bytecode.strings[const.value :: number]
- }
- end
- elseif const.type == 4 then
- return {
- Type = "LBC_Import",
- Value = const.value :: number
- }
- elseif const.type == 5 then
- return {
- Type = "LBC_Table",
- Value = const.value :: { number }
- }
- elseif const.type == 6 then
- return {
- Type = "LBC_Closure",
- Value = const.value :: number
- }
- end
- return {
- Type = "Nil"
- }
- end
- 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
- local instructions: { Instruction } = {}
- local idx = 1
- local vars: { Local } = {}
- local regs: { [number]: Value } = passed_regs or {}
- local regs_stack: { { [number]: Value } } = {}
- 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
- local function Variable(instr: Instruction, value: Value, target: number, force_emit: boolean?): boolean
- local refs = 0
- local loop_depth = 0
- if not force_emit then
- local writes = 0
- local ends: { number } = {}
- for i = value.Type == "InlineNamecall" and idx or idx + 1, #proto.instructions do
- if Captures(proto.instructions[i], target) then
- force_emit = true
- break
- end
- if WritesTo(proto.instructions[i], target) then
- writes += 1
- if writes >= 2 then
- force_emit = true
- break
- end
- end
- if loop_depth == 0 and ReAssigns(proto.instructions[i], target) then
- break
- elseif ReAssigns(proto.instructions[i], target) then
- -- Check if a variable assigned to inside a scope is used outside the scope, if not, don't emit (unless other conditions met)
- local exitted_scope = false
- local new_i = 0
- for j = i, #proto.instructions do
- for _, x in next, ends do
- if j == x then
- exitted_scope = true
- new_i = j
- break
- end
- end
- if exitted_scope then break end
- end
- local outside_refs = 0
- for j = new_i, #proto.instructions do
- if References(proto.instructions[j], target) then
- outside_refs += 1
- end
- end
- if outside_refs <= 1 then
- break
- end
- end
- if References(proto.instructions[i], target) then
- refs += 1
- end
- -- TODO: Special case for JUMPX
- if table.find({"JUMP", "JUMPIF", "JUMPIFNOT", "JUMPIFEQ", "JUMPIFNOTEQ", "JUMPIFLT", "JUMPIFLE", "JUMPIFNOTLT", "JUMPIFNOTLE", "FORGPREP", "FORGPREP_NEXT", "FORGPREP_INEXT", "FORNPREP"}, proto.instructions[i].name) then
- loop_depth += 1
- local bytes_counted: number = 0
- local ninstructions: number = 0
- while bytes_counted < proto.instructions[i].d do
- bytes_counted += 1
- ninstructions += 1
- if proto.instructions[idx + ninstructions].aux ~= nil then
- bytes_counted += 1
- end
- end
- table.insert(ends, i + ninstructions)
- end
- for _, j in next, ends do
- if i == j then
- loop_depth -= 1
- end
- end
- end
- end
- if (value.Type == "InlineNamecall" or value.Type == "InlineCall") and refs == 0 then
- force_emit = true
- end
- -- TODO: Check for self reassigns and ALWAYS emit them
- -- Ex: a = a + 1 (or a += 1)
- if refs > 1 or force_emit then
- 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
- regs[target] = {
- Type = "Local",
- Id = "",
- Value = value,
- Latest = value,
- Register = target,
- Scope = proto
- }
- instr.ArgA = regs[target]
- table.insert(instructions, instr)
- elseif regs[target] and regs[target].Type == "Local" then
- instr.ArgA = regs[target]
- table.insert(instructions, instr)
- end
- else
- regs[target] = value
- table.insert(instructions, {
- Name = "NOP"
- })
- end
- return (refs > 1 or force_emit) :: boolean
- end
- local function CloneRegs(nbytes: number)
- -- This function will replace the regs table with an exact clone for nbytes bytes, as
- -- it separates newly created variables within the scope from variables outside the scope, while
- -- still alowing for re-assigns
- -- below code converts the number of bytes to a number of instructions by counting instructions with an AUX as 2 bytes.
- local bytes_counted: number = 0
- local ninstructions: number = 0
- while bytes_counted <= nbytes and idx + ninstructions < #proto.instructions do
- bytes_counted += 1
- ninstructions += 1
- if proto.instructions[idx + ninstructions] and proto.instructions[idx + ninstructions].aux ~= nil then
- bytes_counted += 1
- end
- end
- local regs_clone: { [number] : Value } = {}
- for k, v in next, regs do
- regs_clone[k] = v
- end
- if not regs_stack[idx + ninstructions] then
- regs_stack[idx + ninstructions] = regs_clone
- end
- end
- while idx <= #proto.instructions do
- for index, old_regs in next, regs_stack do
- if idx == index and old_regs then
- table.clear(regs)
- for k, v in next, old_regs do
- regs[k] = v
- end
- regs_stack[idx] = nil
- break
- end
- end
- local function GetUpvalues(): { Local }
- local l_upvalues: { Local } = {}
- while proto.instructions[idx + 1].name == "CAPTURE" do
- if proto.instructions[idx + 1].a <= 1 then
- table.insert(l_upvalues, regs[proto.instructions[idx + 1].b] :: Local)
- elseif proto.instructions[idx + 1].a == 2 then
- table.insert(l_upvalues, upvalues[proto.instructions[idx + 1].b + 1])
- end
- table.insert(instructions, { Name = "NOP" })
- idx += 1
- end
- return l_upvalues
- end
- local current: luau_instruction = proto.instructions[idx]
- if current.name == "LOADN" then
- Variable({ -- this is a VERY gross format to call a function lmfao
- Name = "LOADN",
- ArgB = {
- Type = "Number",
- Value = current.d
- }
- }, {
- Type = "Number",
- Value = current.d
- }, current.a)
- elseif current.name == "LOADB" then
- Variable({
- Name = "LOADB",
- ArgB = {
- Type = "Boolean",
- Value = current.b == 1
- },
- ArgC = {
- Type = "Number",
- Value = current.c
- }
- }, {
- Type = "Boolean",
- Value = current.b == 1
- }, current.a)
- elseif current.name == "LOADK" then
- Variable({
- Name = "LOADK",
- ArgB = GetConstant(proto, current.d)
- }, GetConstant(proto, current.d), current.a)
- elseif current.name == "LOADNIL" then
- Variable({
- Name = "LOADNIL",
- ArgB = {Type = "Nil"}
- }, {Type = "Nil"}, current.a)
- elseif current.name == "MOVE" then
- Variable({
- Name = "MOVE",
- ArgB = regs[current.b]
- }, regs[current.b], current.a)
- elseif current.name == "GETGLOBAL" then
- Variable({
- Name = "GETGLOBAL",
- ArgAux = GetConstant(proto, current.aux :: number, true)
- }, GetConstant(proto, current.aux :: number, true), current.a)
- elseif current.name == "SETGLOBAL" then
- table.insert(instructions, {
- Name = "SETGLOBAL",
- ArgA = regs[current.a],
- ArgAux = GetConstant(proto, current.aux :: number)
- })
- elseif current.name == "GETUPVAL" then
- Variable({
- Name = "GETUPVAL",
- ArgB = upvalues[current.b + 1]
- }, upvalues[current.b + 1], current.a)
- elseif current.name == "SETUPVAL" then
- table.insert(instructions, {
- Name = "SETUPVAL",
- ArgA = regs[current.a],
- ArgB = upvalues[current.b + 1]
- })
- elseif current.name == "GETIMPORT" then
- local path_length: number = bit32.rshift(current.aux :: number, 30)
- 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)}
- local import_path: Value
- if path_length == 1 then
- import_path = {
- Type = "Global",
- Value = (GetConstant(proto, path[1]) :: String).Value
- }
- elseif path_length == 2 then
- import_path = {
- Type = "TableIndex",
- Table = {
- Type = "Global",
- Value = (GetConstant(proto, path[1]) :: String).Value
- },
- Index = GetConstant(proto, path[2]) :: String
- }
- else
- import_path = {
- Type = "TableIndex",
- Table = {
- Type = "TableIndex",
- Table = {
- Type = "Global",
- Value = (GetConstant(proto, path[1]) :: String).Value
- },
- Index = GetConstant(proto, path[2]) :: String
- },
- Index = GetConstant(proto, path[3]) :: String
- }
- end
- Variable({
- Name = "GETIMPORT",
- Special = import_path
- }, import_path, current.a)
- elseif current.name == "GETTABLE" then -- TODO: Fix up table shit with chaining. Nvm I think it works????
- Variable({
- Name = "GETTABLE",
- Special = {
- Type = "TableIndex",
- Table = regs[current.b],
- Index = regs[current.c]
- }
- }, {
- Type = "TableIndex",
- Table = regs[current.b],
- Index = regs[current.c]
- }, current.a)
- elseif current.name == "SETTABLE" then
- table.insert(instructions, {
- Name = "SETTABLE",
- ArgA = regs[current.a],
- ArgB = regs[current.b],
- ArgC = regs[current.c]
- })
- elseif current.name == "GETTABLEKS" then
- Variable({
- Name = "GETTABLE",
- Special = {
- Type = "TableIndex",
- Table = regs[current.b],
- Index = GetConstant(proto, current.aux :: number)
- }
- }, {
- Type = "TableIndex",
- Table = regs[current.b],
- Index = GetConstant(proto, current.aux :: number)
- }, current.a)
- elseif current.name == "SETTABLEKS" then
- table.insert(instructions, {
- Name = "SETTABLE",
- ArgA = regs[current.a],
- ArgB = regs[current.b],
- ArgC = GetConstant(proto, current.aux :: number)
- })
- elseif current.name == "GETTABLEN" then
- Variable({
- Name = "GETTABLE",
- Special = {
- Type = "TableIndex",
- Table = regs[current.b],
- Index = {
- Type = "Number",
- Value = current.c
- }
- }
- }, {
- Type = "TableIndex",
- Table = regs[current.b],
- Index ={
- Type = "Number",
- Value = current.c
- }
- }, current.a)
- elseif current.name == "SETTABLEN" then
- table.insert(instructions, {
- Name = "SETTABLEN",
- ArgA = regs[current.a],
- ArgB = regs[current.b],
- ArgC = {
- Type = "Number",
- Value = current.c
- }
- })
- elseif current.name == "NEWCLOSURE" then
- local _proto = bytecode.protos[proto.child_protos[current.d + 1] + 1]
- local params: { Local } = {}
- local val: Closure = {
- Type = "Closure",
- Value = {},
- Params = params,
- IsVarArg = _proto.isvararg,
- Name = _proto.name or "",
- UpValues = upvalues,
- IsGlobal = false,
- ParamTypes = _proto.types.data
- }
- Variable({
- Name = "NEWCLOSURE",
- ArgD = val
- }, val, current.a, _proto.name ~= nil)
- local upvals: { Local } = GetUpvalues()
- local regs_params: { [number]: Value } = {}
- for i = 1, _proto.numparams do
- table.insert(params, {
- Type = "Local",
- Id = "",
- Register = i - 1,
- Value = { Type = "Nil" },
- Latest = { Type = "Nil" },
- Scope = proto
- })
- regs_params[i - 1] = params[#params]
- end
- val.Value = RefcountProto(_proto, upvals, regs_params)
- if proto.instructions[idx + 1].name == "SETGLOBAL" then
- local ins: luau_instruction = proto.instructions[idx + 1]
- if ins.a == current.a then
- val.IsGlobal = true
- idx += 1
- end
- end
- elseif current.name == "NAMECALL" then
- local call_instr: luau_instruction = proto.instructions[idx + 1]
- idx += 1 -- Consume the call
- table.insert(instructions, { Name = "NOP" }) -- Add a NOP to balance out the missing CALL
- local args: { Value } = {}
- local retvals: { Local } = {}
- 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`
- table.insert(args, regs[i])
- end
- if call_instr.b == 0 then
- for i = call_instr.a + 2, call_instr.a + multret_start do
- table.insert(args, regs[i])
- end
- end
- for i = 1, call_instr.c - 1 do
- table.insert(retvals, {
- Type = "Local",
- Id = "",
- Register = call_instr.a + i - 1,
- Value = { Type = "Nil" },
- Latest = { Type = "Nil" },
- Scope = proto,
- CreatedByCall = true
- })
- table.insert(vars, retvals[#retvals])
- end
- if call_instr.c == 0 then
- multret_start = current.a
- regs[current.a] = {
- Type = "InlineNamecall",
- Args = args,
- Target = GetConstant(proto, current.aux :: number) :: String,
- Object = regs[current.b]
- }
- if not regs[call_instr.a] then
- regs[call_instr.a] = {
- Type = "Local",
- Register = call_instr.a,
- Value = regs[current.a - 1],
- Latest = regs[current.a - 1],
- Id = "",
- Scope = proto
- }
- table.insert(vars, regs[call_instr.a] :: Local)
- end
- end
- if call_instr.c ~= 0 then
- local emitted: boolean = true
- if call_instr.c == 2 then
- emitted = Variable({
- Name = "NAMECALL",
- Special = { args, retvals },
- ArgB = regs[current.b],
- ArgAux = GetConstant(proto, current.aux :: number)
- }, {
- Type = "InlineNamecall",
- Args = args,
- Target = GetConstant(proto, current.aux :: number) :: String,
- Object = regs[current.b]
- }, current.a)
- else
- table.insert(instructions, {
- Name = "NAMECALL",
- Special = { args, retvals },
- ArgB = regs[current.b],
- ArgAux = GetConstant(proto, current.aux :: number)
- })
- end
- if emitted then
- for i, retval in next, retvals do -- explained below
- regs[current.a + i - 1] = retval
- table.insert(vars, regs[current.a + i - 1] :: Local)
- end
- end
- end
- elseif current.name == "CALL" then
- local args: { Value } = {}
- local retvals: { Local } = {}
- for i = current.a + 1, current.a + current.b - 1 do
- table.insert(args, regs[i])
- end
- if current.b == 0 then
- for i = current.a + 1, multret_start do
- table.insert(args, regs[i])
- end
- end
- for i = 1, current.c - 1 do
- table.insert(retvals, {
- Type = "Local",
- Id = "",
- Register = current.a + i - 1,
- Value = { Type = "Nil" },
- Latest = { Type = "Nil" },
- Scope = proto,
- CreatedByCall = true
- })
- end
- local emitted: boolean = true
- local target = regs[current.a]
- if current.c > 0 then
- if current.c == 2 then
- emitted = Variable({
- Name = "CALL",
- ArgA = target,
- Special = { args, retvals },
- }, {
- Type = "InlineCall",
- Args = args,
- Target = target
- }, current.a)
- if emitted then -- Set the ArgA back to the function target
- instructions[#instructions].ArgA = target
- end
- else
- table.insert(instructions, {
- Name = "CALL",
- ArgA = target,
- Special = { args, retvals },
- })
- end
- else
- table.insert(instructions, { -- Create a NOP as an inline call will be emitted by a MULTRET instr
- Name = "NOP"
- })
- multret_start = current.a
- regs[current.a] = {
- Type = "InlineCall",
- Args = args,
- Target = regs[current.a]
- }
- end
- if emitted then
- 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
- regs[current.a + i - 1] = retval
- table.insert(vars, regs[current.a + i - 1] :: Local)
- end
- end
- elseif current.name == "RETURN" then
- local values: { Value } = {}
- for i = 1, current.b - 1 do
- table.insert(values, regs[current.a + i - 1])
- end
- if current.b == 0 then
- for i = current.a, multret_start do
- table.insert(values, regs[i])
- end
- end
- table.insert(instructions, {
- Name = "RETURN",
- Special = values
- })
- elseif current.name == "JUMP" then
- CloneRegs(current.d)
- table.insert(instructions, {
- Name = "JUMP",
- ArgD = {
- Type = "Number",
- Value = current.d
- }
- })
- elseif current.name == "JUMPBACK" then
- table.insert(instructions, {
- Name = "JUMPBACK",
- ArgD = {
- Type = "Number",
- Value = current.d
- }
- })
- elseif current.name == "JUMPIF" then
- CloneRegs(current.d)
- table.insert(instructions, {
- Name = "JUMPIF",
- ArgA = regs[current.a],
- ArgD = {
- Type = "Number",
- Value = current.d
- }
- })
- elseif current.name == "JUMPIFNOT" then
- CloneRegs(current.d)
- table.insert(instructions, {
- Name = "JUMPIFNOT",
- ArgA = regs[current.a],
- ArgD = {
- Type = "Number",
- Value = current.d
- }
- })
- elseif table.find({"JUMPIFEQ", "JUMPIFLE", "JUMPIFLT", "JUMPIFNOTEQ", "JUMPIFNOTLE", "JUMPIFNOTLT"}, current.name) then
- CloneRegs(current.d)
- table.insert(instructions, {
- Name = current.name,
- ArgA = regs[current.a],
- ArgD = {
- Type = "Number",
- Value = current.d
- },
- ArgAux = regs[current.aux :: number]
- })
- elseif table.find({"ADD", "SUB", "MUL", "DIV", "MOD", "POW"}, current.name) then
- Variable({
- Name = current.name,
- Special = {
- Type = "Expression",
- Op = ({ADD="+", SUB="-", MUL="*", DIV="/", MOD="%", POW="^"})[current.name],
- Lhs = regs[current.b],
- Rhs = regs[current.c]
- }
- }, {
- Type = "Expression",
- Op = ({ADD="+", SUB="-", MUL="*", DIV="/", MOD="%", POW="^"})[current.name],
- Lhs = regs[current.b],
- Rhs = regs[current.c]
- }, current.a)
- elseif table.find({"ADDK", "SUBK", "MULK", "DIVK", "MODK", "POWK"}, current.name) then
- Variable({
- Name = current.name,
- Special = {
- Type = "Expression",
- Op = ({ADDK="+", SUBK="-", MULK="*", DIVK="/", MODK="%", POWK="^"})[current.name],
- Lhs = regs[current.b],
- Rhs = GetConstant(proto, current.c)
- }
- }, {
- Type = "Expression",
- Op = ({ADDK="+", SUBK="-", MULK="*", DIVK="/", MODK="%", POWK="^"})[current.name],
- Lhs = regs[current.b],
- Rhs = GetConstant(proto, current.c)
- }, current.a)
- elseif table.find({"AND", "OR"}, current.name) then
- Variable({
- Name = current.name,
- Special = {
- Type = "Expression",
- Op = ({AND="and", OR="or"})[current.name],
- Lhs = regs[current.b],
- Rhs = regs[current.c]
- }
- }, {
- Type = "Expression",
- Op = ({AND="and", OR="or"})[current.name],
- Lhs = regs[current.b],
- Rhs = regs[current.c]
- }, current.a)
- elseif table.find({"ANDK", "ORK"}, current.name) then
- Variable({
- Name = current.name,
- Special = {
- Type = "Expression",
- Op = ({ANDK="and", ORK="or"})[current.name],
- Lhs = regs[current.b],
- Rhs = regs[current.c]
- }
- }, {
- Type = "Expression",
- Op = ({ANDK="and", ORK="or"})[current.name],
- Lhs = regs[current.b],
- Rhs = regs[current.c]
- }, current.a)
- elseif table.find({"NOT", "MINUS", "LENGTH"}, current.name) then
- Variable({
- Name = current.name,
- Special = {
- Type = "Unary",
- Op = ({NOT="not ", MINUS="-", LENGTH="#"})[current.name],
- Value = regs[current.b]
- }
- }, {
- Type = "Unary",
- Op = ({NOT="not ", MINUS="-", LENGTH="#"})[current.name],
- Value = regs[current.b]
- }, current.a)
- elseif current.name == "CONCAT" then
- local values: { Value } = {}
- for i = current.b, current.c do
- table.insert(values, regs[i])
- end
- Variable({
- Name = "CONCAT",
- Special = {
- Type = "Strings",
- Value = values
- }
- }, {
- Type = "Strings",
- Value = values
- }, current.a)
- elseif current.name == "NEWTABLE" then -- Actual table is defined in SETLIST
- if not current.aux or current.aux == 0 or regs[current.a] then
- Variable({
- Name = "NEWTABLE",
- Special = {
- Type = "Table",
- Value = {}
- },
- ArgAux = {
- Type = "Number",
- Value = current.aux :: number
- }
- }, {
- Type = "Table",
- Value = {}
- }, current.a)
- end
- elseif current.name == "DUPTABLE" then
- if regs[current.a] and regs[current.a].Type == "Local" then
- table.insert(instructions, {
- Name = "DUPTABLE",
- ArgA = regs[current.a],
- ArgD = GetConstant(proto, current.d)
- })
- else
- Variable({
- Name = "DUPTABLE",
- ArgD = GetConstant(proto, current.d)
- }, GetConstant(proto, current.d), current.a)
- end
- elseif current.name == "SETLIST" then
- local values: { Value } = {}
- for i = 1, current.c - 1 do
- table.insert(values, regs[current.b + i - 1])
- end
- if current.c == 0 then -- MULTRET
- for i = current.b, multret_start do
- table.insert(values, regs[i])
- end
- end
- Variable({ -- target is passed to SETLIST too so realisically NEWTABLE is useless?
- Name = "SETLIST",
- ArgAux = {
- Type = "Number",
- Value = current.aux :: number
- },
- Special = {
- Type = "Table",
- Value = values
- }
- }, {
- Type = "Table",
- Value = values
- }, current.a)
- elseif current.name == "FORNPREP" then
- CloneRegs(current.d)
- local limit, step, index = regs[current.a], regs[current.a + 1], regs[current.a + 2]
- local iter: Local = {
- Type = "Local",
- Id = "",
- Latest = index,
- Value = index,
- Register = current.a + 2,
- Scope = proto
- }
- table.insert(instructions, {
- Name = "FORNPREP",
- ArgA = regs[current.a + 3],
- ArgD = {
- Type = "Number",
- Value = current.d
- },
- Special = {
- index, limit, step, iter -- Arranged in the way you'd naturally write them (1, 10, 2 as index=1, limit=10, step=2)
- }
- })
- regs[current.a + 2] = iter
- table.insert(vars, regs[current.a + 2] :: Local)
- elseif table.find({"FORGPREP", "FORGPREP_NEXT", "FORGPREP_INEXT"}, current.name) then
- CloneRegs(current.d)
- local generator, state, index = regs[current.a], regs[current.a + 1], regs[current.a + 2] -- Index is pretty much always a NIL I think
- local variables: { Local } = {}
- local for_end: luau_instruction
- local l = 1
- local offset = 0
- local naux = 0
- for i = idx + 1, #proto.instructions do
- if proto.instructions[i].aux ~= nil then
- naux += 1
- end
- if table.find({"FORGPREP", "FORGPREP_NEXT", "FORGPREP_INEXT"}, proto.instructions[i].name) then
- l += 1
- end
- if proto.instructions[i].name == "FORGLOOP" then
- l -= 1
- for_end = proto.instructions[i]
- offset = i - idx -- + naux
- end
- if l == 0 then
- break
- end
- end
- for i = 1, bit32.band(for_end.aux :: number, 0b11111111) do
- regs[current.a + 2 + i] = {
- Type = "Local",
- Id = "",
- Latest = index,
- Value = index,
- Register = current.a + 2 + i,
- Scope = proto
- }
- table.insert(variables, regs[current.a + 2 + i] :: Local)
- table.insert(vars, regs[current.a + 2 + i] :: Local)
- end
- table.insert(instructions, {
- Name = "FORGPREP",
- ArgD = {
- Type = "Number",
- Value = offset -- Pass a custom offset here as it is the correct one
- },
- Special = {
- variables, -- for somereason, the LSP I use hates when this is at the end of specials?????
- generator, -- ^^ I assume it was tryin to cast Special into a { ValueNode } ?
- state,
- index
- }
- })
- elseif current.name == "GETVARARGS" then
- multret_start = current.a
- Variable({
- Name = "GETVARARGS",
- Special = {
- Type = "VarArgs"
- }
- }, {
- Type = "VarArgs"
- }, current.a)
- 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 ????
- local _proto = bytecode.protos[proto.constants[current.d + 1].value :: number + 1]
- local params: { Local } = {}
- local val: Closure = {
- Type = "Closure",
- Value = {},
- Params = params,
- IsVarArg = _proto.isvararg,
- Name = _proto.name or "",
- UpValues = upvalues,
- IsGlobal = false,
- ParamTypes = _proto.types.data
- }
- Variable({
- Name = "NEWCLOSURE",
- ArgD = val
- }, val, current.a, _proto.name ~= nil)
- local upvals: { Local } = GetUpvalues()
- local regs_params: { [number]: Value } = {}
- for i = 1, _proto.numparams do
- table.insert(params, {
- Type = "Local",
- Id = "",
- Register = i - 1,
- Value = { Type = "Nil" },
- Latest = { Type = "Nil" },
- Scope = proto
- })
- regs_params[i - 1] = params[#params]
- end
- val.Value = RefcountProto(_proto, upvals, regs_params)
- elseif current.name == "LOADKX" then
- Variable({
- Name = "LOADKX",
- ArgAux = GetConstant(proto, current.aux :: number)
- }, GetConstant(proto, current.aux :: number), current.a)
- elseif current.name == "JUMPX" then
- table.insert(instructions, {
- Name = "JUMPX",
- ArgE = {
- Type = "Number",
- Value = current.e
- }
- })
- elseif current.name == "FASTCALL" then
- local following_call: luau_instruction = proto.instructions[idx + current.c]
- local args: { Value } = {}
- local retvals: { Local } = {}
- for i = following_call.a + 1, following_call.a + following_call.b - 1 do
- table.insert(args, regs[i])
- end
- if following_call.b == 0 then
- for i = following_call.a + 1, multret_start do
- table.insert(args, regs[i])
- end
- end
- for i = 1, following_call.c - 1 do
- table.insert(retvals, {
- Type = "Local",
- Id = "",
- Register = following_call.a + i - 1,
- Value = { Type = "Nil" },
- Latest = { Type = "Nil" },
- Scope = proto,
- CreatedByCall = true
- })
- end
- local emitted: boolean = true
- if following_call.c == 0 then
- multret_start = following_call.a
- regs[following_call.a] = {
- Type = "InlineCall",
- Args = args,
- Target = {
- Type = "Number",
- Value = current.a
- }
- }
- table.insert(instructions, {
- Name = "NOP"
- })
- elseif following_call.c == 2 then
- emitted = Variable({
- Name = "CALL",
- ArgA = {
- Type = "Number",
- Value = current.a
- },
- Special = { args, retvals },
- ArgC = {
- Type = "Number",
- Value = following_call.c
- }
- }, {
- Type = "InlineCall",
- Args = args,
- Target = {
- Type = "Number",
- Value = current.a
- }
- }, following_call.a)
- if emitted then -- Set the ArgA back to the function target
- instructions[#instructions].ArgA = {
- Type = "Number",
- Value = current.a
- }
- end
- else
- table.insert(instructions, {
- Name = "CALL",
- ArgA = { -- NOTE: Decompiler should detect that thsi is a number and convert it to a builtin call
- Type = "Number",
- Value = current.a
- },
- Special = { args, retvals },
- ArgC = {
- Type = "Number",
- Value = following_call.c
- }
- })
- end
- if emitted then
- for i, retval in next, retvals do
- regs[following_call.a + i - 1] = retval
- table.insert(vars, regs[following_call.a + i - 1] :: Local)
- end
- end
- idx += current.c -- Skip GETUPVAL/MOVE/GETIMPORT + CALL
- elseif current.name == "FASTCALL1" then
- local following_call: luau_instruction = proto.instructions[idx + current.c]
- local args: { Value } = { regs[current.b] }
- local retvals: { Local } = {}
- for i = 1, following_call.c - 1 do
- table.insert(retvals, {
- Type = "Local",
- Id = "",
- Register = following_call.a + i - 1,
- Value = { Type = "Nil" },
- Latest = { Type = "Nil" },
- Scope = proto,
- CreatedByCall = true
- })
- end
- local emitted: boolean = true
- if following_call.c == 0 then
- multret_start = following_call.a
- regs[following_call.a] = {
- Type = "InlineCall",
- Args = args,
- Target = {
- Type = "Number",
- Value = current.a
- }
- }
- table.insert(instructions, {
- Name = "NOP"
- })
- elseif following_call.c == 2 then
- emitted = Variable({
- Name = "CALL",
- ArgA = {
- Type = "Number",
- Value = current.a
- },
- Special = { args, retvals },
- ArgC = {
- Type = "Number",
- Value = following_call.c
- }
- }, {
- Type = "InlineCall",
- Args = args,
- Target = {
- Type = "Number",
- Value = current.a
- }
- }, following_call.a)
- if emitted then -- Set the ArgA back to the function target
- instructions[#instructions].ArgA = {
- Type = "Number",
- Value = current.a
- }
- end
- else
- table.insert(instructions, {
- Name = "CALL",
- ArgA = { -- NOTE: Decompiler should detect that thsi is a number and convert it to a builtin call
- Type = "Number",
- Value = current.a
- },
- Special = {args, retvals},
- ArgC = {
- Type = "Number",
- Value = following_call.c
- }
- })
- end
- if emitted then
- for i, retval in next, retvals do
- regs[following_call.a + i - 1] = retval
- table.insert(vars, regs[following_call.a + i - 1] :: Local)
- end
- end
- idx += current.c -- Skip GETUPVAL/MOVE/GETIMPORT + CALL
- elseif current.name == "FASTCALL2" then
- local following_call: luau_instruction = proto.instructions[idx + current.c]
- local retvals: { Local } = {}
- for i = 1, following_call.c - 1 do
- table.insert(retvals, {
- Type = "Local",
- Id = "",
- Register = following_call.a + i - 1,
- Value = { Type = "Nil" },
- Latest = { Type = "Nil" },
- Scope = proto,
- CreatedByCall = true
- })
- end
- local emitted: boolean = true
- if following_call.c == 0 then
- multret_start = following_call.a
- regs[following_call.a] = {
- Type = "InlineCall",
- Args = {regs[current.b], regs[current.aux :: number]},
- Target = {
- Type = "Number",
- Value = current.a
- }
- }
- table.insert(instructions, {
- Name = "NOP"
- })
- elseif following_call.c == 2 then
- emitted = Variable({
- Name = "CALL",
- ArgA = {
- Type = "Number",
- Value = current.a
- },
- Special = { {regs[current.b], regs[current.aux :: number]}, retvals },
- ArgC = {
- Type = "Number",
- Value = following_call.c
- }
- }, {
- Type = "InlineCall",
- Args = {regs[current.b], regs[current.aux :: number]},
- Target = {
- Type = "Number",
- Value = current.a
- }
- }, following_call.a)
- if emitted then -- Set the ArgA back to the function target
- instructions[#instructions].ArgA = {
- Type = "Number",
- Value = current.a
- }
- end
- else
- table.insert(instructions, {
- Name = "CALL",
- ArgA = { -- NOTE: Decompiler should detect that thsi is a number and convert it to a builtin call
- Type = "Number",
- Value = current.a
- },
- Special = {{regs[current.b], regs[current.aux :: number]}, retvals},
- ArgC = {
- Type = "Number",
- Value = following_call.c
- }
- })
- end
- if emitted then
- for i, retval in next, retvals do
- regs[following_call.a + i - 1] = retval
- table.insert(vars, regs[following_call.a + i - 1] :: Local)
- end
- end
- idx += current.c -- Skip GETUPVAL/MOVE/GETIMPORT + CALL
- elseif current.name == "FASTCALL2K" then
- local following_call: luau_instruction = proto.instructions[idx + current.c - 1]
- local retvals: { Local } = {}
- for i = 1, following_call.c - 1 do
- table.insert(retvals, {
- Type = "Local",
- Id = "",
- Register = following_call.a + i - 1,
- Value = { Type = "Nil" },
- Latest = { Type = "Nil" },
- Scope = proto,
- CreatedByCall = true
- })
- end
- local emitted: boolean = true
- if following_call.c == 0 then
- multret_start = following_call.a
- regs[following_call.a] = {
- Type = "InlineCall",
- Args = {regs[current.b], GetConstant(proto, current.aux :: number)},
- Target = {
- Type = "Number",
- Value = current.a
- }
- }
- table.insert(instructions, {
- Name = "NOP"
- })
- elseif following_call.c == 2 then
- emitted = Variable({
- Name = "CALL",
- ArgA = {
- Type = "Number",
- Value = current.a
- },
- Special = { {regs[current.b], regs[current.aux :: number]}, retvals },
- ArgC = {
- Type = "Number",
- Value = following_call.c
- }
- }, {
- Type = "InlineCall",
- Args = {regs[current.b], regs[current.aux :: number]},
- Target = {
- Type = "Number",
- Value = current.a
- }
- }, following_call.a)
- if emitted then -- Set the ArgA back to the function target
- instructions[#instructions].ArgA = {
- Type = "Number",
- Value = current.a
- }
- end
- else
- table.insert(instructions, {
- Name = "CALL",
- ArgA = { -- NOTE: Decompiler should detect that thsi is a number and convert it to a builtin call
- Type = "Number",
- Value = current.a
- },
- Special = {{regs[current.b], GetConstant(proto, current.aux :: number)}, retvals},
- ArgC = {
- Type = "Number",
- Value = following_call.c
- }
- })
- end
- if emitted then
- for i, retval in next, retvals do
- regs[following_call.a + i - 1] = retval
- table.insert(vars, regs[following_call.a + i - 1] :: Local)
- end
- end
- idx += current.c - 1 -- Not sure why we subtract one here, the docs don't mention it
- elseif table.find({"JUMPXEQKNIL", "JUMPXEQKB"}, current.name) then
- table.insert(instructions, {
- Name = current.name,
- ArgA = regs[current.a],
- ArgD = {
- Type = "Number",
- Value = current.d
- },
- Special = {
- Type = "Expression",
- Lhs = regs[current.a],
- Op = bit32.band(current.aux :: number, 2147483648) and "~=" or "==",
- Rhs = current.name == "JUMPXEQKNIL" and { Type = "Nil" } :: Nil or {
- Type = "Boolean",
- Value = bit32.band(current.aux :: number, 1) ~= 0
- } :: Boolean
- } :: Expression
- })
- elseif table.find({"JUMPXEQKN", "JUMPXEQKS"}, current.name) then
- table.insert(instructions, {
- Name = current.name,
- ArgD = {
- Type = "Number",
- Value = current.d
- },
- Special = {
- Type = "Expression",
- Lhs = regs[current.a],
- Op = bit32.band(current.aux :: number, 2147483648) and "~=" or "==",
- Rhs = GetConstant(proto, bit32.band(current.aux :: number, 16777215))
- } :: Expression
- })
- elseif current.name == "IDIV" then
- Variable({
- Name = "IDIV",
- Special = {
- Type = "Expression",
- Op = "//",
- Lhs = regs[current.b],
- Rhs = regs[current.c]
- }
- }, {
- Type = "Expression",
- Op = "//",
- Lhs = regs[current.b],
- Rhs = regs[current.c]
- }, current.a)
- elseif current.name == "IDIVK" then
- Variable({
- Name = "IDIVK",
- Special = {
- Type = "Expression",
- Op = "//",
- Lhs = regs[current.b],
- Rhs = GetConstant(proto, current.c)
- }
- }, {
- Type = "Expression",
- Op = "//",
- Lhs = regs[current.b],
- Rhs = GetConstant(proto, current.c)
- }, current.a)
- else -- Here will mostly be FORNLOOP, FORGLOOP, and VARARG stuff. None of which we can recover any meaningful data from
- table.insert(instructions, {
- Name = "NOP"
- })
- end
- 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
- table.insert(instructions, {
- Name = "NOP"
- })
- end
- idx += 1
- end
- return instructions
- end
- local function DecompileClosure(closure: Closure): ClosureNode
- local idx = 1
- local node: ClosureNode = {
- Type = "ClosureNode",
- Name = closure.Name,
- Params = closure.Params,
- IsVarArg = closure.IsVarArg,
- Body = {},
- ParamTypes = closure.ParamTypes
- }
- local defined_locals: { Local } = {}
- local function DecompileValue(value: Value?): ValueNode
- if not value then
- return { Type = "NilNode" } -- Should never happen, this is mostly to satisfy the static analyzer
- end
- if value.Type == "Boolean" then
- return {
- Type = "BooleanNode",
- Value = value.Value
- }
- elseif value.Type == "Number" then
- return {
- Type = "NumberNode",
- Value = value.Value
- }
- elseif value.Type == "Nil" then
- return {
- Type = "NilNode"
- }
- elseif value.Type == "Expression" then
- return {
- Type = "ExpressionNode",
- Lhs = DecompileValue(value.Lhs),
- Rhs = DecompileValue(value.Rhs),
- Op = value.Op
- }
- elseif value.Type == "Unary" then
- return {
- Type = "UnaryNode",
- Value = DecompileValue(value.Value),
- Op = value.Op
- }
- elseif value.Type == "Local" then
- return value -- Here we just pass the local as a pseudo-node because I am lazy and it doesn't matter
- elseif value.Type == "String" then
- return {
- Type = "StringNode",
- Value = value.Value
- }
- elseif value.Type == "Strings" then
- local values: { ValueNode } = {}
- for _, value in value.Value do
- table.insert(values, DecompileValue(value))
- end
- return {
- Type = "StringsNode",
- Value = values
- }
- elseif value.Type == "Global" then
- return {
- Type = "GlobalNode",
- Value = value.Value
- }
- elseif value.Type == "Closure" then
- return DecompileClosure(value)
- elseif value.Type == "TableIndex" then
- return {
- Type = "TableIndexNode",
- Table = DecompileValue(value.Table),
- Index = DecompileValue(value.Index)
- }
- elseif value.Type == "Table" then
- local values: { ValueNode } = {}
- for _, v in next, value.Value do
- table.insert(values, DecompileValue(v))
- end
- return {
- Type = "TableNode",
- Body = values
- }
- elseif value.Type == "VarArgs" then
- return {
- Type = "VarArgsNode"
- }
- elseif value.Type == "InlineNamecall" then
- local args: { ValueNode } = {}
- for _, arg in next, value.Args do
- table.insert(args, DecompileValue(arg))
- end
- return {
- Type = "InlineNamecallNode",
- Args = args,
- Object = DecompileValue(value.Object),
- Target = DecompileValue(value.Target) :: StringNode
- }
- elseif value.Type == "InlineCall" then
- local args: { ValueNode } = {}
- for _, arg in next, value.Args do
- table.insert(args, DecompileValue(arg))
- end
- local target: ValueNode = DecompileValue(value.Target)
- if value.Target.Type == "Number" then
- target = ConvertBuiltin(value.Target.Value)
- end
- return {
- Type = "InlineCallNode",
- Args = args,
- Target = target
- }
- end
- return {
- Type = "NilNode" -- TODO: add remainding nodes.
- }
- end
- local function Define(_local: Local, value: ValueNode): VarAssignNode | VarReassignNode
- if table.find(defined_locals, _local) then
- return {
- Type = "VarReassignNode",
- Target = _local,
- Value = value
- }
- end
- table.insert(defined_locals, _local)
- return {
- Type = "VarAssignNode",
- Target = _local,
- Value = value
- }
- end
- local function BreaksScope(instructions: number): boolean -- Checks if a JUMP* instruction would continue the scope of a loop
- return false -- table.find({"JUMPBACK", "FORNLOOP", "FORGLOOP"}, closure.Value[idx + instructions - 2].Name) ~= nil
- end
- local function ContinuesScope(instructions: number): boolean -- Checks if a JUMP* instruction would continue the scope of a loop
- return false -- table.find({"JUMPBACK", "FORNLOOP", "FORGLOOP"}, closure.Value[idx + instructions - 3].Name) ~= nil
- end
- local function DecompileInstruction(instruction: Instruction): Node?
- if instruction.Name == "LOADNIL" then
- return (Define(instruction.ArgA :: Local, DecompileValue(instruction.ArgB)))
- elseif instruction.Name == "LOADN" then
- return (Define(instruction.ArgA :: Local, DecompileValue(instruction.ArgB)))
- elseif instruction.Name == "LOADB" then
- return (Define(instruction.ArgA :: Local, DecompileValue(instruction.ArgB)))
- elseif instruction.Name == "LOADK" then
- return (Define(instruction.ArgA :: Local, DecompileValue(instruction.ArgB)))
- elseif instruction.Name == "MOVE" then
- return (Define(instruction.ArgA :: Local, DecompileValue(instruction.ArgB)))
- elseif instruction.Name == "GETGLOBAL" then
- return (Define(instruction.ArgA :: Local, DecompileValue(instruction.ArgAux)))
- elseif instruction.Name == "SETGLOBAL" then
- return ({
- Type = "GlobalDefinitionNode",
- Target = (instruction.ArgAux :: String).Value,
- Value = DecompileValue(instruction.ArgA)
- })
- elseif instruction.Name == "GETUPVAL" then
- return (Define(instruction.ArgA :: Local, DecompileValue(instruction.ArgB)))
- elseif instruction.Name == "SETUPVAL" then
- return ({
- Type = "VarReassignNode",
- Target = instruction.ArgB :: Local, -- We know that this is a Local as an Upvalue CANNOT be omitted.
- Value = DecompileValue(instruction.ArgA)
- })
- elseif instruction.Name == "NEWCLOSURE" then
- if (instruction.ArgD :: Closure).IsGlobal then
- return ({
- Type = "GlobalDefinitionNode",
- Target = "", -- Name is provided by the closure passed in Value
- Value = DecompileValue(instruction.ArgD)
- })
- end
- return (Define(instruction.ArgA :: Local, DecompileValue(instruction.ArgD)))
- elseif instruction.Name == "CALL" then
- local args: { ValueNode } = {}
- for _, arg in next, instruction.Special[1] do
- table.insert(args, DecompileValue(arg))
- end
- for _, var in next, instruction.Special[2] :: { Local } do
- table.insert(defined_locals, var)
- end
- local target: ValueNode = DecompileValue(instruction.ArgA)
- if (instruction.ArgA :: Value).Type == "Number" then -- Builtin call
- target = ConvertBuiltin((instruction.ArgA :: Number).Value)
- end
- return ( {
- Type = "FunctionCallNode",
- Args = args,
- RetVals = instruction.Special[2],
- Target = target
- })
- elseif table.find({"ADD", "SUB", "MUL", "DIV", "MOD", "POW", "ADDK", "SUBK", "MULK", "DIVK", "MODK", "POWK", "IDIV", "IDIVK", "AND", "OR", "ANDK", "ORK"}, instruction.Name) then
- return (Define(instruction.ArgA :: Local, DecompileValue(instruction.Special)))
- elseif instruction.Name == "RETURN" then
- local values: { ValueNode } = {}
- for _, value in next, instruction.Special do
- table.insert(values, DecompileValue(value))
- end
- return ( {
- Type = "ReturnNode",
- Values = values
- })
- elseif instruction.Name == "GETTABLE" then
- return (Define(instruction.ArgA :: Local, DecompileValue(instruction.Special)))
- elseif instruction.Name == "SETTABLE" then
- return ({
- Type = "TableAssignNode",
- Table = DecompileValue(instruction.ArgB),
- Index = DecompileValue(instruction.ArgC),
- Source = DecompileValue(instruction.ArgA),
- })
- elseif instruction.Name == "NAMECALL" then
- local args: { ValueNode } = {}
- for _, arg in next, instruction.Special[1] do
- table.insert(args, DecompileValue(arg))
- end
- for _, var in next, instruction.Special[2] :: { Local } do
- table.insert(defined_locals, var)
- end
- return ({
- Type = "NamecallNode",
- Args = args,
- RetVals = instruction.Special[2],
- Object = DecompileValue(instruction.ArgB),
- Target = DecompileValue(instruction.ArgAux) :: StringNode
- })
- elseif instruction.Name == "JUMP" then
- if BreaksScope((instruction.ArgD :: Number).Value) then -- Explicit cast; TODO: add error checking
- return ({
- Type = "BreakNode"
- })
- elseif ContinuesScope((instruction.ArgD :: Number).Value) then
- return ({
- Type = "ContinueNode"
- })
- end
- elseif table.find({"JUMPIF", "JUMPIFNOT", "JUMPIFEQ", "JUMPIFLE", "JUMPIFLT", "JUMPIFNOTEQ", "JUMPIFNOTLE", "JUMPIFNOTLT", "JUMPXEQKNIL", "JUMPXEQKB", "JUMPXEQKN", "JUMPXEQKS"}, instruction.Name) then
- local opposites = {
- JUMPIFEQ = "~=",
- JUMPIFNOTEQ = "==",
- JUMPIFLT = ">",
- JUMPIFLE = ">=",
- JUMPIFNOTLE = "<=",
- JUMPIFNOTLT = "<",
- }
- local operators = {
- JUMPIFEQ = "==",
- JUMPIFNOTEQ = "~=",
- JUMPIFLT = "<",
- JUMPIFLE = "<=",
- JUMPIFNOTLE = ">=",
- JUMPIFNOTLT = ">",
- }
- local flipped = { -- This is not useful right now, but in the future this will be used to allow
- -- users to decide whether they want the variable on the left or right side of
- -- a given expression:
- -- `100 < v0` -> `v0 > 100`
- -- ^^ This is done by the Luau compiler as an optimization because the JUMPIFL*
- -- instructions are faster to compute.
- ["=="] = "~=",
- ["<"] = ">",
- ["<="] = ">=",
- [">"] = "<",
- [">="] = "<=",
- }
- local function GetSingularCondition(_instr: Instruction, flip: boolean): ValueNode
- if _instr.Name == "JUMPIF" then
- local cond = DecompileValue(_instr.ArgA)
- if flip then
- cond = {
- Type = "UnaryNode",
- Value = cond,
- Op = "not "
- }
- end
- return cond
- elseif _instr.Name == "JUMPIFNOT" then
- local cond = DecompileValue(_instr.ArgA)
- if not flip then
- cond = {
- Type = "UnaryNode",
- Value = cond,
- Op = "not "
- }
- end
- return cond
- elseif table.find({"JUMPIFEQ", "JUMPIFLE", "JUMPIFLT", "JUMPIFNOTEQ", "JUMPIFNOTLE", "JUMPIFNOTLT"}, _instr.Name) then
- return {
- Type = "ExpressionNode",
- Lhs = DecompileValue(_instr.ArgA),
- Op = (flip and opposites or operators)[_instr.Name],
- Rhs = DecompileValue(_instr.ArgAux)
- }
- else
- return DecompileValue(_instr.Special)
- end
- end
- local condition: ValueNode = GetSingularCondition(instruction, true)
- local body: { Node } = {}
- local elsebody: { Node } = {}
- local body_idx: number = idx
- local body_end_idx: number = idx + (instruction.ArgD :: Number).Value
- 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
- if BreaksScope(body_end_idx) then -- Explicit cast; TODO: add error checking
- table.insert(body, {
- Type = "BreakNode"
- })
- elseif ContinuesScope(body_end_idx) then
- table.insert(body, {
- Type = "ContinueNode"
- })
- else
- idx = body_idx + 1
- local did_jump = false
- while idx < body_end_idx + 1 do
- if closure.Value[idx] and closure.Value[idx].Name == "JUMP" and idx == body_end_idx then
- did_jump = true
- local _end_idx = idx + (closure.Value[idx].ArgD :: Number).Value
- while idx < _end_idx do
- idx += 1
- local _node: Node? = DecompileInstruction(closure.Value[idx])
- if _node then
- table.insert(elsebody, _node)
- end
- end
- break
- end
- local _node: Node? = closure.Value[idx] and DecompileInstruction(closure.Value[idx]) or nil
- if _node then
- table.insert(body, _node)
- end
- idx += 1
- end
- if not did_jump then
- idx -= 1
- end
- end
- if is_while then
- return ({
- Type = "WhileNode",
- Condition = condition,
- Body = body
- })
- end
- return ({
- Type = "IfNode",
- Body = body,
- ElseBody = elsebody,
- Condition = condition
- })
- elseif table.find({"NOT", "MINUS", "LENGTH"}, instruction.Name) then
- return (Define(instruction.ArgA :: Local, DecompileValue(instruction.Special)))
- elseif instruction.Name == "CONCAT" then
- return (Define(instruction.ArgA :: Local, DecompileValue(instruction.Special)))
- elseif instruction.Name == "NEWTABLE" then
- return (Define(instruction.ArgA :: Local, {
- Type = "TableNode",
- Body = {} -- NEWTABLE is only emitted when the table is empty, SETTABLE has body data
- }))
- elseif instruction.Name == "SETLIST" then
- local values: { ValueNode } = {}
- for _, value in next, instruction.Special.Value do
- table.insert(values, DecompileValue(value))
- end
- return (Define(instruction.ArgA :: Local, {
- Type = "TableNode",
- Body = values
- }))
- elseif instruction.Name == "DUPTABLE" then
- return (Define(instruction.ArgA :: Local, {
- Type = "TableNode",
- Body = {} -- DUPTABLE uses a "template" which idrk what is so idc (I assume it has something to do with table size)
- }))
- elseif instruction.Name == "FORNPREP" then
- local body: { Node } = {}
- local end_idx = idx + (instruction.ArgD :: Number).Value
- while idx < end_idx do
- idx += 1
- local node: Node? = DecompileInstruction(closure.Value[idx])
- if node then
- table.insert(body, node)
- end
- end
- return ({
- Type = "ForRangeNode",
- Index = DecompileValue(instruction.Special[1]),
- Limit = DecompileValue(instruction.Special[2]),
- Step = DecompileValue(instruction.Special[3]),
- Iterator = instruction.Special[4],
- Body = body
- })
- elseif instruction.Name == "FORGPREP" then
- local body: { Node } = {}
- local end_idx = idx + (instruction.ArgD :: Number).Value
- while idx <= end_idx do
- idx += 1
- local node: Node? = DecompileInstruction(closure.Value[idx])
- if node then
- table.insert(body, node)
- end
- end
- return ({
- Type = "ForValueNode",
- Generator = DecompileValue(instruction.Special[2]),
- State = DecompileValue(instruction.Special[3]),
- Index = DecompileValue(instruction.Special[4]),
- Variables = instruction.Special[1],
- Body = body
- })
- elseif instruction.Name == "GETVARARGS" then
- return (Define(instruction.ArgA :: Local, {Type = "VarArgsNode"})) --easy to decompile lmfao
- elseif instruction.Name == "LOADKX" then
- return (Define(instruction.ArgA :: Local, DecompileValue(instruction.ArgAux)))
- elseif instruction.Name == "JUMPX" then
- if BreaksScope((instruction.ArgD :: Number).Value) then -- Explicit cast; TODO: add error checking
- return ({
- Type = "BreakNode"
- })
- elseif ContinuesScope((instruction.ArgD :: Number).Value) then
- return ({
- Type = "ContinueNode"
- })
- end
- -- uh oh
- elseif instruction.Name == "GETIMPORT" then
- return (Define(instruction.ArgA :: Local, DecompileValue(instruction.Special)))
- elseif instruction.Name == "NOP" or instruction.Name == "JUMPBACK" then
- -- Silence the UNHANDLED print for NOP and JUMPBACK as they don't need a handler
- else
- print(fmt("UNHANDLED: %s", instruction.Name))
- end
- return
- end
- while idx < #closure.Value + 1 do
- local instruction: Instruction = closure.Value[idx]
- local _node: Node? = DecompileInstruction(instruction)
- if _node then
- table.insert(node.Body, _node)
- end
- idx += 1
- end
- return node
- end
- local function ResolveType(_type: number): string
- local optional = bit32.band(_type, bit32.lshift(1, 7))
- local types = {
- [0] = "nil",
- [1] = "boolean",
- [2] = "number",
- [3] = "string",
- [4] = "{}",
- [5] = "__wdec_type_function",
- [6] = "__wdec_type_thread",
- [7] = "__wdec_type_userdata",
- [8] = "Vector3",
- [15] = "any",
- [256] = "__wdec_type_invalid"
- }
- return types[bit32.band(_type, 0b1111111)] .. optional
- end
- local cid = 0
- local pid = 0
- local function TranspileClosure(closure: ClosureNode, depth: number): string
- local transpiled_closure_src: string = ""
- local function Indent(depth): string
- return string.rep(" ", depth)
- end
- local function TranspileValue(value: ValueNode, depth: number, parent_expr: ExpressionNode?): string
- if value.Type == "BooleanNode" then
- return value.Value and "true" or "false"
- elseif value.Type == "ClosureNode" then -- If a closure is used as a value, it does NOT have a name
- local header: string = "function("
- for i, v in next, value.Params do
- header ..= fmt("p%d", pid)
- if #value.ParamTypes > 2 then
- header ..= ": " .. ResolveType(value.ParamTypes[i + 2])
- end
- if i < #value.Params then
- header ..= ", "
- end
- v.Id = fmt("p%d", pid)
- pid += 1
- end
- if value.IsVarArg then
- if #value.Params > 0 then
- header ..= ", "
- end
- header ..= "..."
- end
- header ..= ")\n"
- return header .. TranspileClosure(value, depth + 1) .. Indent(depth) .. "end"
- elseif value.Type == "ExpressionNode" then
- local operator_precedence = { -- TODO: Find out if these actually are correct (I think they are)
- ["or"] = 0,
- ["and"] = 1,
- ["=="] = 2,
- ["~="] = 2,
- ["<"] = 2,
- [">"] = 2,
- ["<="] = 2,
- [">="] = 2,
- ["+"] = 3,
- ["-"] = 3,
- ["*"] = 4,
- ["/"] = 4,
- ["//"] = 4,
- ["%"] = 4,
- ["^"] = 5,
- }
- local format: string = ""
- if parent_expr and operator_precedence[value.Op] < operator_precedence[parent_expr.Op] then
- format ..= "(%s"
- else
- format ..= "%s"
- end
- format ..= " %s "
- if parent_expr and operator_precedence[value.Op] < operator_precedence[parent_expr.Op] then
- format ..= "%s)"
- else
- format ..= "%s"
- end
- return fmt(format, TranspileValue(value.Lhs, depth, value), value.Op, TranspileValue(value.Rhs, depth, value))
- elseif value.Type == "Local" then
- return value.Id
- elseif value.Type == "NilNode" then
- return "nil"
- elseif value.Type == "NumberNode" then
- return tostring(value.Value)
- elseif value.Type == "StringNode" then -- Raw strings are not supported.
- local s, _ = string.gsub(fmt("\"%s\"", value.Value), "\n", "\\n")
- local newstring, _ = string.gsub(s, "\t", "\\t")
- return newstring
- elseif value.Type == "GlobalNode" then
- return value.Value
- elseif value.Type == "UnaryNode" then
- return fmt("%s%s", value.Op, TranspileValue(value.Value, depth))
- elseif value.Type == "VarArgsNode" then
- return "..."
- elseif value.Type == "TableIndexNode" then
- if value.Index.Type == "StringNode" and not string.find(value.Index.Value, " ") then
- return fmt("%s.%s", TranspileValue(value.Table, depth), value.Index.Value)
- end
- return fmt("%s[%s]", TranspileValue(value.Table, depth), TranspileValue(value.Index, depth))
- elseif value.Type == "StringsNode" then
- local cons: string = ""
- for i, _value in next, value.Value do
- cons ..= TranspileValue(_value, depth)
- if i < #value.Value then
- cons ..= " .. "
- end
- end
- return cons
- elseif value.Type == "TableNode" then -- TODO: Format tables nicer
- local cons: string = ""
- local values: { string } = {}
- local total_length: number = 0
- for i, _value in next, value.Body do
- table.insert(values, TranspileValue(_value, depth))
- total_length += string.len(values[#values])
- end
- for i, _value in next, value.Body do
- cons ..= TranspileValue(_value, depth)
- if i < #value.Body then
- cons ..= fmt(",%s", total_length <= 120 and " " or "\n" .. Indent(depth + 1))
- end
- end
- return fmt("{%s%s%s}", total_length <= 120 and "" or "\n" .. Indent(depth + 1), cons, total_length <= 120 and "" or "\n" .. Indent(depth))
- elseif value.Type == "InlineNamecallNode" then
- local cons: string = TranspileValue(value.Object, depth) .. ":" .. value.Target.Value .. "("
- for i, arg in next, value.Args do
- cons ..= TranspileValue(arg, depth)
- if i < #value.Args then
- cons ..= ", "
- end
- end
- return cons .. ")"
- elseif value.Type == "InlineCallNode" then
- local cons: string = TranspileValue(value.Target, depth) .. "("
- for i, arg in next, value.Args do
- cons ..= TranspileValue(arg, depth)
- if i < #value.Args then
- cons ..= ", "
- end
- end
- print('INLI AAAA')
- return cons .. ")"
- end
- return fmt("nil --[[ Unhandled type: %s ]]", value.Type)
- end
- local function TranspileNode(node: Node, depth: number): string
- if node.Type == "VarAssignNode" then
- local id = fmt("v%d", cid)
- if node.Value.Type == "TableIndexNode" then
- local final: Node = node.Value
- while final.Type == "TableIndexNode" do
- final = node.Value.Index
- end
- if final.Type == "StringNode" and not final.Value:find(" ") then
- id ..= "_" .. final.Value
- end
- end
- node.Target.Id = id
- cid += 1
- if node.Value.Type == "ClosureNode" and node.Value.Name ~= "" then -- Special case for functions
- node.Target.Id = node.Value.Name
- local cons: string = Indent(depth) .. fmt("local function %s(", node.Value.Name)
- for i, v in next, node.Value.Params do
- cons ..= fmt("p%d", pid)
- if #node.Value.ParamTypes > 2 then
- cons ..= ": " .. ResolveType(node.Value.ParamTypes[i + 2])
- end
- if i < #node.Value.Params then
- cons ..= ", "
- end
- v.Id = fmt("p%d", pid)
- pid += 1
- end
- if node.Value.IsVarArg then
- if #node.Value.Params > 0 then
- cons ..= ", "
- end
- cons ..= "..."
- end
- cons ..= ")\n"
- return cons .. TranspileClosure(node.Value, depth + 1) .. Indent(depth) .. "end"
- end
- return Indent(depth) .. fmt("local %s = %s", id, TranspileValue(node.Value, depth))
- elseif node.Type == "VarReassignNode" then
- if node.Value.Type == "ClosureNode" and node.Value.Name ~= "" then -- Special case for functions
- node.Target.Id = node.Value.Name
- local cons: string = Indent(depth) .. fmt("local function %s(", node.Value.Name)
- for i, v in next, node.Value.Params do
- cons ..= fmt("p%d", pid)
- if #node.Value.ParamTypes > 2 then
- cons ..= ": " .. ResolveType(node.Value.ParamTypes[i + 2])
- end
- if i < #node.Value.Params then
- cons ..= ", "
- end
- v.Id = fmt("p%d", pid)
- pid += 1
- end
- if node.Value.IsVarArg then
- if #node.Value.Params > 0 then
- cons ..= ", "
- end
- cons ..= "..."
- end
- cons ..= ")\n"
- return cons .. TranspileClosure(node.Value, depth + 1) .. Indent(depth) .. "end"
- end
- return Indent(depth) .. fmt("%s = %s", node.Target.Id, TranspileValue(node.Value, depth))
- elseif node.Type == "FunctionCallNode" then
- local cons: string = ""
- if node.Target.Type == "ClosureNode" then
- cons = "("
- end
- print('INLI AAAA btw at the functi0n call l2892')
- print(node)
- local target = TranspileValue(node.Target, depth)
- if #node.RetVals > 0 then
- cons ..= "local "
- for i, val in next, node.RetVals do
- if target == "require" and #node.RetVals == 1 then
- -- here we want to get the name of the module, which could be something like script.Parent.SomeModule or script.Parent:WaitForChild("SomeModule")
- local module: string = ""
- if #node.Args == 1 and node.Args[1].Type == "InlineNamecallNode" then
- 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
- module = "_" .. ((node.Args[1] :: InlineNamecallNode).Args[1] :: StringNode).Value
- end
- elseif #node.Args == 1 and node.Args[1].Type == "TableIndexNode" then
- if (node.Args[1] :: TableIndexNode).Index.Type == "StringNode" and not ((node.Args[1] :: TableIndexNode).Index :: StringNode).Value:find(" ") then
- module = "_" .. ((node.Args[1] :: TableIndexNode).Index :: StringNode).Value
- end
- end
- cons ..= fmt("v%d_module%s", cid, module)
- val.Id = fmt("v%d_module%s", cid, module)
- elseif target == "pairs" or target == "ipairs" then
- local id = fmt("v%d_", cid)
- if i == 1 then
- id ..= "generator"
- elseif i == 2 then
- id ..= "state"
- elseif i == 3 then
- id ..= "index"
- end
- cons ..= id
- val.Id = id
- elseif not string.match(target, "%W") then -- isalnum()
- cons ..= fmt("v%d_%s_ret%d", cid, target, i)
- val.Id = fmt("v%d_%s_ret%d", cid, target, i)
- else
- cons ..= fmt("v%d", cid)
- val.Id = fmt("v%d", cid)
- end
- cid += 1
- if i < #node.RetVals then
- cons ..= ", "
- end
- end
- cons ..= " = "
- end
- cons ..= target .. if node.Target.Type == "ClosureNode" then ")" .. "(" else "" .. "("
- for i, arg in next, node.Args do
- cons ..= TranspileValue(arg, depth)
- if i < #node.Args then
- cons ..= ", "
- end
- end
- cons ..= ")"
- return Indent(depth) .. cons
- elseif node.Type == "NamecallNode" then
- local cons: string = ""
- if #node.RetVals > 0 then
- cons ..= "local "
- 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
- cons ..= fmt("v%d", cid) .. "_" .. (node.Args[1] :: StringNode).Value
- node.RetVals[1].Id = fmt("v%d", cid) .. "_" .. (node.Args[1] :: StringNode).Value
- cid += 1
- else
- for i, val in next, node.RetVals do
- cons ..= fmt("v%d_%s_ret%d", cid, node.Target.Value, i)
- val.Id = fmt("v%d_%s_ret%d", cid, node.Target.Value, i)
- cid += 1
- if i < #node.RetVals then
- cons ..= ", "
- end
- end
- end
- cons ..= " = "
- end
- cons ..= TranspileValue(node.Object, depth) .. ":" .. node.Target.Value .. "("
- for i, arg in next, node.Args do
- cons ..= TranspileValue(arg, depth)
- if i < #node.Args then
- cons ..= ", "
- end
- end
- cons ..= ")"
- return Indent(depth) .. cons
- elseif node.Type == "ReturnNode" then
- local cons: string = "return"
- if #node.Values > 0 then
- cons ..= " "
- for i, value in next, node.Values do
- cons ..= TranspileValue(value, depth)
- if i < #node.Values then
- cons ..= ", "
- end
- end
- end
- return Indent(depth) .. cons
- elseif node.Type == "TableAssignNode" then
- if node.Index.Type == "StringNode" and not string.find(node.Index.Value, " ") then
- return Indent(depth) .. fmt("%s.%s = %s", TranspileValue(node.Table, depth), node.Index.Value, TranspileValue(node.Source, depth))
- end
- return Indent(depth) .. fmt("%s[%s] = %s", TranspileValue(node.Table, depth), TranspileValue(node.Index, depth), TranspileValue(node.Source, depth))
- elseif node.Type == "IfNode" then
- while #node.Body == 1 and node.Body[1].Type == "IfNode" do
- node.Condition = {
- Type = "ExpressionNode",
- Lhs = node.Condition,
- Rhs = (node.Body[1] :: IfNode).Condition,
- Op = "and"
- }
- node.Body = (node.Body[1] :: IfNode).Body
- end
- local cons: string = Indent(depth) .. fmt("if %s then\n", TranspileValue(node.Condition, depth))
- for i, _node in next, node.Body do
- cons ..= TranspileNode(_node, depth + 1)
- if i < #node.Body then
- cons ..= "\n"
- end
- end
- local function Resolvebranches(_node: IfNode)
- if #_node.ElseBody == 1 and _node.ElseBody[1].Type == "IfNode" then
- cons ..= "\n" .. Indent(depth) .. fmt("elseif %s then\n", TranspileValue((_node.ElseBody[1] :: IfNode).Condition, depth))
- for i, __node in next, (_node.ElseBody[1] :: IfNode).Body do
- cons ..= TranspileNode(__node, depth + 1)
- if i < #_node.Body then
- cons ..= "\n"
- end
- end
- Resolvebranches(_node.ElseBody[1] :: IfNode)
- table.remove(_node.ElseBody, 1)
- end
- end
- Resolvebranches(node)
- if #node.ElseBody > 0 then
- cons ..= "\n" .. Indent(depth) .. "else\n"
- for i, _node in next, node.ElseBody do
- cons ..= TranspileNode(_node, depth + 1)
- if i < #node.ElseBody then
- cons ..= "\n"
- end
- end
- end
- return cons .. "\n" .. Indent(depth) .. "end"
- elseif node.Type == "WhileNode" then
- local cons: string = Indent(depth) .. fmt("while %s do\n", TranspileValue(node.Condition, depth))
- for i, _node in next, node.Body do
- cons ..= TranspileNode(_node, depth + 1)
- if i < #node.Body then
- cons ..= "\n"
- end
- end
- return cons .. "\n" .. Indent(depth) .. "end"
- elseif node.Type == "ForRangeNode" then
- if node.Iterator.Id == "" then
- node.Iterator.Id = fmt("v%d", cid)
- cid += 1
- end
- 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))
- for i, _node in next, node.Body do
- cons ..= TranspileNode(_node, depth + 1)
- if i < #node.Body then
- cons ..= "\n"
- end
- end
- return cons .. "\n" .. Indent(depth) .. "end"
- elseif node.Type == "ForValueNode" then
- local vars: string = ""
- for i, var in next, node.Variables do
- var.Id = fmt("v%d", cid)
- cid += 1
- vars ..= var.Id
- if i < #node.Variables then
- vars ..= ", "
- end
- end
- local cons: string = ""
- -- lol silly workaround
- if node.Index.Type ~= "NilNode" then
- 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))
- else
- cons = Indent(depth) .. fmt("for %s in %s, %s do\n", vars, TranspileValue(node.Generator, depth), TranspileValue(node.State, depth))
- end
- for i, _node in next, node.Body do
- cons ..= TranspileNode(_node, depth + 1)
- if i < #node.Body then
- cons ..= "\n"
- end
- end
- return cons .. "\n" .. Indent(depth) .. "end"
- elseif node.Type == "GlobalDefinitionNode" then
- if node.Value.Type == "ClosureNode" then
- local cons: string = Indent(depth) .. fmt("function %s(", node.Value.Name)
- for i, v in next, node.Value.Params do
- cons ..= fmt("v%d", cid)
- if i < #node.Value.Params then
- cons ..= ", "
- end
- v.Id = fmt("v%d", cid)
- cid += 1
- end
- if node.Value.IsVarArg then
- if #node.Value.Params > 0 then
- cons ..= ", "
- end
- cons ..= "..."
- end
- cons ..= ")\n"
- return Indent(depth) .. cons .. TranspileClosure(node.Value, depth + 1) .. Indent(depth) .. "end"
- end
- return Indent(depth) .. fmt("%s = %s", node.Target, TranspileValue(node.Value, depth))
- end
- return Indent(depth) .. fmt("-- UNSUPPORTED: %s", node.Type)
- end
- for i, node in next, closure.Body do
- if i == #closure.Body and node.Type == "ReturnNode" and #node.Values == 0 then
- break -- remove trailing return if it is not needed
- end
- transpiled_closure_src ..= fmt("%s\n", TranspileNode(node, depth))
- end
- return transpiled_closure_src
- end
- local main_proto = bytecode.protos[bytecode.main_proto_id + 1]
- local main_proto_instrs: { Instruction } = RefcountProto(main_proto, {})
- -- create a dummy closure for main proto
- local main_closure: Closure = {
- Type = "Closure",
- Value = main_proto_instrs,
- Params = {},
- IsVarArg = false,
- Name = "main",
- UpValues = {},
- IsGlobal = false
- }
- local ast: ClosureNode = DecompileClosure(main_closure)
- local source: string = TranspileClosure(ast, 0)
- local endTime = tick()
- return string.sub(source, 1, string.len(source) - 1), endTime - start_time
- end
- local base64 = (function()
- local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' -- You will need this for encoding/decoding
- -- encoding
- local function enc(data)
- return ((data:gsub('.', function(x)
- local r,b='',x:byte()
- for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
- return r;
- end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
- if (#x < 6) then return '' end
- local c=0
- for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
- return b:sub(c+1,c+1)
- end)..({ '', '==', '=' })[#data%3+1])
- end
- -- decoding
- local function dec(data)
- data = string.gsub(data, '[^'..b..'=]', '')
- return (data:gsub('.', function(x)
- if (x == '=') then return '' end
- local r,f='',(b:find(x)-1)
- for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
- return r;
- end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
- if (#x ~= 8) then return '' end
- local c=0
- for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
- return string.char(c)
- end))
- end
- return {
- encode = enc,
- decode = dec
- }
- end)()
- --[[
- if identifyexecutor then
- return {function(script: LocalScript | ModuleScript)
- local bytes: string = getscriptbytecode(script)
- local bytecode: luau_bytecode = deserialize(bytes, true)
- return "-- Decompiled with SirHurt // https://sirhurt.net/?ref=woffle // written by @luavm, et al.\n\n" .. wdec_decompile(bytecode)
- end}
- else
- -- No credits header for non-roblox decompiled scripts cuz why bother
- return {function(bytes: string)
- local bytecode = deserialize(bytes, true)
- return wdec_decompile(bytecode)
- end}
- end
- ]]
- function decompile(script: LocalScript | ModuleScript)
- local CreditHeader = '--Decompiled with wdec // Made by @luavm // fixed by @legalcarbomb (still needs tweaking)\n'
- local _, bytecode = pcall(getscriptbytecode, script)
- assert(_, 'Failed to retrieve bytecode from script: ' .. script.Name .. '\n\n Cause: ' .. bytecode)
- local _, deserialized = pcall(deserialize, bytecode, true)
- local result, timeTaken = wdec_decompile(deserialized)
- return CreditHeader .. "--Time Taken: " .. timeTaken .. "\n" .. result
- end
- local env = (getgenv and getgenv()) or (getfenv and getfenv()) or shared or _G
- env.decompile = decompile
- function bytecode_decompile(bytecode)
- local CreditHeader = '--Decompiled with wdec // Made by @luavm // fixed by @legalcarbomb (still needs tweaking)\n'
- --//assert(_, 'Failed to retrieve bytecode from script: ' .. script .. '\n\n Cause: ' .. bytecode)
- local _, deserialized = pcall(deserialize, bytecode, false)
- local result, timeTaken = wdec_decompile(deserialized)
- return CreditHeader .. "--Time Taken: " .. timeTaken .. "\n" .. result
- end
- --local LuauCeption = loadstring(game.HttpService:GetAsync("https://github.com/RealEthanPlayzDev/LuauCeption/releases/download/0.645/Luau.LuauCeption.Compiler.0.645.luau", true))() -- Bytecode Compiler
- --Usage: bytecode_decompile(LuauCeption.luau_compile())
Add Comment
Please, Sign In to add comment