Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --// init
- local Settings = {
- return_values = true,
- max_table_size = 3,
- IncludeFunctionEnv = false,
- decode_bytes = true, -- converts stuff like \110 to 'n'
- table_display = 'maximized' --[[
- maximized:
- {
- [1] = 1,
- [2] = 2,
- [3] = 3,
- ...
- }
- minimized:
- { [1] = 1, [2] = 2, [3] = 3, ... }
- ]]
- }
- type userdata = {}
- type _function = (...any) -> (...any)
- function GetFullName(instance)
- local p = instance
- local lo = {}
- while (p ~= game and p.Parent ~= nil) do
- table.insert(lo, p)
- p = p.Parent
- end
- local fullName
- if #lo == 0 then
- return "nil --[[ PARENTED TO NIL OR DESTROYED ]]"
- end
- if lo[#lo].ClassName ~= "Workspace" then
- fullName = 'game:GetService("' .. lo[#lo].ClassName .. '")'
- else
- fullName = "workspace"
- end
- for i = #lo - 1, 1, -1 do
- fullName = fullName .. ':FindFirstChild("' .. lo[i].Name .. '")'
- end
- return fullName
- end
- local function rawlength(t)
- local count = 0
- for _, _ in next, t do count += 1 end
- return count
- end
- local function move(t1, fromt1, tot1, copyintot2, t2)
- local result = {}
- local count = 0
- for k, v in pairs(t1) do
- count = count + 1
- if count > tot1 then
- break
- end
- if count >= fromt1 then
- result[k] = v
- end
- end
- local insertionIndex = copyintot2
- for k, v in pairs(result) do
- t2[k] = v
- end
- return t2
- end
- local Metatable = {
- --[[
- A list of metamethod emulators used to call individual
- metamethods efficiently.
- ]]
- metamethods = {
- __index = function(self, key)
- return self[key]
- end,
- __newindex = function(self, key, value)
- self[key] = value
- end,
- __call = function(self, ...)
- return self(...)
- end,
- __concat = function(self, b)
- return self..b
- end,
- __add = function(self, b)
- return self + b
- end,
- __sub = function(self, b)
- return self - b
- end,
- __mul = function(self, b)
- return self * b
- end,
- __div = function(self, b)
- return self / b
- end,
- __idiv = function(self, b)
- return self // b
- end,
- __mod = function(self, b)
- return self % b
- end,
- __pow = function(self, b)
- return self ^ b
- end,
- __tostring = function(self)
- return tostring(self)
- end,
- __eq = function(self, b)
- return self == b
- end,
- __lt = function(self, b)
- return self < b
- end,
- __le = function(self, b)
- return self <= b
- end,
- __len = function(self)
- return #self
- end,
- __iter = function(self)
- return next, self
- end,
- __namecall = function(self, ...)
- return self:_(...)
- end,
- __metatable = function(self)
- return getmetatable(self)
- end,
- }
- }
- --// methods
- --[[
- Interceptive hook.
- Hooks `obj`'s metamethod `metamethod`
- ]]
- function Metatable.get_L_closure(metamethod: string, obj: {any} | userdata)
- local hooked
- local metamethod_emulator = Metatable.metamethods[metamethod]
- xpcall(function()
- metamethod_emulator(obj)
- end, function()
- hooked = debug.info(2, "f")
- end)
- return hooked
- end
- --[[
- Interceptive hook.
- Attempts to scan for all available metamethods
- ]]
- function Metatable.get_all_L_closures(obj: {any} | userdata)
- local metamethods = {}
- local innacurate = {}
- for method, _ in Metatable.metamethods do
- local metamethod, accurate = Metatable.get_L_closure(method, obj)
- metamethods[method] = metamethod
- end
- return metamethods
- end
- --[[
- Non-recursive hook.
- Calls `f` when a metamethod of `t` gets fired
- ]]
- function Metatable.metahook(t: any, f: _function)
- local metahook = {
- __metatable = getmetatable(t) or "The metatable is locked"
- }
- for metamethod, value in Metatable.metamethods do
- metahook[metamethod] = function(self, ...)
- f()
- return Metatable.metahook({}, f)
- end
- end
- return setmetatable({}, metahook)
- end
- --// Localization
- local setmetatable = setmetatable
- local pcall = pcall
- local table = table.clone(table)
- local debug = debug
- local string = string
- local setfenv = setfenv
- local getfenv = getfenv
- local nonluaglobals = {}
- local libs = {
- coroutine = coroutine,
- math = math,
- buffer = buffer,
- table = table,
- string = string,
- os = os,
- utf8 = utf8,
- bit32 = bit32,
- debug = debug,
- task = task
- }
- for libname, libvalue in next, libs do
- for funcname, _ in next, libvalue do
- nonluaglobals[funcname] = string.format('%s.%s', libname, funcname)
- end
- end
- local SelectedNum = 2147483647 ^ 2
- local SelectedStuff = {}
- table.rawlength = function(t)
- local r = 0
- for _, _ in next, t do
- r = r + 1
- end
- return r
- end
- table.concat = function(t, sep)
- sep = sep or ''
- local result = ''
- local count = 1
- local length = table.rawlength(t)
- for _, v in next, t do
- result = result .. tostring(v)
- if count ~= length then
- result = result .. sep
- end
- count = count + 1
- end
- return result
- end
- local noreturnvalue={'print','warn','error','task.spawn','assert','delay','task.delay','printidentity','spawn','collectgarbage','debug.profilebegin','debug.profileend','debug.resetmemorycategory','debug.dumpcodesize','math.randomseed','table.clear','table.foreach','table.foreachi','table.insert','table.sort','task.synchronize','task.desynchronize','task.cancel','buffer.writei8','buffer.writeu8','buffer.writei16','buffer.writeu16','buffer.writei32','buffer.writeu32','buffer.writef32','buffer.writef64','buffer.writestring','buffer.copy','buffer.fill'}
- local stack = {}
- --// Init
- local function get_param_num(f)
- return debug.info(f, "a")
- end
- local function merge_t(a, b)
- local r = {}
- for i, v in a do r[i] = v end
- for i, v in b do r[i] = v end
- return r
- end
- local lens = {}
- --// Sandbox
- local function Sandbox(f: (...any) -> (...any), upvalues: {any}?, constants: {string}?, protos: {(...any) -> (...any)}?, i: number?, params)
- upvalues = upvalues or {}
- constants = constants or {}
- protos = protos or {}
- params = params or {}
- i = i or 1
- local root = {
- root = true,
- children = {},
- stack = {},
- params = {},
- constants = constants,
- upvalues = upvalues,
- protos = protos,
- pc = 0,
- function_info = {debug.info(f, "na")},
- f = f,
- i = i,
- error_message = '',
- fenv = getfenv(f)
- }
- local id_i = 0
- local last_func
- local function wrap(parent: {pc: number?, children: {any}, arguments: {any?}?})
- local hooks = {}
- local t = {}
- for metamethod in Metatable.metamethods do
- local SelectedNum2 = math.random(2 ^ 16, 2 ^ 24)
- hooks[metamethod] = function(_, ...)
- root.pc += 1
- local pc = root.pc
- local stack = root.stack
- local self = {pc = pc, children = {}, parent = parent, arguments = {...}, metamethod = metamethod}
- SelectedStuff[SelectedNum2] = SelectedNum2 * SelectedNum * pc
- parent.children[pc] = self
- if metamethod == "__len" then
- return SelectedNum2 * SelectedNum * pc
- elseif metamethod == '__tostring' then
- return tostring(parent)
- end
- return wrap(self)
- end
- end
- if root.pc ~= 0 then
- root.stack[t] = root.pc
- else
- root.stack[t] = id_i
- root.params[t] = id_i
- id_i += 1
- end
- return setmetatable(t, hooks)
- end
- local env = wrap(root)
- local param_num, vararg = get_param_num(f)
- for i = 1, param_num do
- local arg = wrap(root)
- table.insert(params, arg)
- root.params[arg] = root.i
- root.i += 1
- end
- if vararg then
- local vararg = wrap(root)
- table.insert(params, vararg)
- root.params[vararg] = "..."
- end
- if Settings.return_values then
- local original_env = getfenv(f)
- local return_value
- task.spawn(function()
- return_value = {pcall(setfenv(f, env), unpack(params))}
- end)
- for _ = 1, 100 do
- if return_value then break end
- task.wait(.1)
- end
- if not return_value then
- warn("ERR_1: Function decompile timeout.")
- return
- end
- if return_value[1] then setfenv(f, original_env) end
- root.return_value = table.move(return_value, 2, #return_value, 1, {})
- root.success = return_value[1]
- root.error_message = not root.success and return_value[2]
- else
- root.success = true
- root.return_value = {}
- end
- return root
- end
- local function Clean(a) -- Remove spaces or illegal charcters from the text
- return a:match("%w+")
- end
- local libs = {string=string, coroutine=coroutine, buffer=buffer, os=os, task=task, utf8=utf8, math=math, table=table, bit32=bit32, debug=debug}
- local format;
- local quickaccess = {}
- function equals(tbl, tbl2) -- Shallow equals for tree.return_value
- if type(tbl) ~= 'table' or type(tbl2) ~= 'table' then return tbl == tbl2 end
- for i, v in next, tbl do
- if tbl2[i] ~= v then return false end
- end
- return true
- end
- --// Disassembler
- local function Disassemble(tree: {any}, tabs: number?)
- tabs = tabs or 1
- local tab_formatting = (" "):rep(tabs)
- local stack = tree.stack
- local params = tree.params
- local upvalues = tree.upvalues
- local final_pc = tree.pc
- local success = tree.success
- local function_info = tree.function_info
- local i = tree.i
- local stack_offset do
- stack_offset = (final_pc > 0 and 1) or 0
- end
- local disassembly = {}
- local constants = {}
- local protos = {}
- local pc = 0
- function format(value, tabs2, exclude)
- tabs2 = tabs2 or tabs
- local type = typeof(value)
- local s_index = stack[value]
- local p_index = params[value]
- local uv_index = upvalues[value]
- if p_index then
- if p_index ~= "..." then
- return ("_p%d"):format(tonumber(p_index) or 1)
- end
- return p_index
- elseif s_index and tonumber(s_index) then
- return ("v%d"):format(tonumber(s_index))
- elseif uv_index then
- if not upvalues[value] then
- disassembly[1] = ("local _uv_%s = v%d[%s]\n"):format(uv_index, uv_index)..(disassembly[1] or "")
- else
- return ("_p%s"):format(uv_index)
- end
- return ("_uv_%d"):format(uv_index)
- elseif not p_index and not s_index and not uv_index and type == 'number' then
- return tostring(value)
- end
- if type == exclude then
- return nil
- end
- if type == "string" then
- local s = ""
- for _, char in {value:byte(1, -1)} do
- if char > 126 or char < 32 then
- s ..= "\\"..char
- else
- s ..= string.char(char)
- end
- end
- table.insert(constants, value)
- if Settings.decode_bytes then
- s = string.gsub(s, "\\(%d+)", string.char)
- end
- return ('"%s"'):format(s)
- elseif type == "table" then
- local t = ""
- local mt = getmetatable(value)
- local count = 0
- local total = 0
- local a = ' '
- local tabf = a:rep(tabs)
- for _, _ in next, value do
- total = total + 1
- end
- for i, v in next, value do
- count = count + 1
- local betterI = typeof(i) == 'number' and tostring(i) or string.format('"%s"', tostring(i))
- local betterV = typeof(v) == 'number' and tostring(v) or format(v, tabs2 + 1)
- t ..= ("%s%s[%s] = %s%s"):format(Settings.table_display == 'maximized' and '\n' or '', Settings.table_display == 'maximized' and (a:rep(tabs2) .. ' ') or '', betterI, betterV, count < total and ', ' or '')
- end
- t = ("{%s%s%s%s}"):format(tabf, t, count > 0 and '\n' or '', a:rep(tabs2))
- if typeof(mt) == 'table' then
- local methods = '{'
- for MethodIndex, _ in Metatable.metamethods do
- local rg = rawget(mt, MethodIndex)
- if rg then
- methods ..= ('%s%s%s = %s;'):format(Settings.table_display == 'maximized' and '\n' or '', tab_formatting .. ' ', MethodIndex, format(rg))
- end
- end
- methods = #methods > 1 and methods .. '\n' .. a:rep(tabs + 4) .. '}\n' .. a:rep(tabs + 1) or '{}'
- t = ('setmetatable(%s, %s)'):format(t, methods)
- end
- return t
- elseif type == "function" then
- if not table.find(protos, value) and value ~= tree.f then
- table.insert(protos, value)
- local paramcount, isvrg = debug.info(tree.f, 'a')
- local pars = ''
- if isvrg then
- pars = '...'
- else
- for i = 1, paramcount do
- pars = pars .. '_p' .. tostring(i)
- if i ~= paramcount then
- pars = pars .. ', '
- end
- end
- end
- for lib_name, lib_value in next, libs do
- for func_name, func_val in next, lib_value do
- if func_val == value then
- return string.format('%s.%s', lib_name, func_name)
- end
- end
- end
- local sandbox = Sandbox(value, merge_t(table.clone(params), upvalues), constants, protos, i)
- local _disassembly = Disassemble(sandbox, tabs + 1)
- if sandbox.function_info[1] == "" then
- return ("function(%s)\n%s%s\n%send"):format(
- pars, tab_formatting, _disassembly, tab_formatting
- )
- else
- disassembly[pc + 1] = ("%slocal function %s(%s) %s\n%send\n"):format(
- tab_formatting,
- sandbox.function_info[1],
- pars,
- _disassembly,
- tab_formatting
- )..(disassembly[pc] or "")
- pc += 1 + stack_offset
- return sandbox.function_info[1]
- end
- elseif value == tree.f then
- local func_name = debug.info(value, "n")
- if func_name == "" then
- return "__func__"
- else
- return func_name
- end
- else
- return ("function() --[[ Unknown proto ]] end")
- end
- end
- if type == "userdata" or type == "vector" then
- local index = math.random(0, 0xFFF)
- stack[value] = index
- return "newproxy(" .. tostring(getmetatable(value) ~= nil) .. ')'
- elseif type == 'Instance' then
- return GetFullName(value)
- end
- return tostring(value)
- end
- local function format_tuple(...)
- local t = {}
- local last = 0
- for i, index in {...} do
- if i - last > 1 then
- local void_size = i - last - 1
- table.move(table.create(void_size, "nil"), i, void_size, 1, t)
- end
- table.insert(t, format(index))
- last = i
- end
- return table.concat(t, ", ")
- end
- local function parse(branch, parent)
- if math.random(0, 2) == 0 then task.wait() end -- prevent script timeout while maintaining performance
- pc = branch.pc
- local metamethod = branch.metamethod
- local args = branch.arguments
- local parent_pc = (parent and parent.pc) or 0
- local a, b = args[1], args[2]
- a = a or '(???)'
- local global = parent == nil
- local argCount, isv = debug.info(tree.f, 'a')
- argCount = isv and math.huge or argCount
- local push = ("-- unsupported method '%s'"):format(metamethod)
- local adjusted_pc = (pc - 1) % argCount + 1
- if metamethod == "__index" then
- if global then
- push = ("local v%d = %s"):format(pc, a)
- table.insert(constants, a)
- stack[pc] = a
- else
- if not nonluaglobals[a] then
- local thing = ''
- if Clean(a) == a then
- thing = string.format('.%s', tostring(a))
- else
- thing = string.format("[%s]", tostring(format(a)))
- end
- push = ("local v%d = %s%s"):format(pc, 'v' .. tostring(parent_pc), thing)
- else
- local result
- if stack[pc-1] then
- local lib = stack[pc-1]
- result = nonluaglobals[a]:gsub(lib, 'v' .. tostring(pc-1))
- end
- push = ("local v%d = %s"):format(pc, result or a)
- end
- table.insert(constants, a)
- stack[pc] = Clean(a) == a and '.' .. a or string.format("[%s]", tostring(format(a)))
- end
- elseif metamethod == "__newindex" then
- if global then
- push = ("local v%d = %s"):format(pc, format(b))
- table.insert(constants, a)
- stack[pc] = format(b)
- else
- push = ("v%d[%s] = %s"):format(parent_pc, format(a), format(b))
- table.insert(constants, a)
- stack[pc] = format(b)
- end
- elseif metamethod == "__call" then
- local namecall_info = ""
- local namecall_stack = stack[args[1] or -1]
- local is_namecall = namecall_stack == parent_pc - 1
- if is_namecall and (parent and parent.parent and parent.parent.arguments) then
- local a = parent.parent.arguments[1] or "(???)"
- local b = parent.arguments[1] or "(???)"
- pcall(function()
- namecall_info = ("-- %s.%s(%s)"):format(a, b, format_tuple(unpack(args)))
- end)
- end
- local tpl = format_tuple(unpack(args))
- local rgs = tpl:split(',')
- local result = ''
- for ff, arg in rgs do
- local arg2 = arg:gsub(' ', '')
- if arg2:sub(1, 2) == '_p' and arg2:sub(3, #arg):match("%d+") == arg2:sub(3, #arg) then
- local index = tonumber(arg2:sub(3, #arg))
- local nIndex = index
- if argCount < index then
- nIndex = (index - 1) % argCount + 1
- end
- arg2 = arg2:gsub(index, nIndex)
- end
- result = result .. arg2
- if ff < #rgs then
- result = result .. ', '
- end
- end
- push = ("local v%d = %s(%s)"):format(pc, parent_pc > 0 and 'v' .. tostring(parent_pc) or '_p' .. tostring(adjusted_pc), tpl)
- elseif metamethod == '__tostring' then
- local thing = ''
- if parent_pc > 0 then
- thing = thing .. 'v'.. tostring(parent_pc)
- else
- thing = thing .. '_p'.. tostring(adjusted_pc)
- end
- push = ("local v%d = tostring(%s)"):format(pc, thing)
- else
- local only_self = {
- __len = "#",
- __unm = "-",
- }
- local math = {
- __add = "+",
- __sub = "-",
- __mul = "*",
- __div = "/",
- __idiv = "//",
- __pow = "^",
- __eq = "==",
- __lt = "<",
- __le = "<=",
- __mod = "%",
- __concat = '..'
- }
- local self_index, math_index = only_self[metamethod], math[metamethod]
- if self_index then
- local thing = self_index
- if parent_pc > 0 then
- thing = thing .. 'v'.. tostring(parent_pc)
- else
- thing = thing .. '_p'.. tostring(adjusted_pc)
- end
- push = ("local v%d = %s"):format(
- pc,
- lens['v' .. tostring(parent_pc)] or thing
- )
- table.insert(constants, 'v' .. tostring(pc))
- elseif math_index then
- push = ("local v%d = v%d %s %s"):format(pc, parent_pc, math_index, format(a))
- end
- end
- disassembly[pc + stack_offset] = tab_formatting .. push
- for _, child in next, branch.children do
- parse(child, branch)
- end
- end
- for _, child in next, tree.children do
- parse(child)
- end
- if success then
- local return_value = tree.return_value
- if final_pc > 0 then
- table.insert(disassembly, "")
- end
- if #return_value > 0 then
- if type(return_value[1]) == 'table' and rawlength(return_value[1]) < Settings.max_table_size then
- table.insert(disassembly, ("%sreturn %s\n"):format(tab_formatting, format_tuple(unpack(return_value))))
- elseif type(return_value[1]) == 'table' and rawlength(return_value[1]) > Settings.max_table_size then
- table.insert(disassembly, ("%sreturn %s -- Skipped %d elements due to max_table_size.\n"):format(
- tab_formatting, format_tuple(
- move(return_value[1], 1, Settings.max_table_size, 1, {})
- ),
- rawlength(return_value[1]) - Settings.max_table_size
- )
- )
- else
- table.insert(disassembly, ("%sreturn %s\n"):format(
- tab_formatting, format_tuple(unpack(return_value))
- )
- )
- end
- end
- end
- disassembly = table.concat(disassembly, "\n")
- return ((not success and ("%s-- An error occured while decompiling (@pc %d): %s\n"):format(tab_formatting, final_pc, tree.error_message)) or "")..disassembly .. '\n', constants, protos, success
- end
- local function typefind(a, b)
- if type(a) ~= 'table' then return a end
- local result = {}
- for c, d in next, a do
- if type(d) == 'table' then
- result[c] = typefind(d, b)
- elseif type(d) ~= 'table' and type(d) ~= 'function' then
- result[c] = d
- end
- end
- return result
- end
- function Decompile(f: (...any) -> (...any), params)
- local start = tick()
- local Sandboxed = Sandbox(f, {}, {}, {}, 0, params)
- local disassembly, constants, protos, _ = Disassemble(Sandboxed)
- local Output = ''
- if Settings.IncludeFunctionEnv then
- Output = '-- fenv start\n'
- for Index, Value in next, Sandboxed.fenv do
- if type(Value) ~= 'function' then
- local v = typefind(Value, 'function')
- Output = Output .. string.format("%s = %s\n", tostring(Index), format(v))
- end
- end
- Output = Output .. '\n\n-- fenv end\n\n'
- end
- if type(f) == 'function' then
- local params = {}
- local ParamCount, IsVrg = debug.info(f, 'a')
- if not IsVrg then
- for i = 1, ParamCount do
- table.insert(params, '_p' .. tostring(i))
- end
- else
- params = {'...'}
- end
- local n = debug.info(f, 'n')
- Output = Output .. string.format('function %s(%s)\n', n ~= '' and n or '__func__', table.concat(params, ', '))
- end
- Output = Output .. disassembly
- Output = Output:gsub("(%d+%.*%d*%w*%+*%-*%d*)", function(match) -- The length fixer
- local m = tonumber(match)
- for num, result in SelectedStuff do
- if result == m then
- local PC = math.round(result/ SelectedNum / num)
- local v2 = constants[PC]
- local fixedMatch = v2
- return fixedMatch
- end
- end
- return match
- end)
- local values, removed, usage = {}, {}, {}
- local function getvalue(v)
- for var, val in next, values do
- if var == v then
- return val
- end
- end
- return v
- end
- local function getvar(v)
- for var, val in next, values do
- if val == v then return var end
- end
- end
- Output = Output:gsub('(v%d+)', function(a)
- if not usage[a] then usage[a] = 1 else usage[a] = usage[a] + 1 end
- end)
- Output = Output:gsub("local (v%d+)(%s*)=(%s*)([^%s]+)", function(a, space1, space2, b)
- for i, v in next, values do
- if v == b then
- table.insert(removed, a)
- values[a] = getvar(v)
- return ""
- end
- end
- usage[a] = usage[a] - 1
- values[a] = b
- end)
- Output = Output:gsub('(local%s*v%d+)(%s*=%s*)(%w+)%(', function(v1, v2, var)
- local a = var
- repeat a = getvalue(a) until not values[a]
- if table.find(noreturnvalue, a) then
- return var .. '('
- else
- return v1 .. v2 .. var .. '('
- end
- end)
- Output = Output:gsub("(v%d+)%.(%w+)", function(var, index)
- if usage[var] == 1 then
- table.insert(removed, var)
- return string.format('%s.%s', getvalue(var):gsub('v%d+', getvalue), index)
- end
- end)
- for _, var in next, removed do
- Output = Output:gsub(`local {var}%s*=%s*([^%s]+)`, function(a)
- return ""
- end)
- Output = Output:gsub(`{var}%(`, getvalue(var) .. '(')
- Output = Output:gsub(`{var}%.`, getvalue(var) .. '.')
- end
- Output = Output:gsub("\n%s*\n", "\n")
- return string.format("-- Decompiled with Function Dumper (Forked xAPI function decompiler) in about %.14f seconds.\n", tick() - start) .. Output .. 'end'
- end
- return Decompile
Add Comment
Please, Sign In to add comment