--[[
    wdec - Luau decompiler
    written by luavm on discord
	a couple things fixed by angelwar3 on discord
]]

---

local fmt = string.format -- string.format is annoying to write

type luau_op = {
	name: string,
	aux: boolean
}

local opcodes: { luau_op } = { -- Opcode table. Every opcode is ordered as their Opcode number (not Roblox encoded)
	{name = "NOP", aux = false},
	{name = "BREAK", aux = false},
	{name = "LOADNIL", aux = false},
	{name = "LOADB", aux = false},
	{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 = false},
	{name = "JUMPIFNOT", aux = false},
	{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 = false},
	{name = "FORNLOOP", aux = false},
	{name = "FORGLOOP", aux = true},
	{name = "FORGPREP_INEXT", aux = false},
	{name = "DEP_FORGLOOP_INEXT", aux = false},
	{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 = "DEP_JUMPIFEQK", aux = false},
	{name = "DEP_JUMPIFNOTEQK", aux = false},
	{name = "FASTCALL1", aux = false},
	{name = "FASTCALL2", aux = true},
	{name = "FASTCALL2K", aux = true},
	{name = "FORGPREP", aux = false},
	{name = "JUMPXEQKNIL", aux = true},
	{name = "JUMPXEQKB", aux = true},
	{name = "JUMPXEQKN", aux = true},
	{name = "JUMPXEQKS", aux = true},
	{name = "IDIV", aux = false},
	{name = "IDIVK", aux = false},
}

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
}

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

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 t_bytes: luau_bytes = BytesToTable(s_bytes)

	local bytes_index = 1

	local function GetByte(): number
		bytes_index += 1
		return t_bytes[bytes_index - 1] and t_bytes[bytes_index - 1] or 0
	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)
		)
	end

	local function GetVarInt(): number
		local value: number = 0
		local shift: number = 0

		repeat
			local byte: number? = GetByte()
			if not byte then break end
			value = bit32.bor(value, bit32.lshift(bit32.band(byte :: number, 127), shift))
			shift = shift + 7
		until (byte :: number) < 128

		return value
	end

	local bytecode_version = GetByte() -- Should always be 3 or 4 apparently
	if bytecode_version ~= 3 and bytecode_version ~= 4 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()
	end

	local string_table = {}
	local strings_sz = GetVarInt()
	for _ = 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
		local len = GetVarInt()
		local buffer = ""
		for _ = 1, len do
			buffer ..= string.char(GetByte())
		end
		table.insert(string_table, buffer)
	end

	local protos: { luau_proto } = {}
	local protos_sz = GetVarInt()
	for _ = 1, protos_sz do -- Iterate over every proto, including main.
		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
		}

		table.insert(protos, 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

		-- Not present in Roblox bytecode bcause -g1

		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()
	}
	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: { BuiltInFunction } = { -- ORDER SENSITIVE
	{ Path = { "assert" }},
	-- math
	{ 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" }},
	-- bit32
	{ 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 = { "type" }},

	-- string
	{ Path = { "string", "byte" }},
	{ Path = { "string", "char" }},
	{ Path = { "string", "len" }},

	{ Path = { "typeof" }},

	-- string
	{ Path = { "string", "sub" }},

	-- math
	{ Path = { "math", "clamp" }},
	{ Path = { "math", "sign" }},
	{ Path = { "math", "round" }},

	-- raw*
	{ Path = { "rawset" }},
	{ Path = { "rawget" }},
	{ Path = { "rawequal" }},

	-- table
	{ Path = { "table", "insert" }},
	{ Path = { "table", "unpack" }},

	-- vector constructor
	{ Path = { "Vector3", "new" }},

	-- bit32.count
	{ Path = { "bit32", "countlz" }},
	{ Path = { "bit32", "countrz" }},

	-- select(_, ...)
	{ Path = { "select" }},

	{ Path = { "rawlen" }},

	{ Path = { "bit32", "extract" }},

	{ Path = { "getmetatable" }},
	{ Path = { "setmetatable" }},

	{ Path = { "tonumber" }},
	{ Path = { "tostring" }},
}

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 decompile(bytecode: luau_bytecode): string
	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({ -- Below is a list of instructions which can possibly write to a register
			-- TODO: finish this list; it fucks up shit if it isn't
			"GETGLOBAL", "GETUPVAL", "NEWCLOSURE",
			"ADD", "SUB", "MUL", "DIV", "ADDK",
			"SUBK", "MULK", "DIVK", "MOD", "POW",
			"MODK", "POWK", "AND", "OR", "ANDK",
			"ORK", "CONCAT", "NOT", "MINUS", "LENGTH",
			"NEWTABLE", "DUPTABLE", "GETVARARGS",
			"LOADKX", "IDIV", "IDIVK", "LOADN",
			"LOADK", "LOADB", "MOVE", "LOADNIL",
			"DUPCLOSURE", "CALL", "SETLIST",
			"FORNPREP", "GETTABLEKS"
			}, instruction.name) then
			return false -- return false if the instruction doesn't write (JUMP, etc)
		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 some fucking reason, 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 the fuck this 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"})) -- Stupidly 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
				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 = ""
				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 .. "("
				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 = {},
        ParamTypes = {},
		IsVarArg = false,
		Name = "main",
		UpValues = {},
		IsGlobal = false
	}

	local ast: ClosureNode = DecompileClosure(main_closure)
	local source: string = TranspileClosure(ast, 0)
	return string.sub(source, 1, string.len(source) - 1)
end 

getgenv().decompile = function(script: LocalScript | ModuleScript)
	local bytes: string = getscriptbytecode(script)
	local bytecode: luau_bytecode = deserialize(bytes, true)
	return "-- Decompiled with SirHurt V6 \n\n" .. decompile(bytecode)
end