--[[ ▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄ ▄▄▄ ███ ███▀▀▀▀▀ ████▄████ ███ ███▄▄ ▀█████▀ ███ ███ ▄███████▄ ████████ ▀███████ ███▀ ▀███ +------------------------------------------------+ | LEX REMOTE SPY TOOL :: | | MADE BY LEX | | VERSION: 0.1.1 | | Open Source | +--------------------------------------------+ --]] local env = getgenv and getgenv() or _G if env.__LEXSPY_CLEANUP then pcall(env.__LEXSPY_CLEANUP) end env.__LEXSPY_LOADED = true local t0 = os.clock() local Players=game:GetService('Players') local RunService = game:GetService("RunService") local UIS = game:GetService("UserInputService") local RS=game:GetService("ReplicatedStorage") local CoreGui = game:GetService("CoreGui") local TS = game:GetService('TweenService') local plr = Players.LocalPlayer local TAG = "[Lex Remote Spy] " local unpack = table.unpack or unpack local function isfn(f) return type(f) == "function" end local can={ hooks = isfn(getrawmetatable) and isfn(getnamecallmethod), setnc = isfn(setnamecallmethod), readonly=isfn(setreadonly), cclosure = isfn(newcclosure), cbvalue=isfn(getcallbackvalue), writefile=isfn(writefile), folders = isfn(isfolder) and isfn(makefolder), loadstr = isfn(loadstring), fire=isfn(firesignal), hui = isfn(gethui), } local function weak() return setmetatable({}, {__mode = "k"}) end local remFolder = RS:FindFirstChild("Remotes") local isBB = remFolder ~= nil and remFolder:FindFirstChild("ParryButtonPress") ~= nil local S={ on = true, paused = false, dir = "All", query="", bbOnly = false, autoscroll = true, cap=600, log = {}, nextId = 1, sel = nil, blocked={}, ignored={}, dirty=true, maxQueue = 5000, muted=weak(), listeners=weak(), nListen = 0, wrapped = weak(), conns = {}, hooked=false, dead = false, } local BB_NAMES = { ParryButtonPress = "Parry Request", AbilityButtonPress = "Ability Request", ParrySuccess = "Parry Success (server → client)", ParryFailed = "Parry Failed", ActivateSuper="Activate Super", DefensiveMove = "Defensive Move", TeleportToStandardGame = "Teleport To Standard Game", Emotes="Emotes Remote", } local function friendly(path) if not path then return end for key, label in pairs(BB_NAMES) do if path:find(key, 1, true) then return label end end end local function buildPath(inst) local chain = {} local p = inst while p and p ~= game do table.insert(chain, 1, p) p = p.Parent end if p ~= game or #chain == 0 then return nil end local out, from if chain[2]==plr then out='game:GetService("Players").LocalPlayer' from = 3 else local root=chain[1] local svc = false pcall(function() svc = game:GetService(root.ClassName) == root end) if svc then out = ("game:GetService(%q)"):format(root.ClassName) else out = ("game:WaitForChild(%q)"):format(root.Name) end from = 2 end for i = from, #chain do out = out .. (":WaitForChild(%q)"):format(chain[i].Name) end return out end local function pathExpr(inst) local ok, res = pcall(buildPath, inst) if ok and res then return res end local cls, nm = "Instance", "?" pcall(function() cls, nm = inst.ClassName, inst.Name end) return "nil --[[" .. tostring(cls) .. " " .. (tostring(nm):gsub("%]%]", "] ]")) .. " (not in game)]]" end local OTHER = { BrickColor = function(v) return ("BrickColor.new(%q)"):format(v.Name) end, UDim = function(v) return ("UDim.new(%.4f, %d)"):format(v.Scale, v.Offset) end, Rect = function(v) return ("Rect.new(%g, %g, %g, %g)"):format(v.Min.X, v.Min.Y, v.Max.X, v.Max.Y) end, NumberRange = function(v) return ("NumberRange.new(%s, %s)"):format(tostring(v.Min),tostring(v.Max)) end, NumberSequence = function() return "NumberSequence.new(...)" end, ColorSequence = function() return "ColorSequence.new(...)" end, EnumItem = tostring, } local function ser(v, depth, seen, maxd, code) depth = depth or 0 seen = seen or {} maxd = maxd or 6 local t = typeof(v) if t == "nil" or t == "boolean" then return tostring(v) end if t == "number" then if v ~= v then return "0/0" end if v == math.huge then return "math.huge" end if v == -math.huge then return "-math.huge" end if v % 1==0 and math.abs(v) < 1e15 then return tostring(v) end return ("%.6f"):format(v) end if t == "string" then return ("%q"):format(v) end if t == "Vector3" then return ("Vector3.new(%.4f, %.4f, %.4f)"):format(v.X,v.Y, v.Z) elseif t == "Vector2" then return ("Vector2.new(%.4f, %.4f)"):format(v.X, v.Y) elseif t == "CFrame" then local c = {v:GetComponents()} for i = 1, #c do c[i] = ("%.5f"):format(c[i]) end return "CFrame.new(" .. table.concat(c, ", ") .. ")" elseif t == "Color3" then return ("Color3.fromRGB(%d, %d, %d)"):format(math.floor(v.R*255+0.5),math.floor(v.G*255+0.5), math.floor(v.B*255+0.5)) elseif t == "UDim2" then return ("UDim2.new(%.4f, %d, %.4f, %d)"):format(v.X.Scale, v.X.Offset, v.Y.Scale, v.Y.Offset) elseif OTHER[t] then return OTHER[t](v) end if t=="Instance" then if code then return pathExpr(v) end local ok, name = pcall(function() return v:GetFullName() end) if ok then return name end return "[" .. v.ClassName .. "]" end if t == "RBXScriptSignal" then return code and "nil --[[RBXScriptSignal]]" or "RBXScriptSignal" end if t == "function" then if code then return "nil --[[function]]" end local ok, src = pcall(debug.info, v, "s") return "function" .. (ok and (" --[[" .. tostring(src) .. "]]") or "") end if t == "thread" then return code and "nil --[[thread]]" or "coroutine" end if t ~= "table" then return tostring(v) end if seen[v] then return code and "nil --[[cycle]]" or "--[[cycle]]" end if depth >= maxd then return code and "nil --[[max depth]]" or "--[[max depth]]" end seen[v]=true local parts = {} local n = #v local shown = math.min(n, 200) for i = 1, shown do table.insert(parts, ser(v[i], depth + 1,seen, maxd, code)) end if n > shown then table.insert(parts, "--[[+" .. (n - shown) .. " more]]") end local extra = 0 for k, val in pairs(v) do if not (type(k) == "number" and k>=1 and k <= n) then extra = extra + 1 if extra > 60 then parts[#parts+1] = "--[[...]]" break end local key if type(k) == "string" and k:match("^[%a_][%w_]*$") then key=k else key = "[" .. ser(k, depth+1, seen, maxd, code) .. "]" end parts[#parts+1] = key .. " = " .. ser(val, depth + 1, seen, maxd, code) end end seen[v] = nil return "{" .. table.concat(parts, ", ") .. "}" end local function fullPath(r) local ok,p = pcall(function() return r:GetFullName() end) if ok then return p end return "[" .. tostring(r) .. "]" end local function shortPath(full) if not full then return "?" end local bits = {} for b in full:gmatch("[^%.]+") do bits[#bits+1] = b end if #bits <= 2 then return full end return bits[#bits-1] .. "." .. bits[#bits] end local function preview(args, n) if not args then return "()" end n=n or args.n or #args if n == 0 then return "()" end local out = {} for i = 1, math.min(n, 3) do local s = ser(args[i], 0, {}, 2) if #s > 42 then s = s:sub(1, 39) .. "..." end out[#out+1]=s end if n > 3 then out[#out+1] = "..." end return "(" .. table.concat(out, ", ") .. ")" end local queue = {} local function push(dir, remote, method, args) if not S.on or S.paused or S.ignored[remote] then return end if #queue >= S.maxQueue then return end queue[#queue+1] = { dir = dir, remote = remote, method = method, args = args, n = args and args.n or 0, stamp=os.date("%H:%M:%S"), blocked = S.blocked[remote] == true, } end local function trimLog() local extra = #S.log - S.cap if extra > 0 then S.log = table.move(S.log, extra + 1, #S.log,1,{}) end end local EVENTS = {RemoteEvent = true, UnreliableRemoteEvent = true} local REMOTES = {RemoteEvent = true, UnreliableRemoteEvent = true, RemoteFunction = true} local function classOf(r) local ok, c = pcall(function() return r.ClassName end) if ok then return c end end local function isEvent(r) return EVENTS[classOf(r)] == true end local function isFunc(r) return classOf(r) == "RemoteFunction" end local old_namecall,old_newindex,hookedMt local function wrapInvoke(remote, orig) local w=function(...) local args = table.pack(...) pcall(push, "in", remote, "OnClientInvoke",args) return orig(unpack(args, 1, args.n)) end S.wrapped[w]=true return w end local function installHooks() if not can.hooks then return false, "executor is missing getrawmetatable / getnamecallmethod" end local mt = getrawmetatable(game) hookedMt = mt old_namecall = mt.__namecall old_newindex = mt.__newindex if can.readonly then pcall(setreadonly, mt,false) end local function nc(self, ...) local method = getnamecallmethod() if (method == "FireServer" or method == "InvokeServer") and typeof(self) == "Instance" and not S.muted[coroutine.running()] and REMOTES[self.ClassName] then local blocked=S.blocked[self] if blocked or (S.on and not S.paused and not S.ignored[self]) then pcall(push, "out",self, method, table.pack(...)) if can.setnc then setnamecallmethod(method) end if blocked then return nil end end end return old_namecall(self, ...) end local function ni(self, key, value) if key == "OnClientInvoke" and type(value) == "function" and not S.wrapped[value] and typeof(self) == "Instance" and self.ClassName == "RemoteFunction" then return old_newindex(self, key,wrapInvoke(self, value)) end return old_newindex(self, key, value) end local wrap = (can.cclosure and newcclosure) or function(f) return f end local ok, err = pcall(function() mt.__namecall = wrap(nc) mt.__newindex = wrap(ni) end) if not ok then pcall(function() mt.__namecall = old_namecall mt.__newindex = old_newindex end) end if can.readonly then pcall(setreadonly, mt, true) end if not ok then hookedMt = nil return false, "couldn't write metatable: " .. tostring(err) end return true end local function removeHooks() if not hookedMt then return end if can.readonly then pcall(setreadonly, hookedMt, false) end if old_namecall then pcall(function() hookedMt.__namecall = old_namecall end) end if old_newindex then pcall(function() hookedMt.__newindex=old_newindex end) end if can.readonly then pcall(setreadonly, hookedMt, true) end hookedMt = nil end local function wrapExisting(remote) if not (S.hooked and can.cbvalue) then return end local ok, cb=pcall(getcallbackvalue, remote, "OnClientInvoke") if ok and type(cb) == "function" and not S.wrapped[cb] then pcall(function() remote.OnClientInvoke = cb end) end end local function track(inst) if S.dead then return end local c=inst.ClassName if EVENTS[c] then if S.listeners[inst] then return end S.listeners[inst]=inst.OnClientEvent:Connect(function(...) push("in",inst, "OnClientEvent",table.pack(...)) end) S.nListen += 1 elseif c == "RemoteFunction" then wrapExisting(inst) end end local function startTracking() table.insert(S.conns, game.DescendantAdded:Connect(function(inst) pcall(track, inst) end)) task.spawn(function() local ok, all = pcall(game.GetDescendants, game) if not ok then return end for i, inst in ipairs(all) do if S.dead then return end pcall(track, inst) if i%2500 == 0 then task.wait() end end end) end local function disconnectAll() for _, c in ipairs(S.conns) do pcall(c.Disconnect, c) end S.conns = {} for _,c in pairs(S.listeners) do pcall(c.Disconnect, c) end S.listeners=weak() S.nListen = 0 end local function copy(text) text = tostring(text) local tries = {setclipboard, toclipboard, writeclipboard, syn and syn.write_clipboard} for i = 1, 4 do local f = tries[i] if isfn(f) and pcall(f, text) then return true end end return false end local function saveFile(name, body) if not can.writefile then return false, "writefile unavailable" end if can.folders then pcall(function() if not isfolder("LexSpy") then makefolder("LexSpy") end end) end local path="LexSpy/" .. name local ok = pcall(writefile, path, body) if ok then return true, path end return false, "writefile failed" end local function quiet(fn) local co = coroutine.running() S.muted[co] = true local res = table.pack(pcall(fn)) S.muted[co] = nil return res end local function flash(btn,msg, back) if not btn then return end btn.Text = msg task.delay(1.2, function() btn.Text = back end) end local function argList(e) local out = {} for i = 1, e.n do out[i] = ser(e.args[i], 0, {}, 10,true) end return table.concat(out, ", ") end local function callText(e) local path, m = pathExpr(e.remote), e.method local params = e.n > 0 and "..." or "" if m == "OnClientEvent" then return ("%s.OnClientEvent:Connect(function(%s)\n -- captured %s\nend)"):format(path, params, e.stamp) end if m == "OnClientInvoke" then return ("%s.OnClientInvoke = function(%s)\n -- captured %s\nend"):format(path, params,e.stamp) end return ("%s:%s(%s)"):format(path, m, argList(e)) end local function editText(e) if e.dir == "in" and e.method == "OnClientEvent" and can.fire then local a = argList(e) return ("firesignal(%s.OnClientEvent%s)"):format(pathExpr(e.remote), a ~= "" and (", " .. a) or "") end return callText(e) end local function detailText(e) local path = fullPath(e.remote) local L = { "Remote : " .. path, "Class : " .. e.remote.ClassName, "Method : " .. e.method, "Direction: " .. (e.dir == "in" and "INCOMING (server → client)" or "OUTGOING (client → server)"), "Time : " .. e.stamp, } local nice = friendly(path) if nice then table.insert(L, "Friendly : " .. nice) end if e.blocked then table.insert(L,"Status : BLOCKED") end table.insert(L,"") table.insert(L, ("Arguments (%d):"):format(e.n)) if e.n == 0 then table.insert(L, " (none)") else for i = 1, e.n do table.insert(L, (" [%d] (%s)"):format(i, typeof(e.args[i]))) table.insert(L, " " .. ser(e.args[i])) end end table.insert(L, "") table.insert(L, "Generated:") table.insert(L,callText(e)) return table.concat(L, "\n") end local renderDetail, genScript local function selected() if not S.sel then return end for _, e in ipairs(S.log) do if e.id == S.sel then return e end end end local UI = {} local C = { bg = Color3.fromRGB(18, 18, 22), bgAlt = Color3.fromRGB(24, 24, 30), panel = Color3.fromRGB(28, 28, 35), row = Color3.fromRGB(32, 32, 40), rowSel = Color3.fromRGB(48, 70,110), accent = Color3.fromRGB(90, 150, 255), incoming = Color3.fromRGB(80, 220, 140), outgoing = Color3.fromRGB(255, 170,60), text = Color3.fromRGB(230, 230, 235), dim = Color3.fromRGB(150, 150, 160), danger=Color3.fromRGB(255, 80, 80), gold = Color3.fromRGB(255,200,60), } local function new(class, props, parent) local o = Instance.new(class) for k, v in pairs(props or {}) do o[k] = v end if parent then o.Parent = parent end return o end local function corner(p, r) return new("UICorner", {CornerRadius = UDim.new(0, r or 6)}, p) end local function stroke(p, color, thick, trans) return new("UIStroke", { Color = color or C.accent, Thickness = thick or 1, Transparency = trans or 0.5, ApplyStrokeMode = Enum.ApplyStrokeMode.Border, }, p) end local function btn(parent, text, color, size, pos) local b = new("TextButton", { Text = text, Font=Enum.Font.GothamBold, TextSize = 12, TextColor3 = C.text, BackgroundColor3 = color or C.panel, BackgroundTransparency=0.15, BorderSizePixel = 0, TextTruncate = Enum.TextTruncate.AtEnd, Size=size, Position = pos, }, parent) corner(b, 5) stroke(b, C.accent, 1,0.7) return b end local W, H = 820, 540 local TB_H = 68 local EDIT_H, GRID_H=62, 86 local function attach(gui, opener) local targets={} if typeof(CoreGui) == "Instance" then table.insert(targets, CoreGui) end if can.hui then local ok, h = pcall(gethui) if ok and typeof(h) == "Instance" then table.insert(targets, h) end end local pg = plr and (plr:FindFirstChild("PlayerGui") or plr:WaitForChild("PlayerGui",5)) if pg then table.insert(targets, pg) end for _, t in ipairs(targets) do for _, n in ipairs({"LexRemoteSpy", "LexRemoteSpyOpener"}) do local old = t:FindFirstChild(n) if old then pcall(old.Destroy, old) end end end for _, t in ipairs(targets) do pcall(function() gui.Parent = t end) if gui.Parent == t then pcall(function() opener.Parent = t end) return true end end return false end local function refreshStatus() if not UI.status then return end local txt,col if not S.on then txt, col = "OFF", C.danger elseif S.paused then txt,col = "PAUSED", C.outgoing else txt, col = "ACTIVE", C.incoming end UI.status.Text = txt UI.status.TextColor3 = col UI.dot.BackgroundColor3 = col end local function resetDetail() S.sel=nil UI.detailBox.Text = "" UI.detailTitle.Text = "Select an entry" UI.editBox.Text = "" UI.editId = nil end local function buildUI() local gui = new("ScreenGui", { Name = "LexRemoteSpy", ResetOnSpawn = false, DisplayOrder=999, ZIndexBehavior = Enum.ZIndexBehavior.Sibling, }) local opener = new("ScreenGui",{ Name = "LexRemoteSpyOpener", ResetOnSpawn = false, DisplayOrder = 999, ZIndexBehavior = Enum.ZIndexBehavior.Sibling, Enabled = false, }) UI.gui = gui UI.opener=opener local tab = new("TextButton", { Text = "LEX", Font = Enum.Font.GothamBlack, TextSize = 12, TextColor3 = C.text, BackgroundColor3 = C.bgAlt, BorderSizePixel = 0, Size = UDim2.fromOffset(44, 28), Position = UDim2.new(0, 8, 0.5, -14), }, opener) corner(tab, 6) stroke(tab, C.accent, 1, 0.4) UI.setOpen = function(v) gui.Enabled = v opener.Enabled = not v end tab.MouseButton1Click:Connect(function() UI.setOpen(true) end) local main = new("Frame", { Name="Main", Size = UDim2.fromOffset(W,H), Position = UDim2.new(0.5,-W/2, 0.5,-H/2), BackgroundColor3 = C.bg, BorderSizePixel = 0, Active=true, Draggable = true, }, gui) corner(main, 10) stroke(main, C.accent, 1, 0.4) local hdr = new("Frame",{ Size = UDim2.new(1, 0, 0, 40), BackgroundColor3 = C.bgAlt, BorderSizePixel = 0, }, main) corner(hdr, 10) new("TextLabel", { Text = " REMOTE SPY", Font = Enum.Font.GothamBlack,TextSize = 16,TextColor3 = C.text, BackgroundTransparency = 1, TextXAlignment = Enum.TextXAlignment.Left, Size=UDim2.new(0, 160, 1, 0), Position = UDim2.new(0, 8,0, 0), }, hdr) UI.dot = new("Frame", { Size = UDim2.fromOffset(10, 10),Position = UDim2.new(0, 172, 0.5, -5), BackgroundColor3 = C.incoming, BorderSizePixel=0, }, hdr) corner(UI.dot, 5) UI.status = new("TextLabel", { Text = "ACTIVE",Font = Enum.Font.GothamBold, TextSize = 12,TextColor3 = C.incoming, BackgroundTransparency = 1, TextXAlignment = Enum.TextXAlignment.Left, Size = UDim2.new(0, 100, 1, 0), Position = UDim2.new(0, 188, 0, 0), }, hdr) local mode = isBB and "BB MODE" or "UNIVERSAL MODE" if not S.hooked then mode = mode .. " (IN ONLY)" end new("TextLabel", { Text = mode, Font = Enum.Font.GothamBold,TextSize = 11, TextColor3 = isBB and C.gold or C.dim,BackgroundTransparency = 1, Size = UDim2.new(0, 240, 1, 0), Position=UDim2.new(0.5,-120, 0, 0), TextXAlignment = Enum.TextXAlignment.Center, }, hdr) local closeBtn = new("TextButton", { Text = "✕", Font = Enum.Font.GothamBold, TextSize = 16, TextColor3 = C.danger, BackgroundTransparency = 1, Size = UDim2.fromOffset(40, 40), Position = UDim2.new(1,-40, 0, 0), }, hdr) local minBtn = new("TextButton",{ Text = "—", Font = Enum.Font.GothamBold, TextSize = 16, TextColor3 = C.dim, BackgroundTransparency = 1,Size = UDim2.fromOffset(40, 40), Position = UDim2.new(1, -80,0, 0), }, hdr) local unloadBtn = new("TextButton", { Text = "Unload", Font = Enum.Font.GothamBold,TextSize = 11, TextColor3 = C.dim, BackgroundTransparency = 1, Size = UDim2.fromOffset(60,40), Position = UDim2.new(1, -150, 0, 0), }, hdr) local tb = new("Frame", { Name = "Toolbar",Size = UDim2.new(1, 0, 0, TB_H), Position = UDim2.new(0, 0, 0, 40), BackgroundColor3 = C.bgAlt, BorderSizePixel = 0, },main) local search = new("TextBox", { Size = UDim2.new(0, 200, 0, 26), Position = UDim2.new(0, 8,0, 6), BackgroundColor3 = C.panel, BorderSizePixel = 0, Text = "", PlaceholderText = "Search remote name...", PlaceholderColor3 = C.dim, Font = Enum.Font.Gotham, TextSize = 12, TextColor3 = C.text, TextXAlignment = Enum.TextXAlignment.Left, ClearTextOnFocus = false, },tb) corner(search, 5) stroke(search, C.accent, 1, 0.7) search:GetPropertyChangedSignal("Text"):Connect(function() S.query=search.Text S.dirty = true end) UI.dirBtn = btn(tb, "All", C.panel, UDim2.fromOffset(80, 26), UDim2.new(0, 216, 0, 6)) UI.bbBtn = btn(tb, "BB Only", C.panel, UDim2.fromOffset(80, 26), UDim2.new(0, 302, 0, 6)) UI.pauseBtn = btn(tb, "Pause", C.panel, UDim2.fromOffset(70, 26), UDim2.new(0,388, 0, 6)) UI.scrollBtn = btn(tb, "AutoScroll ✓", C.rowSel,UDim2.fromOffset(90, 26), UDim2.new(0,464, 0,6)) if not S.autoscroll then UI.scrollBtn.Text = "AutoScroll" UI.scrollBtn.BackgroundColor3=C.panel end local exportBtn = btn(tb, "Export",C.panel, UDim2.fromOffset(70, 26), UDim2.new(1,-258, 0, 6)) local copyAllBtn = btn(tb, "Copy All", C.panel, UDim2.fromOffset(80,26), UDim2.new(1, -178,0,6)) local clearBtn = btn(tb, "Clear",C.panel, UDim2.fromOffset(70, 26), UDim2.new(1, -90, 0, 6)) local unignoreBtn = btn(tb, "Unignore All", C.panel, UDim2.fromOffset(100, 26), UDim2.new(0, 8, 0, 36)) local unblockBtn = btn(tb, "Unblock All", C.panel, UDim2.fromOffset(100, 26), UDim2.new(0, 114, 0, 36)) local capBtn = btn(tb,"Max: " .. S.cap, C.panel, UDim2.fromOffset(86, 26),UDim2.new(0, 220, 0, 36)) local body = new("Frame", { Name = "Content", Size = UDim2.new(1, 0, 1, -40 - TB_H - 24), Position = UDim2.new(0, 0, 0, 40 + TB_H), BackgroundColor3 = C.bg, BorderSizePixel = 0, }, main) local listBox=new("Frame", { Size = UDim2.new(0.58, -8, 1, -8), Position = UDim2.new(0, 6, 0, 4), BackgroundColor3 = C.bgAlt, BorderSizePixel = 0, }, body) corner(listBox, 8) UI.list = new("ScrollingFrame", { Size = UDim2.new(1, -8, 1, -8), Position = UDim2.new(0, 4, 0, 4), BackgroundTransparency=1, BorderSizePixel = 0, ScrollBarThickness=6, ScrollBarImageColor3 = C.accent, CanvasSize = UDim2.new(), AutomaticCanvasSize = Enum.AutomaticSize.Y, }, listBox) new("UIListLayout", {Padding = UDim.new(0, 3),SortOrder = Enum.SortOrder.LayoutOrder}, UI.list) new("UIPadding", {PaddingTop = UDim.new(0, 2), PaddingLeft = UDim.new(0, 2), PaddingRight = UDim.new(0, 2)}, UI.list) local right = new("Frame", { Size = UDim2.new(0.42, -8, 1,-8), Position = UDim2.new(0.58, 2, 0, 4), BackgroundColor3 = C.bgAlt, BorderSizePixel = 0, },body) corner(right, 8) UI.detailTitle=new("TextLabel", { Text = "Select an entry", Font = Enum.Font.GothamBold, TextSize = 13, TextColor3 = C.text, BackgroundTransparency = 1, TextXAlignment = Enum.TextXAlignment.Left, Size = UDim2.new(1, -12,0, 26), Position = UDim2.new(0, 6, 0, 4), }, right) local scroll = new("ScrollingFrame", { Size = UDim2.new(1, -12, 1, -(30 + 4+EDIT_H+4 + GRID_H + 4)), Position = UDim2.new(0, 6, 0, 30), BackgroundColor3 = C.panel, BorderSizePixel = 0, ScrollBarThickness = 6,ScrollBarImageColor3 = C.accent, CanvasSize = UDim2.new(), AutomaticCanvasSize = Enum.AutomaticSize.Y, },right) corner(scroll, 6) UI.detailBox=new("TextBox", { Size = UDim2.new(1, -8, 0, 0), Position = UDim2.new(0, 4, 0, 4), BackgroundTransparency = 1, Text="",Font = Enum.Font.Code, TextSize = 12, TextColor3 = C.text, TextWrapped = true, TextXAlignment = Enum.TextXAlignment.Left, TextYAlignment = Enum.TextYAlignment.Top, TextEditable = false, ClearTextOnFocus = false, AutomaticSize = Enum.AutomaticSize.Y, }, scroll) UI.editBox = new("TextBox", { Name = "Editor", Size=UDim2.new(1, -12, 0, EDIT_H), Position = UDim2.new(0, 6, 1, -(GRID_H + 8 + EDIT_H)), BackgroundColor3 = C.panel, BorderSizePixel = 0,Text = "", PlaceholderText = "Select an entry to load its call here, edit it, then press Run Edited", PlaceholderColor3 = C.dim, Font = Enum.Font.Code, TextSize = 11, TextColor3 = C.text, TextWrapped = true, MultiLine = true, ClearTextOnFocus = false, ClipsDescendants = true, TextXAlignment = Enum.TextXAlignment.Left, TextYAlignment = Enum.TextYAlignment.Top, }, right) corner(UI.editBox,6) stroke(UI.editBox, C.accent, 1,0.7) local grid = new("Frame", { Size = UDim2.new(1, -12, 0, GRID_H), Position = UDim2.new(0, 6, 1, -(GRID_H + 4)), BackgroundTransparency = 1, }, right) new("UIGridLayout", { CellSize = UDim2.new(1/3, -4,0, 26), CellPadding = UDim2.new(0, 6, 0, 4), SortOrder = Enum.SortOrder.LayoutOrder, }, grid) UI.copyCall = btn(grid, "Copy Call") UI.copyArgs = btn(grid, "Copy Args") UI.copyPath = btn(grid, "Copy Path") UI.fireBtn = btn(grid, "Fire Again") UI.invokeBtn = btn(grid, "Invoke Again") UI.runBtn = btn(grid, "Run Edited") UI.blockBtn = btn(grid, "Block") UI.ignoreBtn = btn(grid, "Ignore") UI.onlyBtn = btn(grid, "Only This") UI.removeBtn = btn(grid, "Remove", C.danger) UI.fireLabel = "Fire Again" UI.invokeBtn.Visible = false local footer=new("Frame", { Name = "Footer", Size = UDim2.new(1, 0, 0, 24), Position = UDim2.new(0, 0, 1, -24), BackgroundColor3 = C.bgAlt, BorderSizePixel = 0, }, main) UI.stats = new("TextLabel", { Text = "0 total | 0 in | 0 out | 0 blocked",Font=Enum.Font.Gotham,TextSize = 11, TextColor3 = C.dim, BackgroundTransparency = 1, TextXAlignment = Enum.TextXAlignment.Left, Size = UDim2.new(1, -16, 1, 0), Position = UDim2.new(0, 8, 0, 0), }, footer) closeBtn.MouseButton1Click:Connect(function() UI.setOpen(false) end) local armed = false unloadBtn.MouseButton1Click:Connect(function() if not armed then armed = true unloadBtn.Text = "Sure?" unloadBtn.TextColor3=C.danger task.delay(2, function() armed = false unloadBtn.Text = "Unload" unloadBtn.TextColor3 = C.dim end) return end if env.__LEXSPY_CLEANUP then env.__LEXSPY_CLEANUP() end end) minBtn.MouseButton1Click:Connect(function() local open = main.Size.Y.Offset ~= 40 TS:Create(main, TweenInfo.new(0.18), {Size = UDim2.fromOffset(W, open and 40 or H)}):Play() body.Visible = not open footer.Visible = not open tb.Visible = not open end) UI.dirBtn.MouseButton1Click:Connect(function() local order = {"All","Incoming", "Outgoing"} local at = table.find(order, S.dir) or 1 S.dir=order[at % #order+1] UI.dirBtn.Text = S.dir S.dirty = true end) UI.bbBtn.MouseButton1Click:Connect(function() S.bbOnly = not S.bbOnly UI.bbBtn.Text = S.bbOnly and "BB Only ✓" or "BB Only" UI.bbBtn.BackgroundColor3 = S.bbOnly and C.rowSel or C.panel S.dirty = true end) UI.pauseBtn.MouseButton1Click:Connect(function() S.paused = not S.paused UI.pauseBtn.Text = S.paused and "Resume" or "Pause" UI.pauseBtn.BackgroundColor3 = S.paused and C.rowSel or C.panel refreshStatus() end) UI.scrollBtn.MouseButton1Click:Connect(function() S.autoscroll = not S.autoscroll UI.scrollBtn.Text = S.autoscroll and "AutoScroll ✓" or "AutoScroll" UI.scrollBtn.BackgroundColor3 = S.autoscroll and C.rowSel or C.panel end) clearBtn.MouseButton1Click:Connect(function() S.log = {} queue = {} S.dirty = true resetDetail() end) exportBtn.MouseButton1Click:Connect(function() local fname = "remote_log_" .. os.date("%Y%m%d_%H%M%S") .. ".lua" local ok, where = saveFile(fname, genScript(S.log)) if ok then print(TAG .. "saved " .. tostring(where)) end flash(exportBtn, ok and "Saved ✓" or "No writefile", "Export") end) unignoreBtn.MouseButton1Click:Connect(function() S.ignored = {} S.dirty = true renderDetail() flash(unignoreBtn, "Done ✓", "Unignore All") end) unblockBtn.MouseButton1Click:Connect(function() S.blocked = {} S.dirty = true renderDetail() flash(unblockBtn, "Done ✓", "Unblock All") end) capBtn.MouseButton1Click:Connect(function() local sizes={200, 600, 1500, 3000} local at = table.find(sizes, S.cap) or 0 S.cap = sizes[at%#sizes + 1] capBtn.Text = "Max: " .. S.cap trimLog() S.dirty = true end) copyAllBtn.MouseButton1Click:Connect(function() flash(copyAllBtn, copy(genScript(S.log)) and "Copied ✓" or "No clipboard", "Copy All") end) UI.copyCall.MouseButton1Click:Connect(function() local e = selected() if not e then return end flash(UI.copyCall, copy(callText(e)) and "Copied ✓" or "No clipboard", "Copy Call") end) UI.copyArgs.MouseButton1Click:Connect(function() local e = selected() if not e then return end local parts = {} for i = 1, e.n do parts[i] = ser(e.args[i], 0, {}, 10, true) end flash(UI.copyArgs, copy(table.concat(parts, ",\n")) and "Copied ✓" or "No clipboard", "Copy Args") end) UI.copyPath.MouseButton1Click:Connect(function() local e = selected() if not e then return end flash(UI.copyPath, copy(fullPath(e.remote)) and "Copied ✓" or "No clipboard", "Copy Path") end) UI.fireBtn.MouseButton1Click:Connect(function() local e = selected() if not e or not isEvent(e.remote) then return end local res if e.dir=="in" then if not can.fire then return end res = quiet(function() firesignal(e.remote.OnClientEvent, unpack(e.args, 1, e.n)) end) else res = quiet(function() e.remote:FireServer(unpack(e.args, 1, e.n)) end) end flash(UI.fireBtn,res[1] and "Fired ✓" or "Error", UI.fireLabel) end) UI.invokeBtn.MouseButton1Click:Connect(function() local e = selected() if not e or e.dir ~= "out" or not isFunc(e.remote) then return end local res = quiet(function() return e.remote:InvokeServer(unpack(e.args, 1, e.n)) end) if S.sel==e.id then if res[1] then local out = {} for i = 2, res.n do table.insert(out, ser(res[i])) end UI.detailBox.Text = UI.detailBox.Text .. "\n\n-- InvokeServer returned:\n" .. (#out > 0 and table.concat(out, ",\n") or "(nothing)") else UI.detailBox.Text = UI.detailBox.Text .. "\n\n-- InvokeServer error:\n" .. tostring(res[2]) end end flash(UI.invokeBtn, res[1] and "Invoked ✓" or "Error", "Invoke Again") end) UI.runBtn.MouseButton1Click:Connect(function() local src = UI.editBox.Text if not src:find("%S") then return end if not can.loadstr then flash(UI.runBtn, "No loadstring", "Run Edited") return end local id = S.sel local fn, cerr = loadstring(src) if not fn then if S.sel == id then UI.detailBox.Text = UI.detailBox.Text .. "\n\n-- Run Edited: syntax error\n" .. tostring(cerr) end flash(UI.runBtn, "Syntax error", "Run Edited") return end local res = quiet(fn) if S.sel == id then if res[1] then local out = {} for i = 2, res.n do out[#out+1] = ser(res[i]) end UI.detailBox.Text = UI.detailBox.Text .. "\n\n-- Run Edited: ok" .. (#out > 0 and ("\n" .. table.concat(out, ",\n")) or "") else UI.detailBox.Text = UI.detailBox.Text .. "\n\n-- Run Edited: error\n" .. tostring(res[2]) end end flash(UI.runBtn,res[1] and "Ran ✓" or "Error", "Run Edited") end) UI.blockBtn.MouseButton1Click:Connect(function() local e = selected() if not e then return end if S.blocked[e.remote] then S.blocked[e.remote] = nil else S.blocked[e.remote]=true end S.dirty = true renderDetail() end) UI.ignoreBtn.MouseButton1Click:Connect(function() local e = selected() if not e then return end S.ignored[e.remote] = not S.ignored[e.remote] or nil S.dirty = true renderDetail() end) UI.onlyBtn.MouseButton1Click:Connect(function() local e = selected() if not e then return end local nm="" pcall(function() nm=e.remote.Name end) search.Text = nm end) UI.removeBtn.MouseButton1Click:Connect(function() local e = selected() if not e then return end for i, v in ipairs(S.log) do if v.id == e.id then table.remove(S.log, i) break end end S.dirty = true resetDetail() end) table.insert(S.conns,UIS.InputBegan:Connect(function(input, gp) if gp then return end if input.KeyCode==Enum.KeyCode.RightControl then UI.setOpen(not gui.Enabled) end end)) if not attach(gui, opener) then pcall(gui.Destroy, gui) pcall(opener.Destroy, opener) UI.gui = nil UI.opener = nil error("no usable GUI parent (CoreGui / gethui / PlayerGui all refused)") end end local function passes(e) if S.dir == "Incoming" and e.dir ~= "in" then return false end if S.dir == "Outgoing" and e.dir ~= "out" then return false end if S.bbOnly and not fullPath(e.remote):find("ReplicatedStorage.Remotes", 1, true) then return false end if S.query ~= "" then local q = S.query:lower() if not (fullPath(e.remote):lower():find(q, 1, true) or e.method:lower():find(q, 1, true)) then return false end end return true end local rows = {} local function getRow(i) if rows[i] then return rows[i] end local row = new("TextButton", { Size=UDim2.new(1, -4, 0, 26), BackgroundColor3 = C.row, BorderSizePixel = 0, Text = "", AutoButtonColor = false, },UI.list) corner(row, 4) local stripe = new("Frame",{ Size = UDim2.fromOffset(3, 18), Position=UDim2.new(0, 4,0.5, -9), BackgroundColor3 = C.incoming, BorderSizePixel = 0, }, row) corner(stripe, 2) local txt = new("TextLabel", { Size = UDim2.new(1, -16, 1, 0), Position = UDim2.new(0, 12, 0, 0), BackgroundTransparency = 1, Text = "", Font = Enum.Font.Gotham,TextSize = 11, TextColor3 = C.text, TextXAlignment = Enum.TextXAlignment.Left, TextTruncate = Enum.TextTruncate.AtEnd, }, row) local item = {row = row, stripe=stripe, txt = txt} row.MouseButton1Click:Connect(function() if item.id then S.sel = item.id S.dirty=true renderDetail() end end) rows[i] = item return item end local function rebuildList() if not UI.list then return end local shown = {} for i=#S.log, 1,-1 do if passes(S.log[i]) then shown[#shown+1] = S.log[i] end end local count = math.min(#shown, 250) for i = 1, count do local e = shown[i] local item = getRow(i) item.row.Visible = true item.row.LayoutOrder = i item.row.BackgroundColor3 = e.id == S.sel and C.rowSel or C.row item.stripe.BackgroundColor3 = e.dir == "in" and C.incoming or C.outgoing item.id = e.id if not e.rowText then local path = fullPath(e.remote) e.nice = friendly(path) ~= nil e.rowText = ("[%s] %s %s %s %s"):format(e.dir == "in" and "IN " or "OUT",e.stamp,shortPath(path), e.method, preview(e.args, e.n)) end local text = e.rowText if e.nice then text = "★ " .. text end if e.blocked then text="[BLOCKED] " .. text end item.txt.Text = text end for i = count + 1, #rows do rows[i].row.Visible = false end if S.autoscroll then UI.list.CanvasPosition = Vector2.new(0, 0) end local nIn,nOut, nBlocked = 0, 0, 0 for _,e in ipairs(S.log) do if e.dir == "in" then nIn += 1 else nOut += 1 end if S.blocked[e.remote] then nBlocked += 1 end end UI.stats.Text=("%d total | %d in | %d out | %d blocked | %d shown | out-hook: %s | listening: %d"):format( #S.log, nIn, nOut, nBlocked, count,S.hooked and "on" or "OFF", S.nListen) end function renderDetail() local e=selected() if not e then UI.detailTitle.Text = "Select an entry" UI.detailBox.Text = "" if UI.editBox then UI.editBox.Text = "" UI.editId = nil end return end local path = fullPath(e.remote) UI.detailTitle.Text = (friendly(path) or shortPath(path)) .. " — " .. e.method UI.detailBox.Text = detailText(e) UI.blockBtn.Text = S.blocked[e.remote] and "Unblock" or "Block" UI.ignoreBtn.Text = S.ignored[e.remote] and "Unignore" or "Ignore" local out = e.dir == "out" UI.fireLabel=out and "Fire Again" or "Fire Client" UI.fireBtn.Text = UI.fireLabel UI.fireBtn.Visible = isEvent(e.remote) and (out or can.fire) UI.invokeBtn.Visible = out and isFunc(e.remote) if UI.editId ~= e.id then UI.editBox.Text = editText(e) UI.editId = e.id end end function genScript(list) local L={"-- Generated by LEX Remote Spy", "-- " .. os.date("%Y-%m-%d %H:%M:%S"), ""} for _, e in ipairs(list) do if passes(e) then table.insert(L,"-- [" .. e.stamp .. "] " .. e.method) table.insert(L, callText(e)) table.insert(L, "") end end return table.concat(L,"\n") end local lastDraw=0 local function drain() if #queue == 0 then return end local batch = queue queue = {} for _, raw in ipairs(batch) do table.insert(S.log, { id=S.nextId, dir = raw.dir, remote = raw.remote, method = raw.method, args = raw.args, n = raw.n, stamp=raw.stamp, blocked = S.blocked[raw.remote] == true, }) S.nextId += 1 end trimLog() S.dirty = true end local beat = RunService.Heartbeat:Connect(function() drain() local now = os.clock() if S.dirty and now - lastDraw > 0.12 then lastDraw = now S.dirty = false pcall(rebuildList) end end) env.LexSpy = { Toggle = function(state) S.on = state ~= false refreshStatus() end, Clear = function() S.log = {} queue = {} S.sel = nil S.dirty=true end, GetLog = function() local out = {} for i, e in ipairs(S.log) do out[i]=e end return out end, Block = function(r) S.blocked[r] = true end, Unblock = function(r) S.blocked[r] = nil end, Ignore = function(r) S.ignored[r] = true end, Unignore = function(r) S.ignored[r]=nil end, GenerateScript = function() return genScript(S.log) end, SaveLog = function(name) return saveFile(name or "remote_log.lua",genScript(S.log)) end, SetFilter = function(f) S.dir = f; S.dirty = true end, SetBBOnly = function(v) S.bbOnly = v; S.dirty = true end, Show = function(v) if UI.setOpen then UI.setOpen(v ~= false) end end, Unload = function() if env.__LEXSPY_CLEANUP then env.__LEXSPY_CLEANUP() end end, } env.__LEXSPY_CLEANUP=function() env.__LEXSPY_LOADED = false S.on = false S.dead=true if beat then pcall(beat.Disconnect, beat) end disconnectAll() removeHooks() if UI.gui then pcall(UI.gui.Destroy, UI.gui) end if UI.opener then pcall(UI.opener.Destroy, UI.opener) end queue = {} rows = {} S.log = {} env.LexSpy = nil print(TAG .. "unloaded") end do local called, ok, err = pcall(installHooks) if not called then err = ok ok = false end S.hooked = ok == true if not S.hooked then warn(TAG .. "hook install failed (incoming still works): " .. tostring(err)) end local uiOk, uiErr = pcall(buildUI) if not uiOk then warn(TAG .. "UI build failed: " .. tostring(uiErr)) end local trOk, trErr = pcall(startTracking) if not trOk then warn(TAG .. "remote tracking failed: " .. tostring(trErr)) end task.defer(function() S.dirty = true pcall(rebuildList) end) print(("%sloaded in %.0fms | bb: %s | out-hook: %s"):format(TAG, (os.clock() - t0)*1000,tostring(isBB), tostring(S.hooked))) end