local function deserialize(bytecode)
    local reader do
        reader = {}
        pos = 1
        function reader:pos() return pos end
        function reader:nextByte()
            local v = bytecode:byte(pos, pos)
            pos = pos + 1
            return v
        end
        function reader:nextChar()
            return string.char(reader:nextByte());
        end
        function reader:nextInt()
            local b = { reader:nextByte(), reader:nextByte(), reader:nextByte(), reader:nextByte() }
            return (
                bit32.bor(bit32.lshift(b[4], 24), 
                bit32.bor(bit32.lshift(b[3], 16),
                bit32.bor(bit32.lshift(b[2], 8),
                b[1])))
            )
        end
        function reader:nextVarInt()
            local c1, c2, b, r = 0, 0, 0, 0
            repeat
                c1 = reader:nextByte()
                c2 = bit32.band(c1, 0x7F)
                r = bit32.bor(r, bit32.lshift(c2, b))
                b = b + 7
            until not bit32.btest(c1, 0x80)
            return r;
        end
        function reader:nextString()
            local result = ""
            local len = reader:nextVarInt();
            for i = 1, len do
                result = result .. reader:nextChar();
            end
            return result;
        end
        function reader:nextDouble()
            local b = {};
            for i = 1, 8 do
                table.insert(b, reader:nextByte());
            end
            local str = '';
            for i = 1, 8 do
                str = str .. string.char(b[i]);
            end
            return string.unpack("<d", str)
        end
    end
    local status = reader:nextByte()
    if (status ~= 0) then
        local protoTable = {}
        local stringTable = {}
        
        local sizeStrings = reader:nextVarInt()
        for i = 1,sizeStrings do
            stringTable[i] = reader:nextString()
        end
        
        local sizeProtos = reader:nextVarInt();
        for i = 1,sizeProtos do
            protoTable[i] = {} -- pre-initialize an entry
            protoTable[i].codeTable = {}
            protoTable[i].kTable = {}
            protoTable[i].pTable = {}
            protoTable[i].smallLineInfo = {}
            protoTable[i].largeLineInfo = {}
        end
        
        for i = 1,sizeProtos do
            local proto = protoTable[i]
            proto.maxStackSize = reader:nextByte()
            proto.numParams = reader:nextByte()
            proto.numUpValues = reader:nextByte()
            proto.isVarArg = reader:nextByte()
            
            proto.sizeCode = reader:nextVarInt()
            for j = 1,proto.sizeCode do
                proto.codeTable[j] = reader:nextInt()
            end
            
            proto.sizeConsts = reader:nextVarInt();
            for j = 1,proto.sizeConsts do
                local k = {};
                k.type = reader:nextByte();
                if k.type == 1 then -- boolean
                    k.value = (reader:nextByte() == 1 and true or false)
                elseif k.type == 2 then -- number
                    k.value = reader:nextDouble()
                elseif k.type == 3 then -- string
                    k.value = stringTable[reader:nextVarInt()]
                elseif k.type == 4 then -- cache
                    k.value = reader:nextInt()
                elseif k.type == 5 then -- table
                    k.value = { ["size"] = reader:nextVarInt(), ["ids"] = {} }
                    for s = 1,k.value.size do
                        table.insert(k.value.ids, reader:nextVarInt() + 1)
                    end
                elseif k.type == 6 then -- closure
                    k.value = reader:nextVarInt() + 1 -- closure id
                elseif k.type ~= 0 then
                    error(string.format("Unrecognized constant type: %i", k.type))
                end
                proto.kTable[j] = k
            end
            
            proto.sizeProtos = reader:nextVarInt();
            for j = 1,proto.sizeProtos do
                proto.pTable[j] = protoTable[reader:nextVarInt() + 1]
            end
            
            proto.lineDefined = reader:nextVarInt()
            
            local protoSourceId = reader:nextVarInt()
            proto.source = stringTable[protoSourceId]
            
            if (reader:nextByte() == 1) then -- Has Line info?
                local compKey = reader:nextVarInt()
                for j = 1,proto.sizeCode do
                    proto.smallLineInfo[j] = reader:nextByte()
                end
                
                local n = bit32.band(proto.sizeCode + 3, -4)
                local intervals = bit32.rshift(proto.sizeCode - 1, compKey) + 1
                
                for j = 1,intervals do
                    proto.largeLineInfo[j] = reader:nextInt()
                end
            end
            
            if (reader:nextByte() == 1) then -- Has Debug info?
                error'disassemble() can only be called on ROBLOX scripts'
            end
        end
        
        local mainProtoId = reader:nextVarInt()
        return protoTable[mainProtoId + 1], protoTable, stringTable;
    else
        error(string.format("Invalid bytecode (version: %i)", status))
        return nil;
    end
