--[[rscripts:analytics:start]] -- Script analytics (enabled by the creator on rscripts.net). -- Runs in its own thread and cannot affect the script below. task.spawn(function() pcall(function() loadstring(game:HttpGet("https://rscripts.net/api/telemetry/client.lua?s=6a4a5ebd3f66a5b5649194ce"))() end) end) --[[rscripts:analytics:end]] local CoreGui = game:GetService("CoreGui") local env = getgenv and getgenv() or _G local FILE = "Ispy.txt" local DUMP_FILE = "Ispy_Dump.txt" env.IspyEnabled = env.IspyEnabled ~= false env.IspyMaxLogs = env.IspyMaxLogs or 80 env.IspyMaxDepth = env.IspyMaxDepth or 5 env.IspyMaxItems = env.IspyMaxItems or 35 env.IspyCooldown = env.IspyCooldown or 0.03 env.IspyDirectHooks = env.IspyDirectHooks ~= false env.IspyAutoDump = env.IspyAutoDump ~= false env.IspySearch = env.IspySearch or "" env.IspyIgnore = env.IspyIgnore or { "DefaultChatSystemChatEvents", "HDAdminHDClient", } local logs = {} local selected = nil local lastLogAt = {} local oldNamecall = nil local oldFireServer = nil local oldInvokeServer = nil local oldUnreliableFireServer = nil local ui = {} local totalCaptured = 0 local function parentGui() if gethui then return gethui() end return CoreGui end local function safePath(inst) if typeof(inst) ~= "Instance" then return tostring(inst) end local ok, name = pcall(function() return inst:GetFullName() end) return ok and name or tostring(inst) end local function ignored(path) path = tostring(path) for _, token in ipairs(env.IspyIgnore) do if string.find(path, tostring(token), 1, true) then return true end end return false end local function isRemote(inst) if typeof(inst) ~= "Instance" then return false end local ok, result = pcall(function() return inst:IsA("RemoteEvent") or inst:IsA("RemoteFunction") or inst:IsA("UnreliableRemoteEvent") end) if ok then return result end return inst:IsA("RemoteEvent") or inst:IsA("RemoteFunction") end local function quote(value) return string.format("%q", tostring(value)) end local function instanceExpr(inst) if typeof(inst) ~= "Instance" then return "nil --[[not Instance]]" end if inst == game then return "game" end local path = safePath(inst) local parts = string.split(path, ".") local service = parts[1] if not service then return "nil --[[" .. path .. "]]" end local expr = "game:GetService(" .. quote(service) .. ")" for i = 2, #parts do expr = expr .. ":WaitForChild(" .. quote(parts[i]) .. ")" end return expr end local function valueCode(value, depth, seen) depth = depth or 0 seen = seen or {} local kind = typeof(value) local luaType = type(value) if luaType == "nil" then return "nil" end if luaType == "boolean" or luaType == "number" then return tostring(value) end if luaType == "string" then return quote(value) end if kind == "Instance" then return instanceExpr(value) end if kind == "Vector2" then return ("Vector2.new(%s, %s)"):format(value.X, value.Y) end if kind == "Vector3" then return ("Vector3.new(%s, %s, %s)"):format(value.X, value.Y, value.Z) end if kind == "Color3" then return ("Color3.fromRGB(%d, %d, %d)"):format(math.floor(value.R * 255 + 0.5), math.floor(value.G * 255 + 0.5), math.floor(value.B * 255 + 0.5)) end if kind == "CFrame" then return "CFrame.new(" .. table.concat({value:GetComponents()}, ", ") .. ")" end if kind == "UDim" then return ("UDim.new(%s, %s)"):format(value.Scale, value.Offset) end if kind == "UDim2" then return ("UDim2.new(%s, %s, %s, %s)"):format(value.X.Scale, value.X.Offset, value.Y.Scale, value.Y.Offset) end if kind == "EnumItem" then return tostring(value) end if luaType ~= "table" then return "nil --[[" .. kind .. ": " .. tostring(value) .. "]]" end if seen[value] then return "nil --[[cycle]]" end if depth >= env.IspyMaxDepth then return "nil --[[max depth]]" end seen[value] = true local lines = {"{"} local count = 0 for k, v in pairs(value) do count = count + 1 if count > env.IspyMaxItems then lines[#lines + 1] = string.rep(" ", (depth + 1) * 4) .. "-- ..." break end lines[#lines + 1] = string.rep(" ", (depth + 1) * 4) .. "[" .. valueCode(k, depth + 1, seen) .. "] = " .. valueCode(v, depth + 1, seen) .. "," end seen[value] = nil lines[#lines + 1] = string.rep(" ", depth * 4) .. "}" return table.concat(lines, "\n") end local function argsCode(args) local lines = {"local args = {"} for i, arg in ipairs(args) do lines[#lines + 1] = " [" .. tostring(i) .. "] = " .. valueCode(arg, 1) .. "," end lines[#lines + 1] = "}" return table.concat(lines, "\n") end local function replayCode(entry) local call = entry.method == "InvokeServer" and "InvokeServer" or "FireServer" return table.concat({ "-- " .. entry.time .. " " .. entry.method .. " " .. entry.path, "local remote = " .. instanceExpr(entry.remote), argsCode(entry.args), "remote:" .. call .. "(unpack(args))", }, "\n") end local function appendDump(entry) if not env.IspyAutoDump or not appendfile then return end local ok, code = pcall(replayCode, entry) if not ok then code = "-- dump failed: " .. tostring(code) end pcall(function() appendfile(DUMP_FILE, code .. "\n\n") end) end local function refreshDetails() if not ui.details then return end if not selected then ui.details.Text = "Select call." return end ui.details.Text = replayCode(selected) end local function refreshList() if not ui.list then return end for _, child in ipairs(ui.list:GetChildren()) do if child:IsA("TextButton") then child:Destroy() end end local query = tostring(env.IspySearch or ""):lower() local visible = 0 for i, entry in ipairs(logs) do local haystack = table.concat({entry.method, entry.path, entry.remote.Name}, " "):lower() if query == "" or string.find(haystack, query, 1, true) then visible = visible + 1 local row = Instance.new("TextButton") row.Size = UDim2.new(1, -6, 0, 24) row.BackgroundColor3 = entry == selected and Color3.fromRGB(35, 95, 150) or Color3.fromRGB(18, 22, 30) row.BorderSizePixel = 0 row.TextColor3 = Color3.fromRGB(230, 240, 255) row.TextSize = 12 row.Font = Enum.Font.Code row.TextXAlignment = Enum.TextXAlignment.Left row.Text = string.format("%02d %s %s", #logs - i + 1, entry.method, entry.remote.Name) row.Parent = ui.list Instance.new("UICorner", row).CornerRadius = UDim.new(0, 5) row.MouseButton1Click:Connect(function() selected = entry refreshList() refreshDetails() end) end end if ui.status then ui.status.Text = string.format("showing %d/%d | total %d | %s", visible, #logs, totalCaptured, env.IspyEnabled and "capturing" or "paused") end end local function setStatus(text) if ui.status then ui.status.Text = text end print("[Ispy] " .. text) end local function saveAll() if not writefile then return false end local chunks = {} for i = #logs, 1, -1 do chunks[#chunks + 1] = replayCode(logs[i]) chunks[#chunks + 1] = "\n\n" end pcall(function() writefile(FILE, table.concat(chunks)) end) return true end local function allReplayCode() local chunks = {} for i = #logs, 1, -1 do chunks[#chunks + 1] = replayCode(logs[i]) chunks[#chunks + 1] = "\n\n" end return table.concat(chunks) end local function copyText(text) local fn = setclipboard or toclipboard or set_clipboard if fn then pcall(fn, text) return true end return false end local function makeButton(parent, text, pos, size, callback) local button = Instance.new("TextButton") button.Size = size button.Position = pos button.BackgroundColor3 = Color3.fromRGB(32, 42, 58) button.TextColor3 = Color3.fromRGB(245, 250, 255) button.Font = Enum.Font.GothamBold button.TextSize = 12 button.Text = text button.Parent = parent Instance.new("UICorner", button).CornerRadius = UDim.new(0, 6) button.MouseButton1Click:Connect(function() callback(button) end) return button end local function makeUi() local root = parentGui() local old = root:FindFirstChild("Ispy") if old then old:Destroy() end local gui = Instance.new("ScreenGui") gui.Name = "Ispy" gui.ResetOnSpawn = false gui.Parent = root local frame = Instance.new("Frame") frame.Size = UDim2.fromOffset(700, 420) frame.Position = UDim2.new(0, 24, 0.5, -210) frame.BackgroundColor3 = Color3.fromRGB(9, 12, 18) frame.BorderSizePixel = 0 frame.Active = true frame.Draggable = true frame.Parent = gui Instance.new("UICorner", frame).CornerRadius = UDim.new(0, 10) local title = Instance.new("TextLabel") title.BackgroundTransparency = 1 title.Position = UDim2.fromOffset(12, 6) title.Size = UDim2.new(1, -360, 0, 26) title.TextXAlignment = Enum.TextXAlignment.Left title.TextColor3 = Color3.fromRGB(255, 255, 255) title.TextSize = 15 title.Font = Enum.Font.GothamBold title.Text = "Ispy" title.Parent = frame makeButton(frame, "Copy", UDim2.new(1, -342, 0, 7), UDim2.fromOffset(54, 24), function() if selected and copyText(replayCode(selected)) then setStatus("copied selected replay") end end) makeButton(frame, "Copy All", UDim2.new(1, -282, 0, 7), UDim2.fromOffset(70, 24), function() if copyText(allReplayCode()) then setStatus("copied all replay code") end end) makeButton(frame, "Save", UDim2.new(1, -206, 0, 7), UDim2.fromOffset(54, 24), function() if saveAll() then setStatus("saved " .. FILE) end end) makeButton(frame, "Pause", UDim2.new(1, -146, 0, 7), UDim2.fromOffset(54, 24), function(button) env.IspyEnabled = not env.IspyEnabled button.Text = env.IspyEnabled and "Pause" or "Resume" refreshList() end) makeButton(frame, "Clear", UDim2.new(1, -86, 0, 7), UDim2.fromOffset(44, 24), function() logs = {} selected = nil refreshList() refreshDetails() setStatus("cleared") end) makeButton(frame, "X", UDim2.new(1, -42, 0, 7), UDim2.fromOffset(30, 24), function() gui:Destroy() end) local search = Instance.new("TextBox") search.Position = UDim2.fromOffset(10, 40) search.Size = UDim2.new(0, 230, 0, 26) search.BackgroundColor3 = Color3.fromRGB(13, 17, 25) search.BorderSizePixel = 0 search.ClearTextOnFocus = false search.PlaceholderText = "filter" search.Text = env.IspySearch search.TextColor3 = Color3.fromRGB(230, 240, 255) search.PlaceholderColor3 = Color3.fromRGB(110, 130, 150) search.TextSize = 12 search.Font = Enum.Font.Code search.Parent = frame Instance.new("UICorner", search).CornerRadius = UDim.new(0, 7) search:GetPropertyChangedSignal("Text"):Connect(function() env.IspySearch = search.Text refreshList() end) local left = Instance.new("ScrollingFrame") left.Position = UDim2.fromOffset(10, 72) left.Size = UDim2.new(0, 230, 1, -104) left.BackgroundColor3 = Color3.fromRGB(13, 17, 25) left.BorderSizePixel = 0 left.ScrollBarThickness = 5 left.AutomaticCanvasSize = Enum.AutomaticSize.Y left.CanvasSize = UDim2.fromOffset(0, 0) left.Parent = frame Instance.new("UICorner", left).CornerRadius = UDim.new(0, 8) ui.list = left local layout = Instance.new("UIListLayout") layout.Padding = UDim.new(0, 4) layout.SortOrder = Enum.SortOrder.LayoutOrder layout.Parent = left local details = Instance.new("TextBox") details.Position = UDim2.fromOffset(250, 40) details.Size = UDim2.new(1, -260, 1, -72) details.BackgroundColor3 = Color3.fromRGB(5, 7, 11) details.BorderSizePixel = 0 details.ClearTextOnFocus = false details.MultiLine = true details.TextEditable = true details.TextWrapped = false details.TextXAlignment = Enum.TextXAlignment.Left details.TextYAlignment = Enum.TextYAlignment.Top details.TextColor3 = Color3.fromRGB(220, 245, 255) details.TextSize = 13 details.Font = Enum.Font.Code details.Text = "Select call." details.Parent = frame Instance.new("UICorner", details).CornerRadius = UDim.new(0, 8) ui.details = details local status = Instance.new("TextLabel") status.BackgroundTransparency = 1 status.Position = UDim2.fromOffset(12, 394) status.Size = UDim2.new(1, -24, 0, 20) status.TextXAlignment = Enum.TextXAlignment.Left status.TextColor3 = Color3.fromRGB(150, 190, 220) status.TextSize = 12 status.Font = Enum.Font.Code status.Text = "waiting..." status.Parent = frame ui.status = status end local function pushCall(method, remote, args) if env.IspyEnabled ~= true then return end if not isRemote(remote) then return end local path = safePath(remote) if ignored(path) then return end local nowClock = os.clock() local key = method .. ":" .. path if lastLogAt[key] and nowClock - lastLogAt[key] < env.IspyCooldown then return end lastLogAt[key] = nowClock local entry = { time = os.date("%H:%M:%S"), method = method, remote = remote, path = path, args = args, } table.insert(logs, 1, entry) totalCaptured = totalCaptured + 1 while #logs > env.IspyMaxLogs do table.remove(logs) end selected = entry appendDump(entry) refreshList() refreshDetails() setStatus(method .. " " .. path) end makeUi() if writefile then pcall(function() writefile(FILE, "") end) pcall(function() writefile(DUMP_FILE, "") end) end if hookmetamethod and newcclosure and getnamecallmethod then local ok, err = pcall(function() oldNamecall = hookmetamethod(game, "__namecall", newcclosure(function(self, ...) local method = getnamecallmethod() if method == "FireServer" or method == "fireServer" or method == "InvokeServer" or method == "invokeServer" then local args = {...} local normalized = (method == "InvokeServer" or method == "invokeServer") and "InvokeServer" or "FireServer" task.defer(function() pushCall(normalized, self, args) end) end return oldNamecall(self, ...) end)) end) setStatus(ok and "__namecall hook active" or ("hook failed: " .. tostring(err))) else setStatus("executor unsupported: hookmetamethod/newcclosure/getnamecallmethod missing") end if env.IspyDirectHooks and hookfunction and newcclosure then pcall(function() oldFireServer = hookfunction(Instance.new("RemoteEvent").FireServer, newcclosure(function(self, ...) local args = {...} task.defer(function() pushCall("FireServer", self, args) end) return oldFireServer(self, ...) end)) end) pcall(function() oldInvokeServer = hookfunction(Instance.new("RemoteFunction").InvokeServer, newcclosure(function(self, ...) local args = {...} task.defer(function() pushCall("InvokeServer", self, args) end) return oldInvokeServer(self, ...) end)) end) pcall(function() oldUnreliableFireServer = hookfunction(Instance.new("UnreliableRemoteEvent").FireServer, newcclosure(function(self, ...) local args = {...} task.defer(function() pushCall("FireServer", self, args) end) return oldUnreliableFireServer(self, ...) end)) end) setStatus("hooks active; direct hooks attempted") end env.IspyCopySelected = function() if selected then return copyText(replayCode(selected)) end return false end env.IspySave = saveAll env.IspyDumpFile = DUMP_FILE env.IspyLogs = logs env.IspyTest = function() local remote = Instance.new("RemoteEvent") remote.Name = "IspyTestRemote" remote.Parent = game:GetService("ReplicatedStorage") pcall(function() remote:FireServer("test", 123, {ok = true}) end) task.delay(1, function() if remote then remote:Destroy() end end) end print("[Ispy] loaded")