Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- LocalScript: Full Remote Tester with Scrollable Console + Multiservice + Resizable UI
- -- Place in StarterGui. Development and debugging only.
- local Players = game:GetService("Players")
- local ReplicatedStorage = game:GetService("ReplicatedStorage")
- local Workspace = game:GetService("Workspace")
- local Lighting = game:GetService("Lighting")
- local SoundService = game:GetService("SoundService")
- local StarterGui = game:GetService("StarterGui")
- local StarterPack = game:GetService("StarterPack")
- local StarterPlayer = game:GetService("StarterPlayer")
- local TextChatService = game:GetService("TextChatService")
- local UserInputService = game:GetService("UserInputService")
- local player = Players.LocalPlayer
- local playerGui = player:WaitForChild("PlayerGui")
- -- =========================
- -- UTILITY FUNCTIONS
- -- =========================
- -- Trim whitespace
- local function trim(s) return s:match("^%s*(.-)%s*$") end
- -- Pretty print tables recursively
- local function pretty(v, depth)
- depth = depth or 0
- local t = typeof(v)
- if t == "string" then
- return '"' .. v .. '"'
- elseif t == "number" or t == "boolean" or v == nil then
- return tostring(v)
- elseif t == "table" then
- if depth > 4 then return "{...}" end
- local parts = {}
- for k,val in pairs(v) do
- table.insert(parts, string.rep(" ", depth+1) ..
- "["..pretty(k,depth+1).."] = "..pretty(val,depth+1))
- end
- return "{\n"..table.concat(parts,",\n").."\n"..string.rep(" ",depth).."}"
- else
- return "<"..t..">"
- end
- end
- -- Parse a single token
- local function parseToken(tok)
- tok = trim(tok)
- if tok == "" then return nil end
- local s = tok:match('^"(.*)"$') or tok:match("^'(.*)'$")
- if s then return s end
- if tok == "true" then return true end
- if tok == "false" then return false end
- if tok == "nil" then return nil end
- local n = tonumber(tok)
- if n then return n end
- return tok
- end
- -- Parse CSV arguments into Lua values
- local function parseArgs(csv)
- csv = tostring(csv or "")
- if csv == "" then return {} end
- local out, cur, inQuotes, quoteChar = {}, "", false, nil
- for i = 1, #csv do
- local c = csv:sub(i,i)
- if (c == '"' or c == "'") and not inQuotes then
- inQuotes = true
- quoteChar = c
- cur ..= c
- elseif c == quoteChar and inQuotes then
- inQuotes = false
- quoteChar = nil
- cur ..= c
- elseif c == "," and not inQuotes then
- table.insert(out, parseToken(cur))
- cur = ""
- else
- cur ..= c
- end
- end
- if cur ~= "" then table.insert(out, parseToken(cur)) end
- return out
- end
- -- =========================
- -- REMOTE COLLECTION
- -- =========================
- local function safeGetDescendants(parent)
- local ok, descendants = pcall(function()
- return parent:GetDescendants()
- end)
- return ok and descendants or {}
- end
- local function collectRemotes()
- local remotes = {}
- local services = {
- ReplicatedStorage, Players, Workspace, StarterGui, StarterPack,
- StarterPlayer, playerGui, Lighting, SoundService, ChatService,
- TextChatService, CoreGui, UserGameSettings, VRService
- }
- for _, svc in ipairs(services) do
- for _, inst in ipairs(safeGetDescendants(svc)) do
- if inst:IsA("RemoteEvent") or inst:IsA("RemoteFunction") then
- table.insert(remotes, inst)
- end
- end
- end
- -- Include Backpack remotes
- if player:FindFirstChild("Backpack") then
- for _, inst in ipairs(safeGetDescendants(player.Backpack)) do
- if inst:IsA("RemoteEvent") or inst:IsA("RemoteFunction") then
- table.insert(remotes, inst)
- end
- end
- end
- -- Include Character tool remotes
- if player.Character then
- for _, inst in ipairs(safeGetDescendants(player.Character)) do
- if inst:IsA("RemoteEvent") or inst:IsA("RemoteFunction") then
- table.insert(remotes, inst)
- end
- end
- end
- -- Include StarterPack items
- for _, inst in ipairs(safeGetDescendants(StarterPack)) do
- if inst:IsA("RemoteEvent") or inst:IsA("RemoteFunction") then
- table.insert(remotes, inst)
- end
- end
- table.sort(remotes, function(a,b) return a:GetFullName() < b:GetFullName() end)
- return remotes
- end
- -- =========================
- -- GUI SETUP
- -- =========================
- local screenGui = Instance.new("ScreenGui")
- screenGui.Name = "RemoteTesterGUI"
- screenGui.ResetOnSpawn = false
- screenGui.Parent = playerGui
- screenGui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
- local main = Instance.new("Frame")
- main.Size = UDim2.new(0.45,0,0.6,0)
- main.Position = UDim2.new(0.5,-260,0.15,0)
- main.AnchorPoint = Vector2.new(0.5,0)
- main.BackgroundColor3 = Color3.fromRGB(34,34,34)
- main.BorderSizePixel = 0
- main.Parent = screenGui
- -- UIScale for optional zooming
- local uiScale = Instance.new("UIScale")
- uiScale.Scale = 1
- uiScale.Parent = main
- -- Titlebar
- local titlebar = Instance.new("Frame")
- titlebar.Size = UDim2.new(1,0,0,34)
- titlebar.BackgroundColor3 = Color3.fromRGB(24,24,24)
- titlebar.BorderSizePixel = 0
- titlebar.Parent = main
- local title = Instance.new("TextLabel")
- title.Size = UDim2.new(0.7,0,1,0)
- title.Position = UDim2.new(0,8,0,0)
- title.BackgroundTransparency = 1
- title.Text = "ReplicatedStorage Remote Tester + Scrollable Console"
- title.TextColor3 = Color3.fromRGB(230,230,230)
- title.Font = Enum.Font.SourceSansSemibold
- title.TextSize = 14
- title.TextXAlignment = Enum.TextXAlignment.Left
- title.Parent = titlebar
- local btnClose = Instance.new("TextButton")
- btnClose.Size = UDim2.new(0,36,0,22)
- btnClose.Position = UDim2.new(1,-44,0,6)
- btnClose.Text = "X"
- btnClose.Parent = titlebar
- local btnMin = Instance.new("TextButton")
- btnMin.Size = UDim2.new(0,36,0,22)
- btnMin.Position = UDim2.new(1,-88,0,6)
- btnMin.Text = "_"
- btnMin.Parent = titlebar
- -- Content frame
- local content = Instance.new("Frame")
- content.Size = UDim2.new(1,-16,1,-50)
- content.Position = UDim2.new(0,8,0,40)
- content.BackgroundTransparency = 1
- content.Parent = main
- -- Panels
- local leftPanel = Instance.new("Frame")
- leftPanel.Size = UDim2.new(0.55,0,1,0)
- leftPanel.Position = UDim2.new(0,0,0,0)
- leftPanel.BackgroundColor3 = Color3.fromRGB(28,28,28)
- leftPanel.BorderSizePixel = 0
- leftPanel.Parent = content
- local rightPanel = Instance.new("Frame")
- rightPanel.Size = UDim2.new(0.43,0,1,0)
- rightPanel.Position = UDim2.new(0.57,0,0,0)
- rightPanel.BackgroundColor3 = Color3.fromRGB(30,30,30)
- rightPanel.BorderSizePixel = 0
- rightPanel.Parent = content
- -- Scrollable console
- local consoleFrame = Instance.new("Frame")
- consoleFrame.Size = UDim2.new(1,0,0.25,0)
- consoleFrame.Position = UDim2.new(0,0,0.75,0)
- consoleFrame.BackgroundColor3 = Color3.fromRGB(20,20,20)
- consoleFrame.BorderSizePixel = 0
- consoleFrame.Parent = main
- local consoleScroll = Instance.new("ScrollingFrame")
- consoleScroll.Size = UDim2.new(1,-8,1,-8)
- consoleScroll.Position = UDim2.new(0,4,0,4)
- consoleScroll.BackgroundTransparency = 1
- consoleScroll.CanvasSize = UDim2.new(0,0,0,0)
- consoleScroll.ScrollBarThickness = 6
- consoleScroll.Parent = consoleFrame
- local consoleLayout = Instance.new("UIListLayout")
- consoleLayout.Padding = UDim.new(0,2)
- consoleLayout.Parent = consoleScroll
- -- =========================
- -- LOGGING FUNCTION
- -- =========================
- local function logConsole(msg,isError)
- msg = tostring(msg)
- local lines = msg:split("\n")
- for _,line in ipairs(lines) do
- local label = Instance.new("TextLabel")
- label.Size = UDim2.new(1,0,0,16)
- label.BackgroundTransparency = 1
- label.Font = Enum.Font.Code
- label.TextSize = 12
- label.TextXAlignment = Enum.TextXAlignment.Left
- label.Text = os.date("[%H:%M:%S] ") .. line
- label.TextColor3 = isError and Color3.fromRGB(255,80,80) or Color3.fromRGB(0,255,0)
- label.Parent = consoleScroll
- end
- consoleScroll.CanvasSize = UDim2.new(0,0,0,consoleLayout.AbsoluteContentSize.Y + 4)
- consoleScroll.CanvasPosition = Vector2.new(0, consoleScroll.CanvasSize.Y.Offset)
- end
- -- =========================
- -- REMOTES UI
- -- =========================
- local remotesLabel = Instance.new("TextLabel")
- remotesLabel.Size = UDim2.new(1,0,0,26)
- remotesLabel.Position = UDim2.new(0,8,0,6)
- remotesLabel.BackgroundTransparency = 1
- remotesLabel.Text = "Remotes in accessible services"
- remotesLabel.TextColor3 = Color3.fromRGB(220,220,220)
- remotesLabel.Font = Enum.Font.SourceSansSemibold
- remotesLabel.TextSize = 14
- remotesLabel.TextXAlignment = Enum.TextXAlignment.Left
- remotesLabel.Parent = leftPanel
- local refreshBtn = Instance.new("TextButton")
- refreshBtn.Size = UDim2.new(0,80,0,24)
- refreshBtn.Position = UDim2.new(1,-88,0,6)
- refreshBtn.Text = "Refresh"
- refreshBtn.Parent = leftPanel
- local remotesList = Instance.new("ScrollingFrame")
- remotesList.Size = UDim2.new(1,-16,1,-50)
- remotesList.Position = UDim2.new(0,8,0,38)
- remotesList.CanvasSize = UDim2.new(0,0,0,0)
- remotesList.ScrollBarThickness = 6
- remotesList.BackgroundTransparency = 1
- remotesList.Parent = leftPanel
- local uiListLayout = Instance.new("UIListLayout")
- uiListLayout.Padding = UDim.new(0,6)
- uiListLayout.Parent = remotesList
- local selectedLabel = Instance.new("TextLabel")
- selectedLabel.Size = UDim2.new(1,-16,0,24)
- selectedLabel.Position = UDim2.new(0,8,0,8)
- selectedLabel.BackgroundTransparency = 1
- selectedLabel.Text = "Selected: (none)"
- selectedLabel.TextColor3 = Color3.fromRGB(230,230,230)
- selectedLabel.Font = Enum.Font.SourceSans
- selectedLabel.TextSize = 14
- selectedLabel.TextXAlignment = Enum.TextXAlignment.Left
- selectedLabel.Parent = rightPanel
- local argsLabel = Instance.new("TextLabel")
- argsLabel.Size = UDim2.new(1,-16,0,18)
- argsLabel.Position = UDim2.new(0,8,0,36)
- argsLabel.BackgroundTransparency = 1
- argsLabel.Text = "Arguments (comma separated)"
- argsLabel.TextColor3 = Color3.fromRGB(200,200,200)
- argsLabel.Font = Enum.Font.SourceSans
- argsLabel.TextSize = 12
- argsLabel.TextXAlignment = Enum.TextXAlignment.Left
- argsLabel.Parent = rightPanel
- local argsBox = Instance.new("TextBox")
- argsBox.Size = UDim2.new(1,-16,0,36)
- argsBox.Position = UDim2.new(0,8,0,56)
- argsBox.ClearTextOnFocus = false
- argsBox.PlaceholderText = [["hello",5,true]]
- argsBox.Text = ""
- argsBox.Parent = rightPanel
- local fireButton = Instance.new("TextButton")
- fireButton.Size = UDim2.new(0,200,0,36)
- fireButton.Position = UDim2.new(0,8,0,100)
- fireButton.Text = "Fire / Invoke Selected Remote"
- fireButton.Parent = rightPanel
- -- =========================
- -- DRAGGING AND RESIZING
- -- =========================
- local dragging, dragStartPos, guiStartPos
- local resizing, resizeDir, resizeStartPos, resizeStartSize
- local edgeThreshold = 8
- -- Determine which edge/corner is being hovered
- local function getResizeDirection(mousePos)
- local absPos = main.AbsolutePosition
- local absSize = main.AbsoluteSize
- local rightEdge = absPos.X + absSize.X
- local bottomEdge = absPos.Y + absSize.Y
- local nearLeft = math.abs(mousePos.X - absPos.X) <= edgeThreshold
- local nearRight = math.abs(mousePos.X - rightEdge) <= edgeThreshold
- local nearTop = math.abs(mousePos.Y - absPos.Y) <= edgeThreshold
- local nearBottom = math.abs(mousePos.Y - bottomEdge) <= edgeThreshold
- if nearLeft and nearTop then return "TopLeft"
- elseif nearRight and nearTop then return "TopRight"
- elseif nearLeft and nearBottom then return "BottomLeft"
- elseif nearRight and nearBottom then return "BottomRight"
- elseif nearLeft then return "Left"
- elseif nearRight then return "Right"
- elseif nearTop then return "Top"
- elseif nearBottom then return "Bottom"
- end
- return nil
- end
- -- Dragging
- titlebar.InputBegan:Connect(function(input)
- if input.UserInputType == Enum.UserInputType.MouseButton1 then
- if resizing then return end
- dragging = true
- dragStartPos = input.Position
- guiStartPos = main.Position
- input.Changed:Connect(function()
- if input.UserInputState == Enum.UserInputState.End then
- dragging = false
- end
- end)
- end
- end)
- UserInputService.InputChanged:Connect(function(input)
- -- Dragging
- if dragging and input.UserInputType == Enum.UserInputType.MouseMovement then
- if resizing then return end
- local delta = input.Position - dragStartPos
- main.Position = UDim2.new(guiStartPos.X.Scale, guiStartPos.X.Offset + delta.X,
- guiStartPos.Y.Scale, guiStartPos.Y.Offset + delta.Y)
- end
- -- Resizing
- if resizing and input.UserInputType == Enum.UserInputType.MouseMovement then
- local delta = input.Position - resizeStartPos
- local newSize = resizeStartSize
- local newPos = main.Position
- if resizeDir == "Right" or resizeDir == "TopRight" or resizeDir == "BottomRight" then
- newSize = UDim2.new(0, math.max(240, resizeStartSize.X.Offset + delta.X), 0, newSize.Y.Offset)
- elseif resizeDir == "Left" or resizeDir == "TopLeft" or resizeDir == "BottomLeft" then
- local newWidth = math.max(240, resizeStartSize.X.Offset - delta.X)
- newSize = UDim2.new(0, newWidth, 0, newSize.Y.Offset)
- newPos = UDim2.new(newPos.X.Scale, newPos.X.Offset + delta.X, newPos.Y.Scale, newPos.Y.Offset)
- end
- if resizeDir == "Bottom" or resizeDir == "BottomLeft" or resizeDir == "BottomRight" then
- newSize = UDim2.new(0, newSize.X.Offset, 0, math.max(180, resizeStartSize.Y.Offset + delta.Y))
- elseif resizeDir == "Top" or resizeDir == "TopLeft" or resizeDir == "TopRight" then
- local newHeight = math.max(180, resizeStartSize.Y.Offset - delta.Y)
- newSize = UDim2.new(0, newSize.X.Offset, 0, newHeight)
- newPos = UDim2.new(newPos.X.Scale, newPos.X.Offset, newPos.Y.Scale, newPos.Y.Offset + delta.Y)
- end
- main.Size = newSize
- main.Position = newPos
- end
- end)
- main.InputBegan:Connect(function(input)
- if input.UserInputType == Enum.UserInputType.MouseButton1 then
- local dir = getResizeDirection(input.Position)
- if dir then
- resizing = true
- resizeDir = dir
- resizeStartPos = input.Position
- resizeStartSize = main.Size
- input.Changed:Connect(function()
- if input.UserInputState == Enum.UserInputState.End then
- resizing = false
- resizeDir = nil
- end
- end)
- end
- end
- end)
- -- =========================
- -- MINIMIZE / CLOSE
- -- =========================
- btnClose.MouseButton1Click:Connect(function() screenGui:Destroy() end)
- local minimized = false
- btnMin.MouseButton1Click:Connect(function()
- minimized = not minimized
- content.Visible = not minimized
- consoleFrame.Visible = not minimized
- main.Size = minimized and UDim2.new(0,240,0,34) or UDim2.new(0.45,0,0.6,0)
- end)
- -- =========================
- -- REMOTE LIST POPULATION
- -- =========================
- local selectedRemote
- local function clearList()
- for _,v in ipairs(remotesList:GetChildren()) do
- if v:IsA("TextButton") then v:Destroy() end
- end
- end
- local function populate()
- clearList()
- local remotes = collectRemotes()
- logConsole("Loading "..#remotes.." remotes...")
- spawn(function() -- run asynchronously to avoid freezing the main thread
- for _,inst in ipairs(remotes) do
- local btn = Instance.new("TextButton")
- btn.Size = UDim2.new(1,-8,0,28)
- btn.BackgroundColor3 = Color3.fromRGB(38,38,38)
- btn.BorderSizePixel = 0
- btn.Text = inst:GetFullName()
- btn.Font = Enum.Font.SourceSans
- btn.TextSize = 13
- btn.TextColor3 = Color3.fromRGB(230,230,230)
- btn.Parent = remotesList
- btn.MouseButton1Click:Connect(function()
- for _,b in ipairs(remotesList:GetChildren()) do
- if b:IsA("TextButton") then
- b.BackgroundColor3 = Color3.fromRGB(38,38,38)
- end
- end
- btn.BackgroundColor3 = Color3.fromRGB(60,60,60)
- selectedRemote = inst
- selectedLabel.Text = "Selected: " .. inst:GetFullName()
- end)
- -- Yield a little to avoid freezing on low-end
- task.wait(0.05)
- end
- remotesList.CanvasSize = UDim2.new(0,0,0,uiListLayout.AbsoluteContentSize.Y + 8)
- logConsole("Finished loading "..#remotes.." remotes.")
- end)
- end
- refreshBtn.MouseButton1Click:Connect(function()
- populate()
- logConsole("Manual refresh triggered.")
- end)
- ReplicatedStorage.DescendantAdded:Connect(function(d)
- if d:IsA("RemoteEvent") or d:IsA("RemoteFunction") then populate() end
- end)
- ReplicatedStorage.DescendantRemoving:Connect(function(d)
- if d:IsA("RemoteEvent") or d:IsA("RemoteFunction") then
- if selectedRemote == d then selectedRemote = nil end
- populate()
- end
- end)
- -- =========================
- -- FIRE REMOTE
- -- =========================
- fireButton.MouseButton1Click:Connect(function()
- if not selectedRemote then
- logConsole("No remote selected.",true)
- return
- end
- local args = parseArgs(argsBox.Text)
- local ok, res = pcall(function()
- if selectedRemote:IsA("RemoteEvent") then
- selectedRemote:FireServer(table.unpack(args))
- return "Fired RemoteEvent."
- else
- local result = selectedRemote:InvokeServer(table.unpack(args))
- return "InvokeServer returned: " .. pretty(result)
- end
- end)
- if ok then
- logConsole(res)
- else
- logConsole("Error: "..tostring(res),true)
- end
- end)
- -- =========================
- -- INITIALIZATION
- -- =========================
- populate()
- logConsole("Ready.")
Advertisement
Add Comment
Please, Sign In to add comment