end
local function getluauoptable()
    return {
        -- I could use case multiplier, but that depends only on how accurate
        -- our ordering of the opcodes are -- so if we really want to rely on
        -- the latest updated luau source, then we could do it that way.
        { ["name"] = "NOP", ["type"] = "none", ["case"] = 0, ["number"] = 0x00 },
        { ["name"] = "BREAK", ["type"] = "none", ["case"] = 1, ["number"] = 0xE3 },
        { ["name"] = "LOADNIL", ["type"] = "iA", ["case"] = 2, ["number"] = 0xC6 },
        { ["name"] = "LOADB", ["type"] = "iABC", ["case"] = 3, ["number"] = 0xA9 },
        { ["name"] = "LOADN", ["type"] = "iABx", ["case"] = 4, ["number"] = 0x8C },
        { ["name"] = "LOADK", ["type"] = "iABx", ["case"] = 5, ["number"] = 0x6F },
        { ["name"] = "MOVE", ["type"] = "iAB", ["case"] = 6, ["number"] = 0x52 },
        { ["name"] = "GETGLOBAL", ["type"] = "iAC", ["case"] = 7, ["number"] = 0x35, ["aux"] = true },
        { ["name"] = "SETGLOBAL", ["type"] = "iAC", ["case"] = 8, ["number"] = 0x18, ["aux"] = true },
        { ["name"] = "GETUPVAL", ["type"] = "iAB", ["case"] = 9, ["number"] = 0xFB },
        { ["name"] = "SETUPVAL", ["type"] = "iAB", ["case"] = 10, ["number"] = 0xDE },
        { ["name"] = "CLOSEUPVALS", ["type"] = "iA", ["case"] = 11, ["number"] = 0xC1 },
        { ["name"] = "GETIMPORT", ["type"] = "iABx", ["case"] = 12, ["number"] = 0xA4, ["aux"] = true },
        { ["name"] = "GETTABLE", ["type"] = "iABC", ["case"] = 13, ["number"] = 0x87 },
        { ["name"] = "SETTABLE", ["type"] = "iABC", ["case"] = 14, ["number"] = 0x6A },
        { ["name"] = "GETTABLEKS", ["type"] = "iABC", ["case"] = 15, ["number"] = 0x4D, ["aux"] = true },
        { ["name"] = "SETTABLEKS", ["type"] = "iABC", ["case"] = 16, ["number"] = 0x30, ["aux"] = true },
        { ["name"] = "GETTABLEN", ["type"] = "iABC", ["case"] = 17, ["number"] = 0x13 },
        { ["name"] = "SETTABLEN", ["type"] = "iABC", ["case"] = 18, ["number"] = 0xF6 },
        { ["name"] = "NEWCLOSURE", ["type"] = "iABx", ["case"] = 19, ["number"] = 0xD9 },
        { ["name"] = "NAMECALL", ["type"] = "iABC", ["case"] = 20, ["number"] = 0xBC, ["aux"] = true },
        { ["name"] = "CALL", ["type"] = "iABC", ["case"] = 21, ["number"] = 0x9F },
        { ["name"] = "RETURN", ["type"] = "iAB", ["case"] = 22, ["number"] = 0x82 },
        { ["name"] = "JUMP", ["type"] = "isBx", ["case"] = 23, ["number"] = 0x65 },
        { ["name"] = "JUMPBACK", ["type"] = "isBx", ["case"] = 24, ["number"] = 0x48 },
        { ["name"] = "JUMPIF", ["type"] = "iAsBx", ["case"] = 25, ["number"] = 0x2B },
        { ["name"] = "JUMPIFNOT", ["type"] = "iAsBx", ["case"] = 26, ["number"] = 0x0E },
        { ["name"] = "JUMPIFEQ", ["type"] = "iAsBx", ["case"] = 27, ["number"] = 0xF1, ["aux"] = true },
        { ["name"] = "JUMPIFLE", ["type"] = "iAsBx", ["case"] = 28, ["number"] = 0xD4, ["aux"] = true },
        { ["name"] = "JUMPIFLT", ["type"] = "iAsBx", ["case"] = 29, ["number"] = 0xB7, ["aux"] = true },
        { ["name"] = "JUMPIFNOTEQ", ["type"] = "iAsBx", ["case"] = 30, ["number"] = 0x9A, ["aux"] = true },
        { ["name"] = "JUMPIFNOTLE", ["type"] = "iAsBx", ["case"] = 31, ["number"] = 0x7D, ["aux"] = true },
        { ["name"] = "JUMPIFNOTLT", ["type"] = "iAsBx", ["case"] = 32, ["number"] = 0x60, ["aux"] = true },
        { ["name"] = "ADD", ["type"] = "iABC", ["case"] = 33, ["number"] = 0x43 },
        { ["name"] = "SUB", ["type"] = "iABC", ["case"] = 34, ["number"] = 0x26 },
        { ["name"] = "MUL", ["type"] = "iABC", ["case"] = 35, ["number"] = 0x09 },
        { ["name"] = "DIV", ["type"] = "iABC", ["case"] = 36, ["number"] = 0xEC },
        { ["name"] = "MOD", ["type"] = "iABC", ["case"] = 37, ["number"] = 0xCF },
        { ["name"] = "POW", ["type"] = "iABC", ["case"] = 38, ["number"] = 0xB2 },
        { ["name"] = "ADDK", ["type"] = "iABC", ["case"] = 39, ["number"] = 0x95 },
        { ["name"] = "SUBK", ["type"] = "iABC", ["case"] = 40, ["number"] = 0x78 },
        { ["name"] = "MULK", ["type"] = "iABC", ["case"] = 41, ["number"] = 0x5B },
        { ["name"] = "DIVK", ["type"] = "iABC", ["case"] = 42, ["number"] = 0x3E },
        { ["name"] = "MODK", ["type"] = "iABC", ["case"] = 43, ["number"] = 0x21 },
        { ["name"] = "POWK", ["type"] = "iABC", ["case"] = 44, ["number"] = 0x04 },
        { ["name"] = "AND", ["type"] = "iABC", ["case"] = 45, ["number"] = 0xE7 },
        { ["name"] = "OR", ["type"] = "iABC", ["case"] = 46, ["number"] = 0xCA },
        { ["name"] = "ANDK", ["type"] = "iABC", ["case"] = 47, ["number"] = 0xAD },
        { ["name"] = "ORK", ["type"] = "iABC", ["case"] = 48, ["number"] = 0x90 },
        { ["name"] = "CONCAT", ["type"] = "iABC", ["case"] = 49, ["number"] = 0x73 },
        { ["name"] = "NOT", ["type"] = "iAB", ["case"] = 50, ["number"] = 0x56 },
        { ["name"] = "UNM", ["type"] = "iAB", ["case"] = 51, ["number"] = 0x39 },
        { ["name"] = "LEN", ["type"] = "iAB", ["case"] = 52, ["number"] = 0x1C },
        { ["name"] = "NEWTABLE", ["type"] = "iAB", ["case"] = 53, ["number"] = 0xFF, ["aux"] = true },
        { ["name"] = "DUPTABLE", ["type"] = "iABx", ["case"] = 54, ["number"] = 0xE2 },
        { ["name"] = "SETLIST", ["type"] = "iABC", ["case"] = 55, ["number"] = 0xC5, ["aux"] = true },
        { ["name"] = "NFORPREP", ["type"] = "iABx", ["case"] = 56, ["number"] = 0xA8 },
        { ["name"] = "NFORLOOP", ["type"] = "iABx", ["case"] = 57, ["number"] = 0x8B },
        { ["name"] = "TFORLOOP", ["type"] = "iABx", ["case"] = 58, ["number"] = 0x6E, ["aux"] = true },
        { ["name"] = "IPAIRSPREP", ["type"] = "none", ["case"] = 59, ["number"] = 0x51 },
        { ["name"] = "IPAIRSLOOP", ["type"] = "none", ["case"] = 60, ["number"] = 0x34 },
        { ["name"] = "PAIRSPREP", ["type"] = "none", ["case"] = 61, ["number"] = 0x17 },
        { ["name"] = "PAIRSLOOP", ["type"] = "none", ["case"] = 62, ["number"] = 0xFA },
        { ["name"] = "GETVARARGS", ["type"] = "iAB", ["case"] = 63, ["number"] = 0xDD },
        { ["name"] = "DUPCLOSURE", ["type"] = "iABx", ["case"] = 64, ["number"] = 0xC0 },
        { ["name"] = "PREPVARARGS", ["type"] = "iA", ["case"] = 65, ["number"] = 0xA3 },
        { ["name"] = "LOADKX", ["type"] = "iA", ["case"] = 66, ["number"] = 0x86 },
        { ["name"] = "JUMPX", ["type"] = "isAx", ["case"] = 67, ["number"] = 0x69 },
        { ["name"] = "FASTCALL", ["type"] = "iAC", ["case"] = 68, ["number"] = 0x4C },
        { ["name"] = "COVERAGE", ["type"] = "isAx", ["case"] = 69, ["number"] = 0x2F },
        { ["name"] = "CAPTURE", ["type"] = "iAB", ["case"] = 70, ["number"] = 0x12 },
        { ["name"] = "JUMPIFEQK", ["type"] = "iABx", ["case"] = 71, ["number"] = 0xF5, ["aux"] = true  },
        { ["name"] = "JUMPIFNOTEQK", ["type"] = "iABx", ["case"] = 72, ["number"] = 0xD8, ["aux"] = true  },
        { ["name"] = "FASTCALL1", ["type"] = "iABC", ["case"] = 73, ["number"] = 0xBB },
        { ["name"] = "FASTCALL2", ["type"] = "iABC", ["case"] = 74, ["number"] = 0x9E, ["aux"] = true },
        { ["name"] = "FASTCALL2K", ["type"] = "iABC", ["case"] = 75, ["number"] = 0x81, ["aux"] = true },
        { ["name"] = "COUNT", ["type"] = "none", ["case"] = 76, ["number"] = 0x64 }
    };
