-- 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.")