end
local luau = {};
luau.SIZE_A = 8
luau.SIZE_C = 8
luau.SIZE_B = 8
luau.SIZE_Bx = (luau.SIZE_C + luau.SIZE_B)
luau.SIZE_OP = 8
luau.POS_OP = 0
luau.POS_A = (luau.POS_OP + luau.SIZE_OP)
luau.POS_B = (luau.POS_A + luau.SIZE_A)
luau.POS_C = (luau.POS_B + luau.SIZE_B)
luau.POS_Bx = luau.POS_B
luau.MAXARG_A = (bit32.lshift(1, luau.SIZE_A) - 1)
luau.MAXARG_B = (bit32.lshift(1, luau.SIZE_B) - 1)
luau.MAXARG_C = (bit32.lshift(1, luau.SIZE_C) - 1)
luau.MAXARG_Bx = (bit32.lshift(1, luau.SIZE_Bx) - 1)
luau.MAXARG_sBx = bit32.rshift(luau.MAXARG_Bx, 1)
luau.BITRK = bit32.lshift(1, (luau.SIZE_B - 1))
luau.MAXINDEXRK = (luau.BITRK - 1)
luau.ISK = function(x) return bit32.band(x, luau.BITRK) end
luau.INDEXK = function(x) return bit32.band(x, bit32.bnot(luau.BITRK)) end
luau.RKASK = function(x) return bit32.bor(x, luau.BITRK) end
luau.MASK1 = function(n,p) return bit32.lshift(bit32.bnot(bit32.lshift(bit32.bnot(0), n)), p) end
luau.MASK0 = function(n,p) return bit32.bnot(luau.MASK1(n, p)) end
luau.GETARG_A = function(i) return bit32.band(bit32.rshift(i, luau.POS_A), luau.MASK1(luau.SIZE_A, 0)) end
luau.GETARG_B = function(i) return bit32.band(bit32.rshift(i, luau.POS_B), luau.MASK1(luau.SIZE_B, 0)) end
luau.GETARG_C = function(i) return bit32.band(bit32.rshift(i, luau.POS_C), luau.MASK1(luau.SIZE_C, 0)) end
luau.GETARG_Bx = function(i) return bit32.band(bit32.rshift(i, luau.POS_Bx), luau.MASK1(luau.SIZE_Bx, 0)) end
luau.GETARG_sBx = function(i) local Bx = luau.GETARG_Bx(i) local sBx = Bx + 1; if Bx > 0x7FFF and Bx <= 0xFFFF then sBx = -(0xFFFF - Bx); sBx = sBx - 1; end return sBx end
luau.GETARG_sAx = function(i) return bit32.rshift(i, 8) end
luau.GET_OPCODE = function(i) return bit32.band(bit32.rshift(i, luau.POS_OP), luau.MASK1(luau.SIZE_OP, 0)) end
local function disassemble(a1, showOps)
    if (typeof(a1):lower() == "instance") then
        if not getscriptbytecode then error("Executor does not support getscriptbytecode") end
        a1 = getscriptbytecode(a1);
    end
    
    if type(a1) == "table" then
        -- I just prefer bytecode strings
        local t = a1;
        at = "";
        for i = 1,#t do
            a1 = a1 .. string.char(t[i]);
        end
    end
    
    local output = ""
    local mainProto, protoTable, stringTable = deserialize(a1)
    local luauOpTable = getluauoptable();
    
    local function getOpCode(opName)
        for _,v in pairs(luauOpTable) do 
            if v.name == opName then 
                return v.number;
            end
        end
        return 0;
    end
    mainProto.source = "main"
    mainScope = {}; -- scope control, coming soon
    
    
    local function readProto(proto, depth)
        local output = "";
        
        local function addTabSpace(depth)
            output = output .. string.rep("    ", depth)
        end
        
        -- using function name (this will be removed & done outside of readProto)
        if proto.source then
            output = output .. proto.source .. " = function("
        else
            output = output .. "function("
        end
        
        for i = 1,proto.numParams do
            output = output .. "arg" .. (i - 1) -- args coincide with stack index
            if i < proto.numParams then
                output = output .. ", "
            end
        end
        
        if proto.isVarArg ~= 0 then
            if proto.numParams > 0 then
                output = output .. ", "
            end
            output = output .. "..."
        end
        
        output = output .. ")\n"
        depth = depth + 1
        
        for i = 1,proto.numParams do
            addTabSpace(depth);
            output = output .. string.format("local v%i = arg%i\n", i - 1, i - 1);
        end
        
        local refData = {}
        local nameCall = nil
        local markedAux = false
        local codeIndex = 1
        while codeIndex < proto.sizeCode do
            local i = proto.codeTable[codeIndex]
            local opc = luau.GET_OPCODE(i)
            local A = luau.GETARG_A(i)
            local B = luau.GETARG_B(i)
            local Bx = luau.GETARG_Bx(i)
            local C = luau.GETARG_C(i)
            local sBx = luau.GETARG_sBx(i)
            local sAx = luau.GETARG_sAx(i)
            local aux = proto.codeTable[codeIndex + 1]
            
            if markedAux then
                markedAux = false
            else
                addTabSpace(depth);
                
                local opinfo;
                
                for _,v in pairs(luauOpTable) do 
                    if v.number == opc then 
                        opinfo = v
                        break;
                    end
                end
                
             --   output = output .. tostring(codeIndex) .. ".   " 
                
                if showOps and opinfo then
                    local str = opinfo.name .. string.rep(" ", 16 - string.len(opinfo.name))
                    
                    if opinfo.type == "iA" then
                        str = str .. string.format("%i", A)
                    elseif opinfo.type == "iAB" then
                        str = str .. string.format("%i %i", A, B)
                    elseif opinfo.type == "iAC" then
                        str = str .. string.format("%i %i", A, C)
                    elseif opinfo.type == "iABx" then
                        str = str .. string.format("%i %i", A, Bx)
                    elseif opinfo.type == "iAsBx" then
                        str = str .. string.format("%i %i", A, sBx)
                    elseif opinfo.type == "isBx" then
                        str = str .. string.format("%i", sBx)
                    elseif opinfo.type == "iABC" then
                        str = str .. string.format("%i %i %i", A, B, C)
                    end
                    
                    if opinfo.aux then
                        str = str .. " [aux]";
                        markedAux = true
                    end
                    
                    output = output .. str .. string.rep(" ", 40 - string.len(str))
                else
                    if opinfo then
                        if opinfo.aux then
                            markedAux = true;
                        end
                    end
                end
                
                -- continue with disassembly (rough decompilation -- no scope/flow control)
                -- 
                local varsDefined = {};
                
                local function defineVar(index, name)
                    table.insert(varsDefined, { ["name"] = name, ["stackIndex"] = index });
                end
                
                local function isVarDefined(index)
                    return true;
                    --[[for _,v in pairs(varsDefined) do
                        if v.stackIndex == index then
                            return true
                        end
                    end
                    return false;
                    ]]
                end
                
                local function addReference(refStart, refEnd)
                    for _,v in pairs(refData) do
                        if v.codeIndex == refEnd then
                            table.insert(v.refs, refStart);
                            return;
                        end
                    end
                    table.insert(refData, { ["codeIndex"] = refEnd, ["refs"] = { refStart } });
                end
                
                local nilValue = { ["type"] = "nil", ["value"] = "nil" }
                
                
                --[[ TO-DO: we could make getOpCode faster by using the opcode
                numbers directly, or just getting it by table index and the 
                case-to-opcode multiplier (op * 227)
                but tbh this runs just fine
                ]]
                if opc == getOpCode("LOADNIL") then
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = nil", A)
                elseif opc == getOpCode("BREAK") then
                    output = output .. "break"
                elseif opc == getOpCode("LOADK") then
                    local k = proto.kTable[Bx + 1] or nilValue;
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = %s", A, (type(k.value) == "string") and ("\"" .. k.value .. "\"") or tostring(k.value))
                elseif opc == getOpCode("LOADKX") then
                    local k = proto.kTable[aux + 1] or nilValue;
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = %s", A, (type(k.value) == "string") and ("\"" .. k.value .. "\"") or tostring(k.value))
                elseif opc == getOpCode("LOADB") then
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = %s", A, tostring(B == 1))
                elseif opc == getOpCode("LOADN") then
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = %s", A, tostring(Bx))
                elseif opc == getOpCode("GETUPVAL") then
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = upvalues[%i]", A, B)
                elseif opc == getOpCode("SETUPVAL") then
                    output = output .. string.format("upvalues[%i] = v%i", B, A)
                elseif opc == getOpCode("MOVE") then
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = v%i", A, B)
                elseif opc == getOpCode("LEN") then
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = #v%i", A, B)
                elseif opc == getOpCode("UNM") then
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = -v%i", A, B)
                elseif opc == getOpCode("NOT") then
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = not v%i", A, B)
                elseif opc == getOpCode("GETVARARGS") then
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = ...", A)
                elseif opc == getOpCode("CONCAT") then
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = v%i .. v%i", A, B, C)
                elseif opc == getOpCode("AND") then
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = v%i and v%i", A, B, C)
                elseif opc == getOpCode("OR") then
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = v%i or v%i", A, B, C)
                elseif opc == getOpCode("ANDK") then
                    local k = proto.kTable[C + 1] or nilValue;
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = v%i and %s", A, B, tostring(k.value))
                elseif opc == getOpCode("ORK") then
                    local k = proto.kTable[C + 1] or nilValue;
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = v%i or %s", A, B, tostring(k.value))
                elseif opc == getOpCode("FASTCALL") then
                    output = output .. string.format("FASTCALL[id=%i]()", A, B);
                elseif opc == getOpCode("FASTCALL2") then
                    output = output .. string.format("FASTCALL[id=%i]()", A, B);
                elseif opc == getOpCode("FASTCALL2K") then
                    output = output .. string.format("FASTCALL[id=%i]()", A, B);
                elseif opc == getOpCode("GETIMPORT") then
                    local indexCount = bit32.band(bit32.rshift(aux, 30), 0x3FF) -- 0x40000000 --> 1, 0x80000000 --> 2
                    local cacheIndex1 = bit32.band(bit32.rshift(aux, 20), 0x3FF)
                    local cacheIndex2 = bit32.band(bit32.rshift(aux, 10), 0x3FF)
                    local cacheIndex3 = bit32.band(bit32.rshift(aux, 0), 0x3FF)
                    
                    if indexCount == 1 then
                        local k1 = proto.kTable[cacheIndex1 + 1];
                        
                        output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = %s", A, tostring(k1.value))
                    elseif indexCount == 2 then
                        local k1 = proto.kTable[cacheIndex1 + 1];
                        local k2 = proto.kTable[cacheIndex2 + 1];
                        
                        output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = %s[\"%s\"]", A, k1.value, tostring(k2.value))
                    elseif indexCount == 3 then
                        local k1 = proto.kTable[cacheIndex1 + 1];
                        local k2 = proto.kTable[cacheIndex2 + 1];
                        local k3 = proto.kTable[cacheIndex3 + 1];
                        
                        output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = %s[\"%s\"][\"%s\"]", A, k1.value, tostring(k2.value), tostring(k3.value))
                    else
                        error("[GETIMPORT] Too many entries");
                    end
                elseif opc == getOpCode("GETGLOBAL") then
                    local k = proto.kTable[aux + 1] or nilValue;
                    output = output .. string.format("v%i = %s", A, tostring(k.value))
                elseif opc == getOpCode("SETGLOBAL") then
                    local k = proto.kTable[aux + 1] or nilValue;
                    output = output .. string.format("%s = v%i", tostring(k.value), A)
                elseif opc == getOpCode("GETTABLE") then
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = v%i[v%i]", A, B, C)
                elseif opc == getOpCode("SETTABLE") then
                    output = output .. string.format("v%i[v%i] = v%i", B, C, A)
                elseif opc == getOpCode("GETTABLEN") then
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = v%i[%i]", A, B, C - 1)
                elseif opc == getOpCode("SETTABLEN") then
                    output = output .. string.format("v%i[%i] = v%i", B, C - 1, A)
                elseif opc == getOpCode("GETTABLEKS") then
                    local k = proto.kTable[aux + 1] or nilValue;
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = v%i[%s]", A, B, (type(k.value) == "string") and ("\"" .. k.value .. "\"") or tostring(k.value))
                elseif opc == getOpCode("SETTABLEKS") then
                    local k = proto.kTable[aux + 1] or nilValue;
                    output = output .. string.format("v%i[%s] = v%i", B, (type(k.value) == "string") and ("\"" .. k.value .. "\"") or tostring(k.value), A)
                elseif opc == getOpCode("NAMECALL") then
                    local k = proto.kTable[aux + 1] or nilValue;
                    nameCall = string.format("v%i:%s", B, tostring(k.value))
                    markedAux = true;
                elseif opc == getOpCode("NFORPREP") then
                    output = output .. string.format("nforprep start - [escape at #%i] -- v%i = iterator", (codeIndex + sBx) + 1, A + 3);
                elseif opc == getOpCode("NFORLOOP") then
                    output = output .. string.format("nforloop end - iterate + goto #%i", codeIndex + sBx);
                elseif opc == getOpCode("PAIRSPREP") then
                    output = output .. string.format("pairsprep start - [escape at #%i] -- v%i = key, v%i = value", (codeIndex + sBx) + 1, A + 3, A + 4);
                elseif opc == getOpCode("PAIRSLOOP") then
                    output = output .. string.format("pairsloop end - iterate + goto #%i", codeIndex + sBx);
                elseif opc == getOpCode("IPAIRSPREP") then
                    output = output .. string.format("ipairsprep start [escape at #%i] -- v%i = key, v%i = value", (codeIndex + sBx) + 1, A + 3, A + 4);
                elseif opc == getOpCode("IPAIRSLOOP") then
                    output = output .. string.format("ipairsloop end - iterate + goto #%i", codeIndex + sBx);
                elseif opc == getOpCode("TFORLOOP") then
                    output = output .. string.format("gforloop - iterate + goto #%i", codeIndex + aux);
                elseif opc == getOpCode("JUMP") then
                    addReference(codeIndex, codeIndex + sBx);
                    output = output .. string.format("goto #%i", codeIndex + sBx);
                elseif opc == getOpCode("JUMPBACK") then
                    addReference(codeIndex, codeIndex + sBx);
                    output = output .. string.format("goto #%i", codeIndex + sBx);
                elseif opc == getOpCode("JUMPX") then
                    addReference(codeIndex, codeIndex + sBx);
                    output = output .. string.format("goto #%i", codeIndex + sAx);
                elseif opc == getOpCode("JUMPIFEQK") then
                    local k = proto.kTable[aux + 1] or nilValue;
                    addReference(codeIndex, codeIndex + sBx);
                    output = output .. string.format("goto #%i if v%i == %s", codeIndex + sBx, A, (type(k.value) == "string") and ("\"" .. k.value .. "\"") or tostring(k.value));
                elseif opc == getOpCode("JUMPIFNOTEQK") then
                    local k = proto.kTable[aux + 1] or nilValue;
                    addReference(codeIndex, codeIndex + sBx);
                    output = output .. string.format("goto #%i if v%i ~= %s", codeIndex + sBx, A, (type(k.value) == "string") and ("\"" .. k.value .. "\"") or tostring(k.value));
                elseif opc == getOpCode("JUMPIF") then
                    addReference(codeIndex, codeIndex + sBx);
                    output = output .. string.format("goto #%i if v%i", codeIndex + sBx, A);
                elseif opc == getOpCode("JUMPIFNOT") then
                    addReference(codeIndex, codeIndex + sBx);
                    output = output .. string.format("goto #%i if not v%i", codeIndex + sBx, A);
                elseif opc == getOpCode("JUMPIFEQ") then
                    addReference(codeIndex, codeIndex + sBx);
                    output = output .. string.format("goto #%i if v%i == v%i", codeIndex + sBx, A, aux);
                elseif opc == getOpCode("JUMPIFNOTEQ") then
                    addReference(codeIndex, codeIndex + sBx);
                    output = output .. string.format("goto #%i if v%i ~= v%i", codeIndex + sBx, A, aux);
                elseif opc == getOpCode("JUMPIFLE") then
                    addReference(codeIndex, codeIndex + sBx);
                    output = output .. string.format("goto #%i if v%i <= v%i", codeIndex + sBx, A, aux);
                elseif opc == getOpCode("JUMPIFNOTLE") then
                    addReference(codeIndex, codeIndex + sBx);
                    output = output .. string.format("goto #%i if v%i > v%i", codeIndex + sBx, A, aux);
                elseif opc == getOpCode("JUMPIFLT") then
                    addReference(codeIndex, codeIndex + sBx);
                    output = output .. string.format("goto #%i if v%i < v%i", codeIndex + sBx, A, aux);
                elseif opc == getOpCode("JUMPIFNOTLT") then
                    addReference(codeIndex, codeIndex + sBx);
                    output = output .. string.format("goto #%i if v%i >= v%i", codeIndex + sBx, A, aux);
                elseif opc == getOpCode("ADD") then
                    output = output .. string.format("v%i = v%i + v%i", A, B, C);
                elseif opc == getOpCode("ADDK") then
                    local k = proto.kTable[C + 1] or nilValue;
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = v%i + %s", A, B, tostring(k.value));
                elseif opc == getOpCode("SUB") then
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = v%i - v%i", A, B, C);
                elseif opc == getOpCode("SUBK") then
                    local k = proto.kTable[C + 1] or nilValue;
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = v%i - %s", A, B, tostring(k.value));
                elseif opc == getOpCode("MUL") then
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = v%i * v%i", A, B, C);
                elseif opc == getOpCode("MULK") then
                    local k = proto.kTable[C + 1] or nilValue;
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = v%i * %s", A, B, tostring(k.value));
                elseif opc == getOpCode("DIV") then
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = v%i / v%i", A, B, C);
                elseif opc == getOpCode("DIVK") then
                    local k = proto.kTable[C + 1] or nilValue;
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = v%i / %s", A, B, tostring(k.value));
                elseif opc == getOpCode("MOD") then
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = v%i %% v%i", A, B, C);
                elseif opc == getOpCode("MODK") then
                    local k = proto.kTable[C + 1] or nilValue;
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = v%i %% %s", A, B, tostring(k.value));
                elseif opc == getOpCode("POW") then
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = v%i ^ v%i", A, B, C);
                elseif opc == getOpCode("POWK") then
                    local k = proto.kTable[C + 1] or nilValue;
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = v%i ^ %s", A, B, tostring(k.value));
                elseif opc == getOpCode("CALL") then
                    if C > 1 then
                        for j = 1, C - 1 do
                            output = output .. string.format("v%i", A + j - 1)
                            if j < C - 1 then output = output .. ", " end
                        end
                        output = output .. " = "
                    elseif C == 0 then
                        output = output .. string.format("v%i, ...", A);
                        output = output .. " = "
                        --for j = 1, proto.maxStackSize do
                        --    output = output .. string.format("v%i", A + j - 1)
                        --    if j < proto.maxStackSize - 1 then output = output .. ", " end
                        --end
                    end
                    if nameCall then
                        output = output .. nameCall .. "(";
                    else
                        output = output .. string.format("v%i(", A)
                    end
                    if B > 1 then
                        if nameCall then
                            for j = 1, B - 2 do
                                output = output .. string.format("v%i", A + 1 + j) -- exclude self
                                if j < B - 2 then output = output .. ", " end
                            end
                        else
                            for j = 1, B - 1 do
                                output = output .. string.format("v%i", A + j)
                                if j < B - 1 then output = output .. ", " end
                            end
                        end
                    elseif B == 0 then
                        output = output .. string.format("v%i, ...", A + 1);
                        --for j = 1, proto.maxStackSize do
                        --    if nameCall then
                        --        output = output .. string.format("v%i", A + 1 + j) -- exclude self
                        --    else
                        --        output = output .. string.format("v%i", A + j)
                        --    end
                        --    if j < proto.maxStackSize - 1 then output = output .. ", " end
                        --end
                    end
                    nameCall = nil;
                    output = output .. ")";
                elseif opc == getOpCode("NEWTABLE") then
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = {}", A)
                elseif opc == getOpCode("DUPTABLE") then
                    local t = proto.kTable[Bx + 1].value;
                    output = output .. (isVarDefined(A) and "" or "local ") .. string.format("v%i = { ", A)
                    for j = 1,t.size do
                        local id = t.ids[j];
                        local k = proto.kTable[id];
                        output = output .. ((type(k.value) == "string") and ("\"" .. k.value .. "\"") or tostring(k.value))
                        if j < t.size then
                            output = output .. ", ";
                        end
                    end
                    output = output .. "}";
                elseif opc == getOpCode("SETLIST") then
                    local fieldSize = aux;
                    output = output .. "\n"
                    for j = 1, C do
                        addTabSpace(depth);
                        output = output .. string.format("v%i[%i] = v%i\n", A, j + fieldSize - 1, B + j - 1);
                    end
                elseif opc == getOpCode("CAPTURE") then
                    markedAux = true;
                elseif opc == getOpCode("NEWCLOSURE") then
                    output = output .. "\n"
                    
                    local nCaptures = 0;
                    for j = codeIndex + 1, proto.sizeCode do
                        if luau.GET_OPCODE(proto.codeTable[j]) ~= getOpCode("CAPTURE") then
                            break
                        else
                            local upvalueIndex = j - codeIndex - 1;
                            local captureType = luau.GETARG_A(proto.codeTable[j]);
                            local captureIndex = luau.GETARG_Bx(proto.codeTable[j]);
                            
                            nCaptures = nCaptures + 1;
                            
                            addTabSpace(depth);
                            if captureType == 0 or captureType == 1 then
                                output = output .. string.format("-- V nested upvalues[%i] = v%i\n", upvalueIndex, captureIndex)
                            elseif captureType == 2 then
                                output = output .. string.format("-- V nested upvalues[%i] = upvalues[%i]\n", upvalueIndex, captureIndex)
                            else
                                error("[NEWCLOSURE] Invalid capture type");
                            end
                        end
                    end
                    codeIndex = codeIndex + nCaptures;
                    
                    addTabSpace(depth);
                    local nextProto = proto.pTable[Bx + 1]
                    if nextProto.source then
                        output = output .. readProto(nextProto, depth)
                        addTabSpace(depth);
                        output = output .. string.format("v%i = ", A) .. nextProto.source
                    else
                        nextProto.source = nil;
                        output = output .. string.format("v%i = ", A) .. readProto(nextProto, depth)
                    end
                elseif opc == getOpCode("DUPCLOSURE") then
                    output = output .. "\n"
                    
                    local nCaptures = 0;
                    for j = codeIndex + 1, proto.sizeCode do
                        if luau.GET_OPCODE(proto.codeTable[j]) ~= getOpCode("CAPTURE") then
                            break
                        else
                            local upvalueIndex = j - codeIndex - 1;
                            local captureType = luau.GETARG_A(proto.codeTable[j]);
                            local captureIndex = luau.GETARG_Bx(proto.codeTable[j]);
                            
                            nCaptures = nCaptures + 1;
                            
                            addTabSpace(depth);
                            if captureType == 0 or captureType == 1 then
                                output = output .. string.format("-- V nested upvalues[%i] = v%i\n", upvalueIndex, captureIndex)
                            elseif captureType == 2 then
                                output = output .. string.format("-- V nested upvalues[%i] = upvalues[%i]\n", upvalueIndex, captureIndex)
                            else
                                error("[DUPCLOSURE] Invalid capture type");
                            end
                        end
                    end
                    codeIndex = codeIndex + nCaptures;
                    
                    addTabSpace(depth);
                    local nextProto = protoTable[proto.kTable[Bx + 1].value]
                    if nextProto.source then
                        output = output .. readProto(nextProto, depth)
                        addTabSpace(depth);
                        output = output .. string.format("v%i = ", A) .. nextProto.source
                    else
                        nextProto.source = nil;
                        output = output .. string.format("v%i = ", A) .. readProto(nextProto, depth)
                    end
                elseif opc == getOpCode("RETURN") then
                    if B > 1 then
                        output = output .. "return ";
                        for j = 1, B - 1 do
                            output = output .. string.format("v%i", A + j)
                            if j < B - 1 then output = output .. ", " end
                        end
                    elseif B == 0 then
                        output = output .. string.format("v%i, ...", A)
                    end
                end
                
                for _,v in pairs(refData) do
                    if v.codeIndex == codeIndex then
                        output = output .. " -- referenced by "
                        for j = 1,#v.refs do
                            output = output .. "#" .. v.refs[j]
                            if j < #v.refs then
                                output = output .. ", "
                            end
                        end
                    end
                end
                
                output = output .. "\n"
            end
            
            codeIndex = codeIndex + 1
        end
        
        depth = depth - 1
        
        addTabSpace(depth)
        output = output .. "end\n"
        return output;
    end
    
    local startDepth = 0;
    output = output .. readProto(mainProto, startDepth)
    
    return output
end
getgenv().decompile = newcclosure(function(script)
    return disassemble(getscriptbytecode(script), false)
end)