· 6 months ago · Mar 31, 2025, 12:35 PM
1--[[
2 SimpleSpy v2.2 SOURCE
3
4 SimpleSpy is a lightweight penetration testing tool that logs remote calls.
5
6 Credits:
7 exx - basically everything
8 Frosty - GUI to Lua
9]]
10
11-- shuts down the previous instance of SimpleSpy
12if _G.SimpleSpyExecuted and type(_G.SimpleSpyShutdown) == "function" then
13 print(pcall(_G.SimpleSpyShutdown))
14end
15
16local Players = game:GetService("Players")
17local CoreGui = game:GetService("CoreGui")
18local Highlight =
19 loadstring(
20 game:HttpGet("https://github.com/exxtremestuffs/SimpleSpySource/raw/master/highlight.lua")
21 )()
22
23---- GENERATED (kinda sorta mostly) BY GUI to LUA ----
24
25-- Instances:
26
27local SimpleSpy2 = Instance.new("ScreenGui")
28local Background = Instance.new("Frame")
29local LeftPanel = Instance.new("Frame")
30local LogList = Instance.new("ScrollingFrame")
31local UIListLayout = Instance.new("UIListLayout")
32local RemoteTemplate = Instance.new("Frame")
33local ColorBar = Instance.new("Frame")
34local Text = Instance.new("TextLabel")
35local Button = Instance.new("TextButton")
36local RightPanel = Instance.new("Frame")
37local CodeBox = Instance.new("Frame")
38local ScrollingFrame = Instance.new("ScrollingFrame")
39local UIGridLayout = Instance.new("UIGridLayout")
40local FunctionTemplate = Instance.new("Frame")
41local ColorBar_2 = Instance.new("Frame")
42local Text_2 = Instance.new("TextLabel")
43local Button_2 = Instance.new("TextButton")
44local TopBar = Instance.new("Frame")
45local Simple = Instance.new("TextButton")
46local CloseButton = Instance.new("TextButton")
47local ImageLabel = Instance.new("ImageLabel")
48local MaximizeButton = Instance.new("TextButton")
49local ImageLabel_2 = Instance.new("ImageLabel")
50local MinimizeButton = Instance.new("TextButton")
51local ImageLabel_3 = Instance.new("ImageLabel")
52local ToolTip = Instance.new("Frame")
53local TextLabel = Instance.new("TextLabel")
54
55--Properties:
56
57SimpleSpy2.Name = "SimpleSpy2"
58SimpleSpy2.ResetOnSpawn = false
59
60Background.Name = "Background"
61Background.Parent = SimpleSpy2
62Background.BackgroundColor3 = Color3.new(1, 1, 1)
63Background.BackgroundTransparency = 1
64Background.Position = UDim2.new(0, 500, 0, 200)
65Background.Size = UDim2.new(0, 450, 0, 268)
66
67LeftPanel.Name = "LeftPanel"
68LeftPanel.Parent = Background
69LeftPanel.BackgroundColor3 = Color3.fromRGB(53, 52, 55)
70LeftPanel.BorderSizePixel = 0
71LeftPanel.Position = UDim2.new(0, 0, 0, 19)
72LeftPanel.Size = UDim2.new(0, 131, 0, 249)
73
74LogList.Name = "LogList"
75LogList.Parent = LeftPanel
76LogList.Active = true
77LogList.BackgroundColor3 = Color3.new(1, 1, 1)
78LogList.BackgroundTransparency = 1
79LogList.BorderSizePixel = 0
80LogList.Position = UDim2.new(0, 0, 0, 9)
81LogList.Size = UDim2.new(0, 131, 0, 232)
82LogList.CanvasSize = UDim2.new(0, 0, 0, 0)
83LogList.ScrollBarThickness = 4
84
85UIListLayout.Parent = LogList
86UIListLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
87UIListLayout.SortOrder = Enum.SortOrder.LayoutOrder
88
89RemoteTemplate.Name = "RemoteTemplate"
90RemoteTemplate.Parent = LogList
91RemoteTemplate.BackgroundColor3 = Color3.new(1, 1, 1)
92RemoteTemplate.BackgroundTransparency = 1
93RemoteTemplate.Size = UDim2.new(0, 117, 0, 27)
94
95ColorBar.Name = "ColorBar"
96ColorBar.Parent = RemoteTemplate
97ColorBar.BackgroundColor3 = Color3.fromRGB(255, 242, 0)
98ColorBar.BorderSizePixel = 0
99ColorBar.Position = UDim2.new(0, 0, 0, 1)
100ColorBar.Size = UDim2.new(0, 7, 0, 18)
101ColorBar.ZIndex = 2
102
103Text.Name = "Text"
104Text.Parent = RemoteTemplate
105Text.BackgroundColor3 = Color3.new(1, 1, 1)
106Text.BackgroundTransparency = 1
107Text.Position = UDim2.new(0, 12, 0, 1)
108Text.Size = UDim2.new(0, 105, 0, 18)
109Text.ZIndex = 2
110Text.Font = Enum.Font.SourceSans
111Text.Text = "TEXT"
112Text.TextColor3 = Color3.new(1, 1, 1)
113Text.TextSize = 14
114Text.TextXAlignment = Enum.TextXAlignment.Left
115Text.TextWrapped = true
116
117Button.Name = "Button"
118Button.Parent = RemoteTemplate
119Button.BackgroundColor3 = Color3.new(0, 0, 0)
120Button.BackgroundTransparency = 0.75
121Button.BorderColor3 = Color3.new(1, 1, 1)
122Button.Position = UDim2.new(0, 0, 0, 1)
123Button.Size = UDim2.new(0, 117, 0, 18)
124Button.AutoButtonColor = false
125Button.Font = Enum.Font.SourceSans
126Button.Text = ""
127Button.TextColor3 = Color3.new(0, 0, 0)
128Button.TextSize = 14
129
130RightPanel.Name = "RightPanel"
131RightPanel.Parent = Background
132RightPanel.BackgroundColor3 = Color3.fromRGB(37, 36, 38)
133RightPanel.BorderSizePixel = 0
134RightPanel.Position = UDim2.new(0, 131, 0, 19)
135RightPanel.Size = UDim2.new(0, 319, 0, 249)
136
137CodeBox.Name = "CodeBox"
138CodeBox.Parent = RightPanel
139CodeBox.BackgroundColor3 = Color3.new(0.0823529, 0.0745098, 0.0784314)
140CodeBox.BorderSizePixel = 0
141CodeBox.Size = UDim2.new(0, 319, 0, 119)
142
143ScrollingFrame.Parent = RightPanel
144ScrollingFrame.Active = true
145ScrollingFrame.BackgroundColor3 = Color3.new(1, 1, 1)
146ScrollingFrame.BackgroundTransparency = 1
147ScrollingFrame.Position = UDim2.new(0, 0, 0.5, 0)
148ScrollingFrame.Size = UDim2.new(1, 0, 0.5, -9)
149ScrollingFrame.CanvasSize = UDim2.new(0, 0, 0, 0)
150ScrollingFrame.ScrollBarThickness = 4
151
152UIGridLayout.Parent = ScrollingFrame
153UIGridLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
154UIGridLayout.SortOrder = Enum.SortOrder.LayoutOrder
155UIGridLayout.CellPadding = UDim2.new(0, 0, 0, 0)
156UIGridLayout.CellSize = UDim2.new(0, 94, 0, 27)
157
158FunctionTemplate.Name = "FunctionTemplate"
159FunctionTemplate.Parent = ScrollingFrame
160FunctionTemplate.BackgroundColor3 = Color3.new(1, 1, 1)
161FunctionTemplate.BackgroundTransparency = 1
162FunctionTemplate.Size = UDim2.new(0, 117, 0, 23)
163
164ColorBar_2.Name = "ColorBar"
165ColorBar_2.Parent = FunctionTemplate
166ColorBar_2.BackgroundColor3 = Color3.new(1, 1, 1)
167ColorBar_2.BorderSizePixel = 0
168ColorBar_2.Position = UDim2.new(0, 7, 0, 10)
169ColorBar_2.Size = UDim2.new(0, 7, 0, 18)
170ColorBar_2.ZIndex = 3
171
172Text_2.Name = "Text"
173Text_2.Parent = FunctionTemplate
174Text_2.BackgroundColor3 = Color3.new(1, 1, 1)
175Text_2.BackgroundTransparency = 1
176Text_2.Position = UDim2.new(0, 19, 0, 10)
177Text_2.Size = UDim2.new(0, 69, 0, 18)
178Text_2.ZIndex = 2
179Text_2.Font = Enum.Font.SourceSans
180Text_2.Text = "TEXT"
181Text_2.TextColor3 = Color3.new(1, 1, 1)
182Text_2.TextSize = 14
183Text_2.TextStrokeColor3 = Color3.new(0.145098, 0.141176, 0.14902)
184Text_2.TextXAlignment = Enum.TextXAlignment.Left
185Text_2.TextWrapped = true
186
187Button_2.Name = "Button"
188Button_2.Parent = FunctionTemplate
189Button_2.BackgroundColor3 = Color3.new(0, 0, 0)
190Button_2.BackgroundTransparency = 0.69999998807907
191Button_2.BorderColor3 = Color3.new(1, 1, 1)
192Button_2.Position = UDim2.new(0, 7, 0, 10)
193Button_2.Size = UDim2.new(0, 80, 0, 18)
194Button_2.AutoButtonColor = false
195Button_2.Font = Enum.Font.SourceSans
196Button_2.Text = ""
197Button_2.TextColor3 = Color3.new(0, 0, 0)
198Button_2.TextSize = 14
199
200TopBar.Name = "TopBar"
201TopBar.Parent = Background
202TopBar.BackgroundColor3 = Color3.fromRGB(37, 35, 38)
203TopBar.BorderSizePixel = 0
204TopBar.Size = UDim2.new(0, 450, 0, 19)
205
206Simple.Name = "Simple"
207Simple.Parent = TopBar
208Simple.BackgroundColor3 = Color3.new(1, 1, 1)
209Simple.AutoButtonColor = false
210Simple.BackgroundTransparency = 1
211Simple.Position = UDim2.new(0, 5, 0, 0)
212Simple.Size = UDim2.new(0, 57, 0, 18)
213Simple.Font = Enum.Font.SourceSansBold
214Simple.Text = "SimpleSpy"
215Simple.TextColor3 = Color3.new(1, 1, 1)
216Simple.TextSize = 14
217Simple.TextXAlignment = Enum.TextXAlignment.Left
218
219CloseButton.Name = "CloseButton"
220CloseButton.Parent = TopBar
221CloseButton.BackgroundColor3 = Color3.new(0.145098, 0.141176, 0.14902)
222CloseButton.BorderSizePixel = 0
223CloseButton.Position = UDim2.new(1, -19, 0, 0)
224CloseButton.Size = UDim2.new(0, 19, 0, 19)
225CloseButton.Font = Enum.Font.SourceSans
226CloseButton.Text = ""
227CloseButton.TextColor3 = Color3.new(0, 0, 0)
228CloseButton.TextSize = 14
229
230ImageLabel.Parent = CloseButton
231ImageLabel.BackgroundColor3 = Color3.new(1, 1, 1)
232ImageLabel.BackgroundTransparency = 1
233ImageLabel.Position = UDim2.new(0, 5, 0, 5)
234ImageLabel.Size = UDim2.new(0, 9, 0, 9)
235ImageLabel.Image = "http://www.roblox.com/asset/?id=5597086202"
236
237MaximizeButton.Name = "MaximizeButton"
238MaximizeButton.Parent = TopBar
239MaximizeButton.BackgroundColor3 = Color3.new(0.145098, 0.141176, 0.14902)
240MaximizeButton.BorderSizePixel = 0
241MaximizeButton.Position = UDim2.new(1, -38, 0, 0)
242MaximizeButton.Size = UDim2.new(0, 19, 0, 19)
243MaximizeButton.Font = Enum.Font.SourceSans
244MaximizeButton.Text = ""
245MaximizeButton.TextColor3 = Color3.new(0, 0, 0)
246MaximizeButton.TextSize = 14
247
248ImageLabel_2.Parent = MaximizeButton
249ImageLabel_2.BackgroundColor3 = Color3.new(1, 1, 1)
250ImageLabel_2.BackgroundTransparency = 1
251ImageLabel_2.Position = UDim2.new(0, 5, 0, 5)
252ImageLabel_2.Size = UDim2.new(0, 9, 0, 9)
253ImageLabel_2.Image = "http://www.roblox.com/asset/?id=5597108117"
254
255MinimizeButton.Name = "MinimizeButton"
256MinimizeButton.Parent = TopBar
257MinimizeButton.BackgroundColor3 = Color3.new(0.145098, 0.141176, 0.14902)
258MinimizeButton.BorderSizePixel = 0
259MinimizeButton.Position = UDim2.new(1, -57, 0, 0)
260MinimizeButton.Size = UDim2.new(0, 19, 0, 19)
261MinimizeButton.Font = Enum.Font.SourceSans
262MinimizeButton.Text = ""
263MinimizeButton.TextColor3 = Color3.new(0, 0, 0)
264MinimizeButton.TextSize = 14
265
266ImageLabel_3.Parent = MinimizeButton
267ImageLabel_3.BackgroundColor3 = Color3.new(1, 1, 1)
268ImageLabel_3.BackgroundTransparency = 1
269ImageLabel_3.Position = UDim2.new(0, 5, 0, 5)
270ImageLabel_3.Size = UDim2.new(0, 9, 0, 9)
271ImageLabel_3.Image = "http://www.roblox.com/asset/?id=5597105827"
272
273ToolTip.Name = "ToolTip"
274ToolTip.Parent = SimpleSpy2
275ToolTip.BackgroundColor3 = Color3.fromRGB(26, 26, 26)
276ToolTip.BackgroundTransparency = 0.1
277ToolTip.BorderColor3 = Color3.new(1, 1, 1)
278ToolTip.Size = UDim2.new(0, 200, 0, 50)
279ToolTip.ZIndex = 3
280ToolTip.Visible = false
281
282TextLabel.Parent = ToolTip
283TextLabel.BackgroundColor3 = Color3.new(1, 1, 1)
284TextLabel.BackgroundTransparency = 1
285TextLabel.Position = UDim2.new(0, 2, 0, 2)
286TextLabel.Size = UDim2.new(0, 196, 0, 46)
287TextLabel.ZIndex = 3
288TextLabel.Font = Enum.Font.SourceSans
289TextLabel.Text = "This is some slightly longer text."
290TextLabel.TextColor3 = Color3.new(1, 1, 1)
291TextLabel.TextSize = 14
292TextLabel.TextWrapped = true
293TextLabel.TextXAlignment = Enum.TextXAlignment.Left
294TextLabel.TextYAlignment = Enum.TextYAlignment.Top
295
296-------------------------------------------------------------------------------
297-- init
298local RunService = game:GetService("RunService")
299local UserInputService = game:GetService("UserInputService")
300local TweenService = game:GetService("TweenService")
301local ContentProvider = game:GetService("ContentProvider")
302local TextService = game:GetService("TextService")
303local Mouse
304
305local selectedColor = Color3.new(0.321569, 0.333333, 1)
306local deselectedColor = Color3.new(0.8, 0.8, 0.8)
307--- So things are descending
308local layoutOrderNum = 999999999
309--- Whether or not the gui is closing
310local mainClosing = false
311--- Whether or not the gui is closed (defaults to false)
312local closed = false
313--- Whether or not the sidebar is closing
314local sideClosing = false
315--- Whether or not the sidebar is closed (defaults to true but opens automatically on remote selection)
316local sideClosed = false
317--- Whether or not the code box is maximized (defaults to false)
318local maximized = false
319--- The event logs to be read from
320local logs = {}
321--- The event currently selected.Log (defaults to nil)
322local selected = nil
323--- The blacklist (can be a string name or the Remote Instance)
324local blacklist = {}
325--- The block list (can be a string name or the Remote Instance)
326local blocklist = {"RequestRejoinGame"}
327--- Whether or not to add getNil function
328local getNil = false
329--- Array of remotes (and original functions) connected to
330local connectedRemotes = {}
331--- True = hookfunction, false = namecall
332local toggle = false
333local gm
334local original
335--- used to prevent recursives
336local prevTables = {}
337--- holds logs (for deletion)
338local remoteLogs = {}
339--- used for hookfunction
340local remoteEvent = Instance.new("RemoteEvent")
341--- used for hookfunction
342local remoteFunction = Instance.new("RemoteFunction")
343local originalEvent = remoteEvent.FireServer
344local originalFunction = remoteFunction.InvokeServer
345--- the maximum amount of remotes allowed in logs
346_G.SIMPLESPYCONFIG_MaxRemotes = 500
347--- how many spaces to indent
348local indent = 4
349--- used for task scheduler
350local scheduled = {}
351--- RBXScriptConnect of the task scheduler
352local schedulerconnect
353local SimpleSpy = {}
354local topstr = ""
355local bottomstr = ""
356local remotesFadeIn
357local rightFadeIn
358local codebox
359local p
360local getnilrequired = false
361
362-- autoblock variables
363local autoblock = false
364local history = {}
365local excluding = {}
366
367-- function info variables
368local funcEnabled = true
369
370-- remote hooking/connecting api variables
371local remoteSignals = {}
372local remoteHooks = {}
373
374-- original mouse icon
375local oldIcon
376
377-- if mouse inside gui
378local mouseInGui = false
379
380-- handy array of RBXScriptConnections to disconnect on shutdown
381local connections = {}
382
383-- whether or not SimpleSpy uses 'getcallingscript()' to get the script (default is false because detection)
384local useGetCallingScript = false
385
386--- used to enable/disable SimpleSpy's keyToString for remotes
387local keyToString = false
388
389-- determines whether return values are recorded
390local recordReturnValues = false
391
392-- functions
393
394--- Converts arguments to a string and generates code that calls the specified method with them, recommended to be used in conjunction with ValueToString (method must be a string, e.g. `game:GetService("ReplicatedStorage").Remote.remote:FireServer`)
395--- @param method string
396--- @param args any[]
397--- @return string
398function SimpleSpy:ArgsToString(method, args)
399 assert(typeof(method) == "string", "string expected, got " .. typeof(method))
400 assert(typeof(args) == "table", "table expected, got " .. typeof(args))
401 return v2v({ args = args }) .. "\n\n" .. method .. "(unpack(args))"
402end
403
404--- Converts a value to variables with the specified index as the variable name (if nil/invalid then the name will be assigned automatically)
405--- @param t any[]
406--- @return string
407function SimpleSpy:TableToVars(t)
408 assert(typeof(t) == "table", "table expected, got " .. typeof(t))
409 return v2v(t)
410end
411
412--- Converts a value to a variable with the specified `variablename` (if nil/invalid then the name will be assigned automatically)
413--- @param value any
414--- @return string
415function SimpleSpy:ValueToVar(value, variablename)
416 assert(variablename == nil or typeof(variablename) == "string", "string expected, got " .. typeof(variablename))
417 if not variablename then
418 variablename = 1
419 end
420 return v2v({ [variablename] = value })
421end
422
423--- Converts any value to a string, cannot preserve function contents
424--- @param value any
425--- @return string
426function SimpleSpy:ValueToString(value)
427 return v2s(value)
428end
429
430--- Generates the simplespy function info
431--- @param func function
432--- @return string
433function SimpleSpy:GetFunctionInfo(func)
434 assert(typeof(func) == "function", "Instance expected, got " .. typeof(func))
435 warn("Function info currently unavailable due to crashing in Synapse X")
436 return v2v({ functionInfo = {
437 info = debug.getinfo(func),
438 constants = debug.getconstants(func),
439 } })
440end
441
442--- Gets the ScriptSignal for a specified remote being fired
443--- @param remote Instance
444function SimpleSpy:GetRemoteFiredSignal(remote)
445 assert(typeof(remote) == "Instance", "Instance expected, got " .. typeof(remote))
446 if not remoteSignals[remote] then
447 remoteSignals[remote] = newSignal()
448 end
449 return remoteSignals[remote]
450end
451
452--- Allows for direct hooking of remotes **THIS CAN BE VERY DANGEROUS**
453--- @param remote Instance
454--- @param f function
455function SimpleSpy:HookRemote(remote, f)
456 assert(typeof(remote) == "Instance", "Instance expected, got " .. typeof(remote))
457 assert(typeof(f) == "function", "function expected, got " .. typeof(f))
458 remoteHooks[remote] = f
459end
460
461--- Blocks the specified remote instance/string
462--- @param remote any
463function SimpleSpy:BlockRemote(remote)
464 assert(
465 typeof(remote) == "Instance" or typeof(remote) == "string",
466 "Instance | string expected, got " .. typeof(remote)
467 )
468 blocklist[remote] = true
469end
470
471--- Excludes the specified remote from logs (instance/string)
472--- @param remote any
473function SimpleSpy:ExcludeRemote(remote)
474 assert(
475 typeof(remote) == "Instance" or typeof(remote) == "string",
476 "Instance | string expected, got " .. typeof(remote)
477 )
478 blacklist[remote] = true
479end
480
481--- Creates a new ScriptSignal that can be connected to and fired
482--- @return table
483function newSignal()
484 local connected = {}
485 return {
486 Connect = function(self, f)
487 assert(connected, "Signal is closed")
488 connected[tostring(f)] = f
489 return {
490 Connected = true,
491 Disconnect = function(self)
492 if not connected then
493 warn("Signal is already closed")
494 end
495 self.Connected = false
496 connected[tostring(f)] = nil
497 end,
498 }
499 end,
500 Wait = function(self)
501 local thread = coroutine.running()
502 local connection
503 connection = self:Connect(function()
504 connection:Disconnect()
505 if coroutine.status(thread) == "suspended" then
506 coroutine.resume(thread)
507 end
508 end)
509 coroutine.yield()
510 end,
511 Fire = function(self, ...)
512 for _, f in pairs(connected) do
513 coroutine.wrap(f)(...)
514 end
515 end,
516 }
517end
518
519--- Prevents remote spam from causing lag (clears logs after `_G.SIMPLESPYCONFIG_MaxRemotes` or 500 remotes)
520function clean()
521 local max = _G.SIMPLESPYCONFIG_MaxRemotes
522 if not typeof(max) == "number" and math.floor(max) ~= max then
523 max = 500
524 end
525 if #remoteLogs > max then
526 for i = 100, #remoteLogs do
527 local v = remoteLogs[i]
528 if typeof(v[1]) == "RBXScriptConnection" then
529 v[1]:Disconnect()
530 end
531 if typeof(v[2]) == "Instance" then
532 v[2]:Destroy()
533 end
534 end
535 local newLogs = {}
536 for i = 1, 100 do
537 table.insert(newLogs, remoteLogs[i])
538 end
539 remoteLogs = newLogs
540 end
541end
542
543--- Scales the ToolTip to fit containing text
544function scaleToolTip()
545 local size = TextService:GetTextSize(
546 TextLabel.Text,
547 TextLabel.TextSize,
548 TextLabel.Font,
549 Vector2.new(196, math.huge)
550 )
551 TextLabel.Size = UDim2.new(0, size.X, 0, size.Y)
552 ToolTip.Size = UDim2.new(0, size.X + 4, 0, size.Y + 4)
553end
554
555--- Executed when the toggle button (the SimpleSpy logo) is hovered over
556function onToggleButtonHover()
557 if not toggle then
558 TweenService:Create(Simple, TweenInfo.new(0.5), { TextColor3 = Color3.fromRGB(252, 51, 51) }):Play()
559 else
560 TweenService:Create(Simple, TweenInfo.new(0.5), { TextColor3 = Color3.fromRGB(68, 206, 91) }):Play()
561 end
562end
563
564--- Executed when the toggle button is unhovered over
565function onToggleButtonUnhover()
566 TweenService:Create(Simple, TweenInfo.new(0.5), { TextColor3 = Color3.fromRGB(255, 255, 255) }):Play()
567end
568
569--- Executed when the X button is hovered over
570function onXButtonHover()
571 TweenService:Create(CloseButton, TweenInfo.new(0.2), { BackgroundColor3 = Color3.fromRGB(255, 60, 60) }):Play()
572end
573
574--- Executed when the X button is unhovered over
575function onXButtonUnhover()
576 TweenService:Create(CloseButton, TweenInfo.new(0.2), { BackgroundColor3 = Color3.fromRGB(37, 36, 38) }):Play()
577end
578
579--- Toggles the remote spy method (when button clicked)
580function onToggleButtonClick()
581 if toggle then
582 TweenService:Create(Simple, TweenInfo.new(0.5), { TextColor3 = Color3.fromRGB(252, 51, 51) }):Play()
583 else
584 TweenService:Create(Simple, TweenInfo.new(0.5), { TextColor3 = Color3.fromRGB(68, 206, 91) }):Play()
585 end
586 toggleSpyMethod()
587end
588
589--- Reconnects bringBackOnResize if the current viewport changes and also connects it initially
590function connectResize()
591 local lastCam = workspace.CurrentCamera:GetPropertyChangedSignal("ViewportSize"):Connect(bringBackOnResize)
592 workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(function()
593 lastCam:Disconnect()
594 if workspace.CurrentCamera then
595 lastCam = workspace.CurrentCamera:GetPropertyChangedSignal("ViewportSize"):Connect(bringBackOnResize)
596 end
597 end)
598end
599
600--- Brings gui back if it gets lost offscreen (connected to the camera viewport changing)
601function bringBackOnResize()
602 validateSize()
603 if sideClosed then
604 minimizeSize()
605 else
606 maximizeSize()
607 end
608 local currentX = Background.AbsolutePosition.X
609 local currentY = Background.AbsolutePosition.Y
610 local viewportSize = workspace.CurrentCamera.ViewportSize
611 if (currentX < 0) or (currentX > (viewportSize.X - (sideClosed and 131 or Background.AbsoluteSize.X))) then
612 if currentX < 0 then
613 currentX = 0
614 else
615 currentX = viewportSize.X - (sideClosed and 131 or Background.AbsoluteSize.X)
616 end
617 end
618 if (currentY < 0) or (currentY > (viewportSize.Y - (closed and 19 or Background.AbsoluteSize.Y) - 36)) then
619 if currentY < 0 then
620 currentY = 0
621 else
622 currentY = viewportSize.Y - (closed and 19 or Background.AbsoluteSize.Y) - 36
623 end
624 end
625 TweenService.Create(
626 TweenService,
627 Background,
628 TweenInfo.new(0.1),
629 { Position = UDim2.new(0, currentX, 0, currentY) }
630 ):Play()
631end
632
633--- Drags gui (so long as mouse is held down)
634--- @param input InputObject
635function onBarInput(input)
636 if input.UserInputType == Enum.UserInputType.MouseButton1 then
637 local lastPos = UserInputService.GetMouseLocation(UserInputService)
638 local mainPos = Background.AbsolutePosition
639 local offset = mainPos - lastPos
640 local currentPos = offset + lastPos
641 RunService.BindToRenderStep(RunService, "drag", 1, function()
642 local newPos = UserInputService.GetMouseLocation(UserInputService)
643 if newPos ~= lastPos then
644 local currentX = (offset + newPos).X
645 local currentY = (offset + newPos).Y
646 local viewportSize = workspace.CurrentCamera.ViewportSize
647 if
648 (currentX < 0 and currentX < currentPos.X)
649 or (
650 currentX > (viewportSize.X - (sideClosed and 131 or TopBar.AbsoluteSize.X))
651 and currentX > currentPos.X
652 )
653 then
654 if currentX < 0 then
655 currentX = 0
656 else
657 currentX = viewportSize.X - (sideClosed and 131 or TopBar.AbsoluteSize.X)
658 end
659 end
660 if
661 (currentY < 0 and currentY < currentPos.Y)
662 or (
663 currentY > (viewportSize.Y - (closed and 19 or Background.AbsoluteSize.Y) - 36)
664 and currentY > currentPos.Y
665 )
666 then
667 if currentY < 0 then
668 currentY = 0
669 else
670 currentY = viewportSize.Y - (closed and 19 or Background.AbsoluteSize.Y) - 36
671 end
672 end
673 currentPos = Vector2.new(currentX, currentY)
674 lastPos = newPos
675 TweenService.Create(
676 TweenService,
677 Background,
678 TweenInfo.new(0.1),
679 { Position = UDim2.new(0, currentPos.X, 0, currentPos.Y) }
680 ):Play()
681 end
682 -- if input.UserInputState ~= Enum.UserInputState.Begin then
683 -- RunService.UnbindFromRenderStep(RunService, "drag")
684 -- end
685 end)
686 table.insert(
687 connections,
688 UserInputService.InputEnded:Connect(function(inputE)
689 if input == inputE then
690 RunService:UnbindFromRenderStep("drag")
691 end
692 end)
693 )
694 end
695end
696
697--- Fades out the table of elements (and makes them invisible), returns a function to make them visible again
698function fadeOut(elements)
699 local data = {}
700 for _, v in pairs(elements) do
701 if typeof(v) == "Instance" and v:IsA("GuiObject") and v.Visible then
702 coroutine.wrap(function()
703 data[v] = {
704 BackgroundTransparency = v.BackgroundTransparency,
705 }
706 TweenService:Create(v, TweenInfo.new(0.5), { BackgroundTransparency = 1 }):Play()
707 if v:IsA("TextBox") or v:IsA("TextButton") or v:IsA("TextLabel") then
708 data[v].TextTransparency = v.TextTransparency
709 TweenService:Create(v, TweenInfo.new(0.5), { TextTransparency = 1 }):Play()
710 elseif v:IsA("ImageButton") or v:IsA("ImageLabel") then
711 data[v].ImageTransparency = v.ImageTransparency
712 TweenService:Create(v, TweenInfo.new(0.5), { ImageTransparency = 1 }):Play()
713 end
714 wait(0.5)
715 v.Visible = false
716 for i, x in pairs(data[v]) do
717 v[i] = x
718 end
719 data[v] = true
720 end)()
721 end
722 end
723 return function()
724 for i, _ in pairs(data) do
725 coroutine.wrap(function()
726 local properties = {
727 BackgroundTransparency = i.BackgroundTransparency,
728 }
729 i.BackgroundTransparency = 1
730 TweenService
731 :Create(i, TweenInfo.new(0.5), { BackgroundTransparency = properties.BackgroundTransparency })
732 :Play()
733 if i:IsA("TextBox") or i:IsA("TextButton") or i:IsA("TextLabel") then
734 properties.TextTransparency = i.TextTransparency
735 i.TextTransparency = 1
736 TweenService
737 :Create(i, TweenInfo.new(0.5), { TextTransparency = properties.TextTransparency })
738 :Play()
739 elseif i:IsA("ImageButton") or i:IsA("ImageLabel") then
740 properties.ImageTransparency = i.ImageTransparency
741 i.ImageTransparency = 1
742 TweenService
743 :Create(i, TweenInfo.new(0.5), { ImageTransparency = properties.ImageTransparency })
744 :Play()
745 end
746 i.Visible = true
747 end)()
748 end
749 end
750end
751
752--- Expands and minimizes the gui (closed is the toggle boolean)
753function toggleMinimize(override)
754 if mainClosing and not override or maximized then
755 return
756 end
757 mainClosing = true
758 closed = not closed
759 if closed then
760 if not sideClosed then
761 toggleSideTray(true)
762 end
763 LeftPanel.Visible = true
764 TweenService:Create(LeftPanel, TweenInfo.new(0.5), { Size = UDim2.new(0, 131, 0, 0) }):Play()
765 wait(0.5)
766 remotesFadeIn = fadeOut(LeftPanel:GetDescendants())
767 wait(0.5)
768 else
769 TweenService:Create(LeftPanel, TweenInfo.new(0.5), { Size = UDim2.new(0, 131, 0, 249) }):Play()
770 wait(0.5)
771 if remotesFadeIn then
772 remotesFadeIn()
773 remotesFadeIn = nil
774 end
775 bringBackOnResize()
776 end
777 mainClosing = false
778end
779
780--- Expands and minimizes the sidebar (sideClosed is the toggle boolean)
781function toggleSideTray(override)
782 if sideClosing and not override or maximized then
783 return
784 end
785 sideClosing = true
786 sideClosed = not sideClosed
787 if sideClosed then
788 rightFadeIn = fadeOut(RightPanel:GetDescendants())
789 wait(0.5)
790 minimizeSize(0.5)
791 wait(0.5)
792 RightPanel.Visible = false
793 else
794 if closed then
795 toggleMinimize(true)
796 end
797 RightPanel.Visible = true
798 maximizeSize(0.5)
799 wait(0.5)
800 if rightFadeIn then
801 rightFadeIn()
802 end
803 bringBackOnResize()
804 end
805 sideClosing = false
806end
807
808--- Expands code box to fit screen for more convenient viewing
809function toggleMaximize()
810 if not sideClosed and not maximized then
811 maximized = true
812 local disable = Instance.new("TextButton")
813 local prevSize = UDim2.new(0, CodeBox.AbsoluteSize.X, 0, CodeBox.AbsoluteSize.Y)
814 local prevPos = UDim2.new(0, CodeBox.AbsolutePosition.X, 0, CodeBox.AbsolutePosition.Y)
815 disable.Size = UDim2.new(1, 0, 1, 0)
816 disable.BackgroundColor3 = Color3.new()
817 disable.BorderSizePixel = 0
818 disable.Text = 0
819 disable.ZIndex = 3
820 disable.BackgroundTransparency = 1
821 disable.AutoButtonColor = false
822 CodeBox.ZIndex = 4
823 CodeBox.Position = prevPos
824 CodeBox.Size = prevSize
825 TweenService
826 :Create(
827 CodeBox,
828 TweenInfo.new(0.5),
829 { Size = UDim2.new(0.5, 0, 0.5, 0), Position = UDim2.new(0.25, 0, 0.25, 0) }
830 )
831 :Play()
832 TweenService:Create(disable, TweenInfo.new(0.5), { BackgroundTransparency = 0.5 }):Play()
833 disable.MouseButton1Click:Connect(function()
834 if
835 UserInputService:GetMouseLocation().Y + 36 >= CodeBox.AbsolutePosition.Y
836 and UserInputService:GetMouseLocation().Y + 36 <= CodeBox.AbsolutePosition.Y + CodeBox.AbsoluteSize.Y
837 and UserInputService:GetMouseLocation().X >= CodeBox.AbsolutePosition.X
838 and UserInputService:GetMouseLocation().X <= CodeBox.AbsolutePosition.X + CodeBox.AbsoluteSize.X
839 then
840 return
841 end
842 TweenService:Create(CodeBox, TweenInfo.new(0.5), { Size = prevSize, Position = prevPos }):Play()
843 TweenService:Create(disable, TweenInfo.new(0.5), { BackgroundTransparency = 1 }):Play()
844 maximized = false
845 wait(0.5)
846 disable:Destroy()
847 CodeBox.Size = UDim2.new(1, 0, 0.5, 0)
848 CodeBox.Position = UDim2.new(0, 0, 0, 0)
849 CodeBox.ZIndex = 0
850 end)
851 end
852end
853
854--- Checks if cursor is within resize range
855--- @param p Vector2
856function isInResizeRange(p)
857 local relativeP = p - Background.AbsolutePosition
858 local range = 5
859 if
860 relativeP.X >= TopBar.AbsoluteSize.X - range
861 and relativeP.Y >= Background.AbsoluteSize.Y - range
862 and relativeP.X <= TopBar.AbsoluteSize.X
863 and relativeP.Y <= Background.AbsoluteSize.Y
864 then
865 return true, "B"
866 elseif relativeP.X >= TopBar.AbsoluteSize.X - range and relativeP.X <= Background.AbsoluteSize.X then
867 return true, "X"
868 elseif relativeP.Y >= Background.AbsoluteSize.Y - range and relativeP.Y <= Background.AbsoluteSize.Y then
869 return true, "Y"
870 end
871 return false
872end
873
874--- Checks if cursor is within dragging range
875--- @param p Vector2
876function isInDragRange(p)
877 local relativeP = p - Background.AbsolutePosition
878 if
879 relativeP.X <= TopBar.AbsoluteSize.X - CloseButton.AbsoluteSize.X * 3
880 and relativeP.X >= 0
881 and relativeP.Y <= TopBar.AbsoluteSize.Y
882 and relativeP.Y >= 0
883 then
884 return true
885 end
886 return false
887end
888
889--- Called when mouse enters SimpleSpy
890function mouseEntered()
891 local existingCursor = SimpleSpy2:FindFirstChild("Cursor")
892 while existingCursor do
893 existingCursor:Destroy()
894 existingCursor = SimpleSpy2:FindFirstChild("Cursor")
895 end
896 local customCursor = Instance.new("ImageLabel")
897 customCursor.Name = "Cursor"
898 customCursor.Size = UDim2.fromOffset(200, 200)
899 customCursor.ZIndex = 1e5
900 customCursor.BackgroundTransparency = 1
901 customCursor.Image = ""
902 customCursor.Parent = SimpleSpy2
903 UserInputService.OverrideMouseIconBehavior = Enum.OverrideMouseIconBehavior.ForceHide
904 RunService:BindToRenderStep("SIMPLESPY_CURSOR", 1, function()
905 if mouseInGui and _G.SimpleSpyExecuted then
906 local mouseLocation = UserInputService:GetMouseLocation() - Vector2.new(0, 36)
907 customCursor.Position = UDim2.fromOffset(
908 mouseLocation.X - customCursor.AbsoluteSize.X / 2,
909 mouseLocation.Y - customCursor.AbsoluteSize.Y / 2
910 )
911 local inRange, type = isInResizeRange(mouseLocation)
912 if inRange and not sideClosed and not closed then
913 customCursor.Image = type == "B" and "rbxassetid://6065821980"
914 or type == "X" and "rbxassetid://6065821086"
915 or type == "Y" and "rbxassetid://6065821596"
916 elseif inRange and not closed and type == "Y" or type == "B" then
917 customCursor.Image = "rbxassetid://6065821596"
918 elseif customCursor.Image ~= "rbxassetid://6065775281" then
919 customCursor.Image = "rbxassetid://6065775281"
920 end
921 else
922 UserInputService.OverrideMouseIconBehavior = Enum.OverrideMouseIconBehavior.None
923 customCursor:Destroy()
924 RunService:UnbindFromRenderStep("SIMPLESPY_CURSOR")
925 end
926 end)
927end
928
929--- Called when mouse moves
930function mouseMoved()
931 local mousePos = UserInputService:GetMouseLocation() - Vector2.new(0, 36)
932 if
933 not closed
934 and mousePos.X >= TopBar.AbsolutePosition.X
935 and mousePos.X <= TopBar.AbsolutePosition.X + TopBar.AbsoluteSize.X
936 and mousePos.Y >= Background.AbsolutePosition.Y
937 and mousePos.Y <= Background.AbsolutePosition.Y + Background.AbsoluteSize.Y
938 then
939 if not mouseInGui then
940 mouseInGui = true
941 mouseEntered()
942 end
943 else
944 mouseInGui = false
945 end
946end
947
948--- Adjusts the ui elements to the 'Maximized' size
949function maximizeSize(speed)
950 if not speed then
951 speed = 0.05
952 end
953 TweenService
954 :Create(
955 LeftPanel,
956 TweenInfo.new(speed),
957 { Size = UDim2.fromOffset(LeftPanel.AbsoluteSize.X, Background.AbsoluteSize.Y - TopBar.AbsoluteSize.Y) }
958 )
959 :Play()
960 TweenService
961 :Create(RightPanel, TweenInfo.new(speed), {
962 Size = UDim2.fromOffset(
963 Background.AbsoluteSize.X - LeftPanel.AbsoluteSize.X,
964 Background.AbsoluteSize.Y - TopBar.AbsoluteSize.Y
965 ),
966 })
967 :Play()
968 TweenService
969 :Create(
970 TopBar,
971 TweenInfo.new(speed),
972 { Size = UDim2.fromOffset(Background.AbsoluteSize.X, TopBar.AbsoluteSize.Y) }
973 )
974 :Play()
975 TweenService
976 :Create(ScrollingFrame, TweenInfo.new(speed), {
977 Size = UDim2.fromOffset(Background.AbsoluteSize.X - LeftPanel.AbsoluteSize.X, 110),
978 Position = UDim2.fromOffset(0, Background.AbsoluteSize.Y - 119 - TopBar.AbsoluteSize.Y),
979 })
980 :Play()
981 TweenService
982 :Create(CodeBox, TweenInfo.new(speed), {
983 Size = UDim2.fromOffset(
984 Background.AbsoluteSize.X - LeftPanel.AbsoluteSize.X,
985 Background.AbsoluteSize.Y - 119 - TopBar.AbsoluteSize.Y
986 ),
987 })
988 :Play()
989 TweenService
990 :Create(
991 LogList,
992 TweenInfo.new(speed),
993 { Size = UDim2.fromOffset(LogList.AbsoluteSize.X, Background.AbsoluteSize.Y - TopBar.AbsoluteSize.Y - 18) }
994 )
995 :Play()
996end
997
998--- Adjusts the ui elements to close the side
999function minimizeSize(speed)
1000 if not speed then
1001 speed = 0.05
1002 end
1003 TweenService
1004 :Create(
1005 LeftPanel,
1006 TweenInfo.new(speed),
1007 { Size = UDim2.fromOffset(LeftPanel.AbsoluteSize.X, Background.AbsoluteSize.Y - TopBar.AbsoluteSize.Y) }
1008 )
1009 :Play()
1010 TweenService
1011 :Create(
1012 RightPanel,
1013 TweenInfo.new(speed),
1014 { Size = UDim2.fromOffset(0, Background.AbsoluteSize.Y - TopBar.AbsoluteSize.Y) }
1015 )
1016 :Play()
1017 TweenService
1018 :Create(
1019 TopBar,
1020 TweenInfo.new(speed),
1021 { Size = UDim2.fromOffset(LeftPanel.AbsoluteSize.X, TopBar.AbsoluteSize.Y) }
1022 )
1023 :Play()
1024 TweenService
1025 :Create(ScrollingFrame, TweenInfo.new(speed), {
1026 Size = UDim2.fromOffset(0, 119),
1027 Position = UDim2.fromOffset(0, Background.AbsoluteSize.Y - 119 - TopBar.AbsoluteSize.Y),
1028 })
1029 :Play()
1030 TweenService
1031 :Create(
1032 CodeBox,
1033 TweenInfo.new(speed),
1034 { Size = UDim2.fromOffset(0, Background.AbsoluteSize.Y - 119 - TopBar.AbsoluteSize.Y) }
1035 )
1036 :Play()
1037 TweenService
1038 :Create(
1039 LogList,
1040 TweenInfo.new(speed),
1041 { Size = UDim2.fromOffset(LogList.AbsoluteSize.X, Background.AbsoluteSize.Y - TopBar.AbsoluteSize.Y - 18) }
1042 )
1043 :Play()
1044end
1045
1046--- Ensures size is within screensize limitations
1047function validateSize()
1048 local x, y = Background.AbsoluteSize.X, Background.AbsoluteSize.Y
1049 local screenSize = workspace.CurrentCamera.ViewportSize
1050 if x + Background.AbsolutePosition.X > screenSize.X then
1051 if screenSize.X - Background.AbsolutePosition.X >= 450 then
1052 x = screenSize.X - Background.AbsolutePosition.X
1053 else
1054 x = 450
1055 end
1056 elseif y + Background.AbsolutePosition.Y > screenSize.Y then
1057 if screenSize.X - Background.AbsolutePosition.Y >= 268 then
1058 y = screenSize.Y - Background.AbsolutePosition.Y
1059 else
1060 y = 268
1061 end
1062 end
1063 Background.Size = UDim2.fromOffset(x, y)
1064end
1065
1066--- Called on user input while mouse in 'Background' frame
1067--- @param input InputObject
1068function backgroundUserInput(input)
1069 local mousePos = UserInputService:GetMouseLocation() - Vector2.new(0, 36)
1070 local inResizeRange, type = isInResizeRange(mousePos)
1071 if input.UserInputType == Enum.UserInputType.MouseButton1 and inResizeRange then
1072 local lastPos = UserInputService:GetMouseLocation()
1073 local offset = Background.AbsoluteSize - lastPos
1074 local currentPos = lastPos + offset
1075 RunService:BindToRenderStep("SIMPLESPY_RESIZE", 1, function()
1076 local newPos = UserInputService:GetMouseLocation()
1077 if newPos ~= lastPos then
1078 local currentX = (newPos + offset).X
1079 local currentY = (newPos + offset).Y
1080 if currentX < 450 then
1081 currentX = 450
1082 end
1083 if currentY < 268 then
1084 currentY = 268
1085 end
1086 currentPos = Vector2.new(currentX, currentY)
1087 Background.Size = UDim2.fromOffset(
1088 (not sideClosed and not closed and (type == "X" or type == "B")) and currentPos.X
1089 or Background.AbsoluteSize.X,
1090 (--[[(not sideClosed or currentPos.X <= LeftPanel.AbsolutePosition.X + LeftPanel.AbsoluteSize.X) and]]not closed and (type == "Y" or type == "B"))
1091 and currentPos.Y
1092 or Background.AbsoluteSize.Y
1093 )
1094 validateSize()
1095 if sideClosed then
1096 minimizeSize()
1097 else
1098 maximizeSize()
1099 end
1100 lastPos = newPos
1101 end
1102 end)
1103 table.insert(
1104 connections,
1105 UserInputService.InputEnded:Connect(function(inputE)
1106 if input == inputE then
1107 RunService:UnbindFromRenderStep("SIMPLESPY_RESIZE")
1108 end
1109 end)
1110 )
1111 elseif isInDragRange(mousePos) then
1112 onBarInput(input)
1113 end
1114end
1115
1116--- Gets the player an instance is descended from
1117function getPlayerFromInstance(instance)
1118 for _, v in pairs(Players:GetPlayers()) do
1119 if v.Character and (instance:IsDescendantOf(v.Character) or instance == v.Character) then
1120 return v
1121 end
1122 end
1123end
1124
1125--- Runs on MouseButton1Click of an event frame
1126function eventSelect(frame)
1127 if selected and selected.Log and selected.Log.Button then
1128 TweenService
1129 :Create(selected.Log.Button, TweenInfo.new(0.5), { BackgroundColor3 = Color3.fromRGB(0, 0, 0) })
1130 :Play()
1131 selected = nil
1132 end
1133 for _, v in pairs(logs) do
1134 if frame == v.Log then
1135 selected = v
1136 end
1137 end
1138 if selected and selected.Log then
1139 TweenService
1140 :Create(frame.Button, TweenInfo.new(0.5), { BackgroundColor3 = Color3.fromRGB(92, 126, 229) })
1141 :Play()
1142 codebox:setRaw(selected.GenScript)
1143 end
1144 if sideClosed then
1145 toggleSideTray()
1146 end
1147end
1148
1149--- Updates the canvas size to fit the current amount of function buttons
1150function updateFunctionCanvas()
1151 ScrollingFrame.CanvasSize = UDim2.fromOffset(UIGridLayout.AbsoluteContentSize.X, UIGridLayout.AbsoluteContentSize.Y)
1152end
1153
1154--- Updates the canvas size to fit the amount of current remotes
1155function updateRemoteCanvas()
1156 LogList.CanvasSize = UDim2.fromOffset(UIListLayout.AbsoluteContentSize.X, UIListLayout.AbsoluteContentSize.Y)
1157end
1158
1159--- Allows for toggling of the tooltip and easy setting of le description
1160--- @param enable boolean
1161--- @param text string
1162function makeToolTip(enable, text)
1163 if enable then
1164 if ToolTip.Visible then
1165 ToolTip.Visible = false
1166 RunService:UnbindFromRenderStep("ToolTip")
1167 end
1168 local first = true
1169 RunService:BindToRenderStep("ToolTip", 1, function()
1170 local topLeft = Vector2.new(Mouse.X + 20, Mouse.Y + 20)
1171 local bottomRight = topLeft + ToolTip.AbsoluteSize
1172 if topLeft.X < 0 then
1173 topLeft = Vector2.new(0, topLeft.Y)
1174 elseif bottomRight.X > workspace.CurrentCamera.ViewportSize.X then
1175 topLeft = Vector2.new(workspace.CurrentCamera.ViewportSize.X - ToolTip.AbsoluteSize.X, topLeft.Y)
1176 end
1177 if topLeft.Y < 0 then
1178 topLeft = Vector2.new(topLeft.X, 0)
1179 elseif bottomRight.Y > workspace.CurrentCamera.ViewportSize.Y - 35 then
1180 topLeft = Vector2.new(topLeft.X, workspace.CurrentCamera.ViewportSize.Y - ToolTip.AbsoluteSize.Y - 35)
1181 end
1182 if topLeft.X <= Mouse.X and topLeft.Y <= Mouse.Y then
1183 topLeft = Vector2.new(Mouse.X - ToolTip.AbsoluteSize.X - 2, Mouse.Y - ToolTip.AbsoluteSize.Y - 2)
1184 end
1185 if first then
1186 ToolTip.Position = UDim2.fromOffset(topLeft.X, topLeft.Y)
1187 first = false
1188 else
1189 ToolTip:TweenPosition(UDim2.fromOffset(topLeft.X, topLeft.Y), "Out", "Linear", 0.1)
1190 end
1191 end)
1192 TextLabel.Text = text
1193 ToolTip.Visible = true
1194 else
1195 if ToolTip.Visible then
1196 ToolTip.Visible = false
1197 RunService:UnbindFromRenderStep("ToolTip")
1198 end
1199 end
1200end
1201
1202--- Creates new function button (below codebox)
1203--- @param name string
1204---@param description function
1205---@param onClick function
1206function newButton(name, description, onClick)
1207 local button = FunctionTemplate:Clone()
1208 button.Text.Text = name
1209 button.Button.MouseEnter:Connect(function()
1210 makeToolTip(true, description())
1211 end)
1212 button.Button.MouseLeave:Connect(function()
1213 makeToolTip(false)
1214 end)
1215 button.AncestryChanged:Connect(function()
1216 makeToolTip(false)
1217 end)
1218 button.Button.MouseButton1Click:Connect(function(...)
1219 onClick(button, ...)
1220 end)
1221 button.Parent = ScrollingFrame
1222 updateFunctionCanvas()
1223end
1224
1225--- Adds new Remote to logs
1226--- @param name string The name of the remote being logged
1227--- @param type string The type of the remote being logged (either 'function' or 'event')
1228--- @param args any
1229--- @param remote any
1230--- @param function_info string
1231--- @param blocked any
1232function newRemote(type, name, args, remote, function_info, blocked, src, returnValue)
1233 local remoteFrame = RemoteTemplate:Clone()
1234 remoteFrame.Text.Text = string.sub(name, 1, 50)
1235 remoteFrame.ColorBar.BackgroundColor3 = type == "event" and Color3.new(255, 242, 0) or Color3.fromRGB(99, 86, 245)
1236 local id = Instance.new("IntValue")
1237 id.Name = "ID"
1238 id.Value = #logs + 1
1239 id.Parent = remoteFrame
1240 local weakRemoteTable = setmetatable({ remote = remote }, { __mode = "v" })
1241 local log = {
1242 Name = name,
1243 Function = function_info,
1244 Remote = weakRemoteTable,
1245 Log = remoteFrame,
1246 Blocked = blocked,
1247 Source = src,
1248 GenScript = "-- Generating, please wait... (click to reload)\n-- (If this message persists, the remote args are likely extremely long)",
1249 ReturnValue = returnValue,
1250 }
1251 logs[#logs + 1] = log
1252 schedule(function()
1253 log.GenScript = genScript(remote, args)
1254 if blocked then
1255 logs[#logs].GenScript = "-- THIS REMOTE WAS PREVENTED FROM FIRING THE SERVER BY SIMPLESPY\n\n"
1256 .. logs[#logs].GenScript
1257 end
1258 end)
1259 local connect = remoteFrame.Button.MouseButton1Click:Connect(function()
1260 eventSelect(remoteFrame)
1261 end)
1262 if layoutOrderNum < 1 then
1263 layoutOrderNum = 999999999
1264 end
1265 remoteFrame.LayoutOrder = layoutOrderNum
1266 layoutOrderNum = layoutOrderNum - 1
1267 remoteFrame.Parent = LogList
1268 table.insert(remoteLogs, 1, { connect, remoteFrame })
1269 clean()
1270 updateRemoteCanvas()
1271end
1272
1273--- Generates a script from the provided arguments (first has to be remote path)
1274function genScript(remote, args)
1275 prevTables = {}
1276 local gen = ""
1277 if #args > 0 then
1278 if not pcall(function()
1279 gen = v2v({ args = args }) .. "\n"
1280 end) then
1281 gen = gen
1282 .. "-- TableToString failure! Reverting to legacy functionality (results may vary)\nlocal args = {"
1283 if
1284 not pcall(function()
1285 for i, v in pairs(args) do
1286 if type(i) ~= "Instance" and type(i) ~= "userdata" then
1287 gen = gen .. "\n [object] = "
1288 elseif type(i) == "string" then
1289 gen = gen .. '\n ["' .. i .. '"] = '
1290 elseif type(i) == "userdata" and typeof(i) ~= "Instance" then
1291 gen = gen .. "\n [" .. string.format("nil --[[%s]]", typeof(v)) .. ")] = "
1292 elseif type(i) == "userdata" then
1293 gen = gen .. "\n [game." .. i:GetFullName() .. ")] = "
1294 end
1295 if type(v) ~= "Instance" and type(v) ~= "userdata" then
1296 gen = gen .. "object"
1297 elseif type(v) == "string" then
1298 gen = gen .. '"' .. v .. '"'
1299 elseif type(v) == "userdata" and typeof(v) ~= "Instance" then
1300 gen = gen .. string.format("nil --[[%s]]", typeof(v))
1301 elseif type(v) == "userdata" then
1302 gen = gen .. "game." .. v:GetFullName()
1303 end
1304 end
1305 gen = gen .. "\n}\n\n"
1306 end)
1307 then
1308 gen = gen .. "}\n-- Legacy tableToString failure! Unable to decompile."
1309 end
1310 end
1311 if not remote:IsDescendantOf(game) and not getnilrequired then
1312 gen = "function getNil(name,class) for _,v in pairs(getnilinstances())do if v.ClassName==class and v.Name==name then return v;end end end\n\n"
1313 .. gen
1314 end
1315 if remote:IsA("RemoteEvent") then
1316 gen = gen .. v2s(remote) .. ":FireServer(unpack(args))"
1317 elseif remote:IsA("RemoteFunction") then
1318 gen = gen .. v2s(remote) .. ":InvokeServer(unpack(args))"
1319 end
1320 else
1321 if remote:IsA("RemoteEvent") then
1322 gen = gen .. v2s(remote) .. ":FireServer()"
1323 elseif remote:IsA("RemoteFunction") then
1324 gen = gen .. v2s(remote) .. ":InvokeServer()"
1325 end
1326 end
1327 gen = "-- Script generated by SimpleSpy - credits to exx#9394\n\n" .. gen
1328 prevTables = {}
1329 return gen
1330end
1331
1332--- value-to-string: value, string (out), level (indentation), parent table, var name, is from tovar
1333function v2s(v, l, p, n, vtv, i, pt, path, tables, tI)
1334 if not tI then
1335 tI = { 0 }
1336 else
1337 tI[1] += 1
1338 end
1339 if typeof(v) == "number" then
1340 if v == math.huge then
1341 return "math.huge"
1342 elseif tostring(v):match("nan") then
1343 return "0/0 --[[NaN]]"
1344 end
1345 return tostring(v)
1346 elseif typeof(v) == "boolean" then
1347 return tostring(v)
1348 elseif typeof(v) == "string" then
1349 return formatstr(v, l)
1350 elseif typeof(v) == "function" then
1351 return f2s(v)
1352 elseif typeof(v) == "table" then
1353 return t2s(v, l, p, n, vtv, i, pt, path, tables, tI)
1354 elseif typeof(v) == "Instance" then
1355 return i2p(v)
1356 elseif typeof(v) == "userdata" then
1357 return "newproxy(true)"
1358 elseif type(v) == "userdata" then
1359 return u2s(v)
1360 elseif type(v) == "vector" then
1361 return string.format("Vector3.new(%s, %s, %s)", v2s(v.X), v2s(v.Y), v2s(v.Z))
1362 else
1363 return "nil --[[" .. typeof(v) .. "]]"
1364 end
1365end
1366
1367--- value-to-variable
1368--- @param t any
1369function v2v(t)
1370 topstr = ""
1371 bottomstr = ""
1372 getnilrequired = false
1373 local ret = ""
1374 local count = 1
1375 for i, v in pairs(t) do
1376 if type(i) == "string" and i:match("^[%a_]+[%w_]*$") then
1377 ret = ret .. "local " .. i .. " = " .. v2s(v, nil, nil, i, true) .. "\n"
1378 elseif tostring(i):match("^[%a_]+[%w_]*$") then
1379 ret = ret
1380 .. "local "
1381 .. tostring(i):lower()
1382 .. "_"
1383 .. tostring(count)
1384 .. " = "
1385 .. v2s(v, nil, nil, tostring(i):lower() .. "_" .. tostring(count), true)
1386 .. "\n"
1387 else
1388 ret = ret
1389 .. "local "
1390 .. type(v)
1391 .. "_"
1392 .. tostring(count)
1393 .. " = "
1394 .. v2s(v, nil, nil, type(v) .. "_" .. tostring(count), true)
1395 .. "\n"
1396 end
1397 count = count + 1
1398 end
1399 if getnilrequired then
1400 topstr = "function getNil(name,class) for _,v in pairs(getnilinstances())do if v.ClassName==class and v.Name==name then return v;end end end\n"
1401 .. topstr
1402 end
1403 if #topstr > 0 then
1404 ret = topstr .. "\n" .. ret
1405 end
1406 if #bottomstr > 0 then
1407 ret = ret .. bottomstr
1408 end
1409 return ret
1410end
1411
1412--- table-to-string
1413--- @param t table
1414--- @param l number
1415--- @param p table
1416--- @param n string
1417--- @param vtv boolean
1418--- @param i any
1419--- @param pt table
1420--- @param path string
1421--- @param tables table
1422--- @param tI table
1423function t2s(t, l, p, n, vtv, i, pt, path, tables, tI)
1424 local globalIndex = table.find(getgenv(), t) -- checks if table is a global
1425 if type(globalIndex) == "string" then
1426 return globalIndex
1427 end
1428 if not tI then
1429 tI = { 0 }
1430 end
1431 if not path then -- sets path to empty string (so it doesn't have to manually provided every time)
1432 path = ""
1433 end
1434 if not l then -- sets the level to 0 (for indentation) and tables for logging tables it already serialized
1435 l = 0
1436 tables = {}
1437 end
1438 if not p then -- p is the previous table but doesn't really matter if it's the first
1439 p = t
1440 end
1441 for _, v in pairs(tables) do -- checks if the current table has been serialized before
1442 if n and rawequal(v, t) then
1443 bottomstr = bottomstr
1444 .. "\n"
1445 .. tostring(n)
1446 .. tostring(path)
1447 .. " = "
1448 .. tostring(n)
1449 .. tostring(({ v2p(v, p) })[2])
1450 return "{} --[[DUPLICATE]]"
1451 end
1452 end
1453 table.insert(tables, t) -- logs table to past tables
1454 local s = "{" -- start of serialization
1455 local size = 0
1456 l = l + indent -- set indentation level
1457 for k, v in pairs(t) do -- iterates over table
1458 size = size + 1 -- changes size for max limit
1459 if size > (_G.SimpleSpyMaxTableSize or 1000) then
1460 s = s
1461 .. "\n"
1462 .. string.rep(" ", l)
1463 .. "-- MAXIMUM TABLE SIZE REACHED, CHANGE '_G.SimpleSpyMaxTableSize' TO ADJUST MAXIMUM SIZE "
1464 break
1465 end
1466 if rawequal(k, t) then -- checks if the table being iterated over is being used as an index within itself (yay, lua)
1467 bottomstr = bottomstr
1468 .. "\n"
1469 .. tostring(n)
1470 .. tostring(path)
1471 .. "["
1472 .. tostring(n)
1473 .. tostring(path)
1474 .. "]"
1475 .. " = "
1476 .. (
1477 rawequal(v, k) and tostring(n) .. tostring(path)
1478 or v2s(v, l, p, n, vtv, k, t, path .. "[" .. tostring(n) .. tostring(path) .. "]", tables)
1479 )
1480 size -= 1
1481 continue
1482 end
1483 local currentPath = "" -- initializes the path of 'v' within 't'
1484 if type(k) == "string" and k:match("^[%a_]+[%w_]*$") then -- cleanly handles table path generation (for the first half)
1485 currentPath = "." .. k
1486 else
1487 currentPath = "[" .. k2s(k, l, p, n, vtv, k, t, path .. currentPath, tables, tI) .. "]"
1488 end
1489 if size % 100 == 0 then
1490 scheduleWait()
1491 end
1492 -- actually serializes the member of the table
1493 s = s
1494 .. "\n"
1495 .. string.rep(" ", l)
1496 .. "["
1497 .. k2s(k, l, p, n, vtv, k, t, path .. currentPath, tables, tI)
1498 .. "] = "
1499 .. v2s(v, l, p, n, vtv, k, t, path .. currentPath, tables, tI)
1500 .. ","
1501 end
1502 if #s > 1 then -- removes the last comma because it looks nicer (no way to tell if it's done 'till it's done so...)
1503 s = s:sub(1, #s - 1)
1504 end
1505 if size > 0 then -- cleanly indents the last curly bracket
1506 s = s .. "\n" .. string.rep(" ", l - indent)
1507 end
1508 return s .. "}"
1509end
1510
1511--- key-to-string
1512function k2s(v, ...)
1513 if keyToString then
1514 if typeof(v) == "userdata" and getrawmetatable(v) then
1515 return string.format(
1516 '"<void> (%s)" --[[Potentially hidden data (tostring in SimpleSpy:HookRemote/GetRemoteFiredSignal at your own risk)]]',
1517 safetostring(v)
1518 )
1519 elseif typeof(v) == "userdata" then
1520 return string.format('"<void> (%s)"', safetostring(v))
1521 elseif type(v) == "userdata" and typeof(v) ~= "Instance" then
1522 return string.format('"<%s> (%s)"', typeof(v), tostring(v))
1523 elseif type(v) == "function" then
1524 return string.format('"<Function> (%s)"', tostring(v))
1525 end
1526 end
1527 return v2s(v, ...)
1528end
1529
1530--- function-to-string
1531function f2s(f)
1532 for k, x in pairs(getgenv()) do
1533 local isgucci, gpath
1534 if rawequal(x, f) then
1535 isgucci, gpath = true, ""
1536 elseif type(x) == "table" then
1537 isgucci, gpath = v2p(f, x)
1538 end
1539 if isgucci and type(k) ~= "function" then
1540 if type(k) == "string" and k:match("^[%a_]+[%w_]*$") then
1541 return k .. gpath
1542 else
1543 return "getgenv()[" .. v2s(k) .. "]" .. gpath
1544 end
1545 end
1546 end
1547 if funcEnabled and debug.getinfo(f).name:match("^[%a_]+[%w_]*$") then
1548 return "function()end --[[" .. debug.getinfo(f).name .. "]]"
1549 end
1550 return "function()end --[[" .. tostring(f) .. "]]"
1551end
1552
1553--- instance-to-path
1554--- @param i userdata
1555function i2p(i)
1556 local player = getplayer(i)
1557 local parent = i
1558 local out = ""
1559 if parent == nil then
1560 return "nil"
1561 elseif player then
1562 while true do
1563 if parent and parent == player.Character then
1564 if player == Players.LocalPlayer then
1565 return 'game:GetService("Players").LocalPlayer.Character' .. out
1566 else
1567 return i2p(player) .. ".Character" .. out
1568 end
1569 else
1570 if parent.Name:match("[%a_]+[%w+]*") ~= parent.Name then
1571 out = ":FindFirstChild(" .. formatstr(parent.Name) .. ")" .. out
1572 else
1573 out = "." .. parent.Name .. out
1574 end
1575 end
1576 parent = parent.Parent
1577 end
1578 elseif parent ~= game then
1579 while true do
1580 if parent and parent.Parent == game then
1581 local service = game:FindService(parent.ClassName)
1582 if service then
1583 if parent.ClassName == "Workspace" then
1584 return "workspace" .. out
1585 else
1586 return 'game:GetService("' .. service.ClassName .. '")' .. out
1587 end
1588 else
1589 if parent.Name:match("[%a_]+[%w_]*") then
1590 return "game." .. parent.Name .. out
1591 else
1592 return "game:FindFirstChild(" .. formatstr(parent.Name) .. ")" .. out
1593 end
1594 end
1595 elseif parent.Parent == nil then
1596 getnilrequired = true
1597 return "getNil(" .. formatstr(parent.Name) .. ', "' .. parent.ClassName .. '")' .. out
1598 elseif parent == Players.LocalPlayer then
1599 out = ".LocalPlayer" .. out
1600 else
1601 if parent.Name:match("[%a_]+[%w_]*") ~= parent.Name then
1602 out = ":FindFirstChild(" .. formatstr(parent.Name) .. ")" .. out
1603 else
1604 out = "." .. parent.Name .. out
1605 end
1606 end
1607 parent = parent.Parent
1608 end
1609 else
1610 return "game"
1611 end
1612end
1613
1614--- userdata-to-string: userdata
1615--- @param u userdata
1616function u2s(u)
1617 if typeof(u) == "TweenInfo" then
1618 -- TweenInfo
1619 return "TweenInfo.new("
1620 .. tostring(u.Time)
1621 .. ", Enum.EasingStyle."
1622 .. tostring(u.EasingStyle)
1623 .. ", Enum.EasingDirection."
1624 .. tostring(u.EasingDirection)
1625 .. ", "
1626 .. tostring(u.RepeatCount)
1627 .. ", "
1628 .. tostring(u.Reverses)
1629 .. ", "
1630 .. tostring(u.DelayTime)
1631 .. ")"
1632 elseif typeof(u) == "Ray" then
1633 -- Ray
1634 return "Ray.new(" .. u2s(u.Origin) .. ", " .. u2s(u.Direction) .. ")"
1635 elseif typeof(u) == "NumberSequence" then
1636 -- NumberSequence
1637 local ret = "NumberSequence.new("
1638 for i, v in pairs(u.KeyPoints) do
1639 ret = ret .. tostring(v)
1640 if i < #u.Keypoints then
1641 ret = ret .. ", "
1642 end
1643 end
1644 return ret .. ")"
1645 elseif typeof(u) == "DockWidgetPluginGuiInfo" then
1646 -- DockWidgetPluginGuiInfo
1647 return "DockWidgetPluginGuiInfo.new(Enum.InitialDockState" .. tostring(u) .. ")"
1648 elseif typeof(u) == "ColorSequence" then
1649 -- ColorSequence
1650 local ret = "ColorSequence.new("
1651 for i, v in pairs(u.KeyPoints) do
1652 ret = ret .. "Color3.new(" .. tostring(v) .. ")"
1653 if i < #u.Keypoints then
1654 ret = ret .. ", "
1655 end
1656 end
1657 return ret .. ")"
1658 elseif typeof(u) == "BrickColor" then
1659 -- BrickColor
1660 return "BrickColor.new(" .. tostring(u.Number) .. ")"
1661 elseif typeof(u) == "NumberRange" then
1662 -- NumberRange
1663 return "NumberRange.new(" .. tostring(u.Min) .. ", " .. tostring(u.Max) .. ")"
1664 elseif typeof(u) == "Region3" then
1665 -- Region3
1666 local center = u.CFrame.Position
1667 local size = u.CFrame.Size
1668 local vector1 = center - size / 2
1669 local vector2 = center + size / 2
1670 return "Region3.new(" .. u2s(vector1) .. ", " .. u2s(vector2) .. ")"
1671 elseif typeof(u) == "Faces" then
1672 -- Faces
1673 local faces = {}
1674 if u.Top then
1675 table.insert(faces, "Enum.NormalId.Top")
1676 end
1677 if u.Bottom then
1678 table.insert(faces, "Enum.NormalId.Bottom")
1679 end
1680 if u.Left then
1681 table.insert(faces, "Enum.NormalId.Left")
1682 end
1683 if u.Right then
1684 table.insert(faces, "Enum.NormalId.Right")
1685 end
1686 if u.Back then
1687 table.insert(faces, "Enum.NormalId.Back")
1688 end
1689 if u.Front then
1690 table.insert(faces, "Enum.NormalId.Front")
1691 end
1692 return "Faces.new(" .. table.concat(faces, ", ") .. ")"
1693 elseif typeof(u) == "EnumItem" then
1694 return tostring(u)
1695 elseif typeof(u) == "Enums" then
1696 return "Enum"
1697 elseif typeof(u) == "Enum" then
1698 return "Enum." .. tostring(u)
1699 elseif typeof(u) == "RBXScriptSignal" then
1700 return "nil --[[RBXScriptSignal]]"
1701 elseif typeof(u) == "Vector3" then
1702 return string.format("Vector3.new(%s, %s, %s)", v2s(u.X), v2s(u.Y), v2s(u.Z))
1703 elseif typeof(u) == "CFrame" then
1704 local xAngle, yAngle, zAngle = u:ToEulerAnglesXYZ()
1705 return string.format(
1706 "CFrame.new(%s, %s, %s) * CFrame.Angles(%s, %s, %s)",
1707 v2s(u.X),
1708 v2s(u.Y),
1709 v2s(u.Z),
1710 v2s(xAngle),
1711 v2s(yAngle),
1712 v2s(zAngle)
1713 )
1714 elseif typeof(u) == "DockWidgetPluginGuiInfo" then
1715 return string.format(
1716 "DockWidgetPluginGuiInfo(%s, %s, %s, %s, %s, %s, %s)",
1717 "Enum.InitialDockState.Right",
1718 v2s(u.InitialEnabled),
1719 v2s(u.InitialEnabledShouldOverrideRestore),
1720 v2s(u.FloatingXSize),
1721 v2s(u.FloatingYSize),
1722 v2s(u.MinWidth),
1723 v2s(u.MinHeight)
1724 )
1725 elseif typeof(u) == "PathWaypoint" then
1726 return string.format("PathWaypoint.new(%s, %s)", v2s(u.Position), v2s(u.Action))
1727 elseif typeof(u) == "UDim" then
1728 return string.format("UDim.new(%s, %s)", v2s(u.Scale), v2s(u.Offset))
1729 elseif typeof(u) == "UDim2" then
1730 return string.format(
1731 "UDim2.new(%s, %s, %s, %s)",
1732 v2s(u.X.Scale),
1733 v2s(u.X.Offset),
1734 v2s(u.Y.Scale),
1735 v2s(u.Y.Offset)
1736 )
1737 elseif typeof(u) == "Rect" then
1738 return string.format("Rect.new(%s, %s)", v2s(u.Min), v2s(u.Max))
1739 else
1740 return string.format("nil --[[%s]]", typeof(u))
1741 end
1742end
1743
1744--- Gets the player an instance is descended from
1745function getplayer(instance)
1746 for _, v in pairs(Players:GetPlayers()) do
1747 if v.Character and (instance:IsDescendantOf(v.Character) or instance == v.Character) then
1748 return v
1749 end
1750 end
1751end
1752
1753--- value-to-path (in table)
1754function v2p(x, t, path, prev)
1755 if not path then
1756 path = ""
1757 end
1758 if not prev then
1759 prev = {}
1760 end
1761 if rawequal(x, t) then
1762 return true, ""
1763 end
1764 for i, v in pairs(t) do
1765 if rawequal(v, x) then
1766 if type(i) == "string" and i:match("^[%a_]+[%w_]*$") then
1767 return true, (path .. "." .. i)
1768 else
1769 return true, (path .. "[" .. v2s(i) .. "]")
1770 end
1771 end
1772 if type(v) == "table" then
1773 local duplicate = false
1774 for _, y in pairs(prev) do
1775 if rawequal(y, v) then
1776 duplicate = true
1777 end
1778 end
1779 if not duplicate then
1780 table.insert(prev, t)
1781 local found
1782 found, p = v2p(x, v, path, prev)
1783 if found then
1784 if type(i) == "string" and i:match("^[%a_]+[%w_]*$") then
1785 return true, "." .. i .. p
1786 else
1787 return true, "[" .. v2s(i) .. "]" .. p
1788 end
1789 end
1790 end
1791 end
1792 end
1793 return false, ""
1794end
1795
1796--- format s: string, byte encrypt (for weird symbols)
1797function formatstr(s, indentation)
1798 if not indentation then
1799 indentation = 0
1800 end
1801 local handled, reachedMax = handlespecials(s, indentation)
1802 return '"'
1803 .. handled
1804 .. '"'
1805 .. (
1806 reachedMax
1807 and " --[[ MAXIMUM STRING SIZE REACHED, CHANGE '_G.SimpleSpyMaxStringSize' TO ADJUST MAXIMUM SIZE ]]"
1808 or ""
1809 )
1810end
1811
1812--- Adds \'s to the text as a replacement to whitespace chars and other things because string.format can't yayeet
1813function handlespecials(value, indentation)
1814 local buildStr = {}
1815 local i = 1
1816 local char = string.sub(value, i, i)
1817 local indentStr
1818 while char ~= "" do
1819 if char == '"' then
1820 buildStr[i] = '\\"'
1821 elseif char == "\\" then
1822 buildStr[i] = "\\\\"
1823 elseif char == "\n" then
1824 buildStr[i] = "\\n"
1825 elseif char == "\t" then
1826 buildStr[i] = "\\t"
1827 elseif string.byte(char) > 126 or string.byte(char) < 32 then
1828 buildStr[i] = string.format("\\%d", string.byte(char))
1829 else
1830 buildStr[i] = char
1831 end
1832 i = i + 1
1833 char = string.sub(value, i, i)
1834 if i % 200 == 0 then
1835 indentStr = indentStr or string.rep(" ", indentation + indent)
1836 table.move({ '"\n', indentStr, '... "' }, 1, 3, i, buildStr)
1837 i += 3
1838 end
1839 end
1840 return table.concat(buildStr)
1841end
1842
1843-- safe (ish) tostring
1844function safetostring(v: any)
1845 if typeof(v) == "userdata" or type(v) == "table" then
1846 local mt = getrawmetatable(v)
1847 local badtostring = mt and rawget(mt, "__tostring")
1848 if mt and badtostring then
1849 rawset(mt, "__tostring", nil)
1850 local out = tostring(v)
1851 rawset(mt, "__tostring", badtostring)
1852 return out
1853 end
1854 end
1855 return tostring(v)
1856end
1857
1858--- finds script from 'src' from getinfo, returns nil if not found
1859--- @param src string
1860function getScriptFromSrc(src)
1861 local realPath
1862 local runningTest
1863 --- @type number
1864 local s, e
1865 local match = false
1866 if src:sub(1, 1) == "=" then
1867 realPath = game
1868 s = 2
1869 else
1870 runningTest = src:sub(2, e and e - 1 or -1)
1871 for _, v in pairs(getnilinstances()) do
1872 if v.Name == runningTest then
1873 realPath = v
1874 break
1875 end
1876 end
1877 s = #runningTest + 1
1878 end
1879 if realPath then
1880 e = src:sub(s, -1):find("%.")
1881 local i = 0
1882 repeat
1883 i += 1
1884 if not e then
1885 runningTest = src:sub(s, -1)
1886 local test = realPath.FindFirstChild(realPath, runningTest)
1887 if test then
1888 realPath = test
1889 end
1890 match = true
1891 else
1892 runningTest = src:sub(s, e)
1893 local test = realPath.FindFirstChild(realPath, runningTest)
1894 local yeOld = e
1895 if test then
1896 realPath = test
1897 s = e + 2
1898 e = src:sub(e + 2, -1):find("%.")
1899 e = e and e + yeOld or e
1900 else
1901 e = src:sub(e + 2, -1):find("%.")
1902 e = e and e + yeOld or e
1903 end
1904 end
1905 until match or i >= 50
1906 end
1907 return realPath
1908end
1909
1910--- schedules the provided function (and calls it with any args after)
1911function schedule(f, ...)
1912 table.insert(scheduled, { f, ... })
1913end
1914
1915--- yields the current thread until the scheduler gives the ok
1916function scheduleWait()
1917 local thread = coroutine.running()
1918 schedule(function()
1919 coroutine.resume(thread)
1920 end)
1921 coroutine.yield()
1922end
1923
1924--- the big (well tbh small now) boi task scheduler himself, handles p much anything as quicc as possible
1925function taskscheduler()
1926 if not toggle then
1927 scheduled = {}
1928 return
1929 end
1930 if #scheduled > 1000 then
1931 table.remove(scheduled, #scheduled)
1932 end
1933 if #scheduled > 0 then
1934 local currentf = scheduled[1]
1935 table.remove(scheduled, 1)
1936 if type(currentf) == "table" and type(currentf[1]) == "function" then
1937 pcall(unpack(currentf))
1938 end
1939 end
1940end
1941
1942--- Handles remote logs
1943function remoteHandler(hookfunction, methodName, remote, args, funcInfo, calling, returnValue)
1944 local validInstance, validClass = pcall(function()
1945 return remote:IsA("RemoteEvent") or remote:IsA("RemoteFunction")
1946 end)
1947 if validInstance and validClass then
1948 local func = funcInfo.func
1949 if not calling then
1950 _, calling = pcall(getScriptFromSrc, funcInfo.source)
1951 end
1952 coroutine.wrap(function()
1953 if remoteSignals[remote] then
1954 remoteSignals[remote]:Fire(args)
1955 end
1956 end)()
1957 if autoblock then
1958 if excluding[remote] then
1959 return
1960 end
1961 if not history[remote] then
1962 history[remote] = { badOccurances = 0, lastCall = tick() }
1963 end
1964 if tick() - history[remote].lastCall < 1 then
1965 history[remote].badOccurances += 1
1966 return
1967 else
1968 history[remote].badOccurances = 0
1969 end
1970 if history[remote].badOccurances > 3 then
1971 excluding[remote] = true
1972 return
1973 end
1974 history[remote].lastCall = tick()
1975 end
1976 local functionInfoStr
1977 local src
1978 if func and islclosure(func) then
1979 local functionInfo = {}
1980 functionInfo.info = funcInfo
1981 pcall(function()
1982 functionInfo.constants = debug.getconstants(func)
1983 end)
1984 pcall(function()
1985 functionInfoStr = v2v({ functionInfo = functionInfo })
1986 end)
1987 pcall(function()
1988 if type(calling) == "userdata" then
1989 src = calling
1990 end
1991 end)
1992 end
1993 if methodName:lower() == "fireserver" then
1994 newRemote(
1995 "event",
1996 remote.Name,
1997 args,
1998 remote,
1999 functionInfoStr,
2000 (blocklist[remote] or blocklist[remote.Name]),
2001 src
2002 )
2003 elseif methodName:lower() == "invokeserver" then
2004 newRemote(
2005 "function",
2006 remote.Name,
2007 args,
2008 remote,
2009 functionInfoStr,
2010 (blocklist[remote] or blocklist[remote.Name]),
2011 src,
2012 returnValue
2013 )
2014 end
2015 end
2016end
2017
2018--- Used for hookfunction
2019function hookRemote(remoteType, remote, ...)
2020 if typeof(remote) == "Instance" then
2021 local args = { ... }
2022 local validInstance, remoteName = pcall(function()
2023 return remote.Name
2024 end)
2025 if validInstance and not (blacklist[remote] or blacklist[remoteName]) then
2026 local funcInfo = {}
2027 local calling
2028 if funcEnabled then
2029 funcInfo = debug.getinfo(4) or funcInfo
2030 calling = useGetCallingScript and getcallingscript() or nil
2031 end
2032 if recordReturnValues and remoteType == "RemoteFunction" then
2033 local thread = coroutine.running()
2034 local args = { ... }
2035 task.defer(function()
2036 local returnValue
2037 if remoteHooks[remote] then
2038 args = { remoteHooks[remote](unpack(args)) }
2039 returnValue = originalFunction(remote, unpack(args))
2040 else
2041 returnValue = originalFunction(remote, unpack(args))
2042 end
2043 schedule(
2044 remoteHandler,
2045 true,
2046 remoteType == "RemoteEvent" and "fireserver" or "invokeserver",
2047 remote,
2048 args,
2049 funcInfo,
2050 calling,
2051 returnValue
2052 )
2053 if blocklist[remote] or blocklist[remoteName] then
2054 coroutine.resume(thread)
2055 else
2056 coroutine.resume(thread, unpack(returnValue))
2057 end
2058 end)
2059 else
2060 schedule(
2061 remoteHandler,
2062 true,
2063 remoteType == "RemoteEvent" and "fireserver" or "invokeserver",
2064 remote,
2065 args,
2066 funcInfo,
2067 calling
2068 )
2069 if blocklist[remote] or blocklist[remoteName] then
2070 return
2071 end
2072 end
2073 end
2074 end
2075 if recordReturnValues and remoteType == "RemoteFunction" then
2076 return coroutine.yield()
2077 elseif remoteType == "RemoteEvent" then
2078 if remoteHooks[remote] then
2079 return originalEvent(remote, remoteHooks[remote](...))
2080 end
2081 return originalEvent(remote, ...)
2082 else
2083 if remoteHooks[remote] then
2084 return originalFunction(remote, remoteHooks[remote](...))
2085 end
2086 return originalFunction(remote, ...)
2087 end
2088end
2089
2090local newnamecall = newcclosure(function(remote, ...)
2091 if typeof(remote) == "Instance" then
2092 local args = { ... }
2093 local methodName = getnamecallmethod()
2094 local validInstance, remoteName = pcall(function()
2095 return remote.Name
2096 end)
2097 if
2098 validInstance
2099 and (methodName == "FireServer" or methodName == "fireServer" or methodName == "InvokeServer" or methodName == "invokeServer")
2100 and not (blacklist[remote] or blacklist[remoteName])
2101 then
2102 local funcInfo = {}
2103 local calling
2104 if funcEnabled then
2105 funcInfo = debug.getinfo(3) or funcInfo
2106 calling = useGetCallingScript and getcallingscript() or nil
2107 end
2108 if recordReturnValues and (methodName == "InvokeServer" or methodName == "invokeServer") then
2109 local namecallThread = coroutine.running()
2110 local args = { ... }
2111 task.defer(function()
2112 local returnValue
2113 setnamecallmethod(methodName)
2114 if remoteHooks[remote] then
2115 args = { remoteHooks[remote](unpack(args)) }
2116 returnValue = { original(remote, unpack(args)) }
2117 else
2118 returnValue = { original(remote, unpack(args)) }
2119 end
2120 coroutine.resume(namecallThread, unpack(returnValue))
2121 coroutine.wrap(function()
2122 schedule(remoteHandler, false, methodName, remote, args, funcInfo, calling, returnValue)
2123 end)()
2124 end)
2125 else
2126 coroutine.wrap(function()
2127 schedule(remoteHandler, false, methodName, remote, args, funcInfo, calling)
2128 end)()
2129 end
2130 end
2131 if recordReturnValues and (methodName == "InvokeServer" or methodName == "invokeServer") then
2132 return coroutine.yield()
2133 elseif
2134 validInstance
2135 and (methodName == "FireServer" or methodName == "fireServer" or methodName == "InvokeServer" or methodName == "invokeServer")
2136 and (blocklist[remote] or blocklist[remoteName])
2137 then
2138 return nil
2139 elseif
2140 (not recordReturnValues or methodName ~= "InvokeServer" or methodName ~= "invokeServer")
2141 and validInstance
2142 and (methodName == "FireServer" or methodName == "fireServer" or methodName == "InvokeServer" or methodName == "invokeServer")
2143 and remoteHooks[remote]
2144 then
2145 return original(remote, remoteHooks[remote](...))
2146 else
2147 return original(remote, ...)
2148 end
2149 end
2150 return original(remote, ...)
2151end, original)
2152
2153local newFireServer = newcclosure(function(...)
2154 return hookRemote("RemoteEvent", ...)
2155end, originalEvent)
2156
2157local newInvokeServer = newcclosure(function(...)
2158 return hookRemote("RemoteFunction", ...)
2159end, originalFunction)
2160
2161--- Toggles on and off the remote spy
2162function toggleSpy()
2163 if not toggle then
2164 if hookmetamethod then
2165 local oldNamecall = hookmetamethod(game, "__namecall", newnamecall)
2166 original = original or function(...)
2167 return oldNamecall(...)
2168 end
2169 _G.OriginalNamecall = original
2170 else
2171 gm = gm or getrawmetatable(game)
2172 original = original or function(...)
2173 return gm.__namecall(...)
2174 end
2175 setreadonly(gm, false)
2176 if not original then
2177 warn("SimpleSpy: namecall method not found!")
2178 onToggleButtonClick()
2179 return
2180 end
2181 gm.__namecall = newnamecall
2182 setreadonly(gm, true)
2183 end
2184 originalEvent = hookfunction(remoteEvent.FireServer, newFireServer)
2185 originalFunction = hookfunction(remoteFunction.InvokeServer, newInvokeServer)
2186 else
2187 if hookmetamethod then
2188 if original then
2189 hookmetamethod(game, "__namecall", original)
2190 end
2191 else
2192 gm = gm or getrawmetatable(game)
2193 setreadonly(gm, false)
2194 gm.__namecall = original
2195 setreadonly(gm, true)
2196 end
2197 hookfunction(remoteEvent.FireServer, originalEvent)
2198 hookfunction(remoteFunction.InvokeServer, originalFunction)
2199 end
2200end
2201
2202--- Toggles between the two remotespy methods (hookfunction currently = disabled)
2203function toggleSpyMethod()
2204 toggleSpy()
2205 toggle = not toggle
2206end
2207
2208--- Shuts down the remote spy
2209function shutdown()
2210 if schedulerconnect then
2211 schedulerconnect:Disconnect()
2212 end
2213 for _, connection in pairs(connections) do
2214 coroutine.wrap(function()
2215 connection:Disconnect()
2216 end)()
2217 end
2218 SimpleSpy2:Destroy()
2219 hookfunction(remoteEvent.FireServer, originalEvent)
2220 hookfunction(remoteFunction.InvokeServer, originalFunction)
2221 if hookmetamethod then
2222 if original then
2223 hookmetamethod(game, "__namecall", original)
2224 end
2225 else
2226 gm = gm or getrawmetatable(game)
2227 setreadonly(gm, false)
2228 gm.__namecall = original
2229 setreadonly(gm, true)
2230 end
2231 _G.SimpleSpyExecuted = false
2232end
2233
2234-- main
2235if not _G.SimpleSpyExecuted then
2236 local succeeded, err = pcall(function()
2237 if not RunService:IsClient() then
2238 error("SimpleSpy cannot run on the server!")
2239 end
2240 if
2241 not hookfunction
2242 or not getrawmetatable
2243 or getrawmetatable and not getrawmetatable(game).__namecall
2244 or not setreadonly
2245 then
2246 local missing = {}
2247 if not hookfunction then
2248 table.insert(missing, "hookfunction")
2249 end
2250 if not getrawmetatable then
2251 table.insert(missing, "getrawmetatable")
2252 end
2253 if getrawmetatable and not getrawmetatable(game).__namecall then
2254 table.insert(missing, "getrawmetatable(game).__namecall")
2255 end
2256 if not setreadonly then
2257 table.insert(missing, "setreadonly")
2258 end
2259 shutdown()
2260 error(
2261 "This environment does not support method hooks!\n(Your exploit is not capable of running SimpleSpy)\nMissing: "
2262 .. table.concat(missing, ", ")
2263 )
2264 end
2265 _G.SimpleSpyShutdown = shutdown
2266 ContentProvider:PreloadAsync({
2267 "rbxassetid://6065821980",
2268 "rbxassetid://6065774948",
2269 "rbxassetid://6065821086",
2270 "rbxassetid://6065821596",
2271 ImageLabel,
2272 ImageLabel_2,
2273 ImageLabel_3,
2274 })
2275 -- if gethui then funcEnabled = false end
2276 onToggleButtonClick()
2277 RemoteTemplate.Parent = nil
2278 FunctionTemplate.Parent = nil
2279 codebox = Highlight.new(CodeBox)
2280 codebox:setRaw("")
2281 getgenv().SimpleSpy = SimpleSpy
2282 getgenv().getNil = function(name, class)
2283 for _, v in pairs(getnilinstances()) do
2284 if v.ClassName == class and v.Name == name then
2285 return v
2286 end
2287 end
2288 end
2289 TextLabel:GetPropertyChangedSignal("Text"):Connect(scaleToolTip)
2290 -- TopBar.InputBegan:Connect(onBarInput)
2291 MinimizeButton.MouseButton1Click:Connect(toggleMinimize)
2292 MaximizeButton.MouseButton1Click:Connect(toggleSideTray)
2293 Simple.MouseButton1Click:Connect(onToggleButtonClick)
2294 CloseButton.MouseEnter:Connect(onXButtonHover)
2295 CloseButton.MouseLeave:Connect(onXButtonUnhover)
2296 Simple.MouseEnter:Connect(onToggleButtonHover)
2297 Simple.MouseLeave:Connect(onToggleButtonUnhover)
2298 CloseButton.MouseButton1Click:Connect(shutdown)
2299 table.insert(connections, UserInputService.InputBegan:Connect(backgroundUserInput))
2300 connectResize()
2301 SimpleSpy2.Enabled = true
2302 coroutine.wrap(function()
2303 wait(1)
2304 onToggleButtonUnhover()
2305 end)()
2306 schedulerconnect = RunService.Heartbeat:Connect(taskscheduler)
2307 if syn and syn.protect_gui then
2308 pcall(syn.protect_gui, SimpleSpy2)
2309 end
2310 bringBackOnResize()
2311 SimpleSpy2.Parent = --[[gethui and gethui() or]]
2312 CoreGui
2313 _G.SimpleSpyExecuted = true
2314 if not Players.LocalPlayer then
2315 Players:GetPropertyChangedSignal("LocalPlayer"):Wait()
2316 end
2317 Mouse = Players.LocalPlayer:GetMouse()
2318 oldIcon = Mouse.Icon
2319 table.insert(connections, Mouse.Move:Connect(mouseMoved))
2320 end)
2321 if not succeeded then
2322 warn(
2323 "A fatal error has occured, SimpleSpy was unable to launch properly.\nPlease DM this error message to @exx#9394:\n\n"
2324 .. tostring(err)
2325 )
2326 SimpleSpy2:Destroy()
2327 hookfunction(remoteEvent.FireServer, originalEvent)
2328 hookfunction(remoteFunction.InvokeServer, originalFunction)
2329 if hookmetamethod then
2330 if original then
2331 hookmetamethod(game, "__namecall", original)
2332 end
2333 else
2334 setreadonly(gm, false)
2335 gm.__namecall = original
2336 setreadonly(gm, true)
2337 end
2338 return
2339 end
2340else
2341 SimpleSpy2:Destroy()
2342 return
2343end
2344
2345----- ADD ONS ----- (easily add or remove additonal functionality to the RemoteSpy!)
2346--[[
2347 Some helpful things:
2348 - add your function in here, and create buttons for them through the 'newButton' function
2349 - the first argument provided is the TextButton the player clicks to run the function
2350 - generated scripts are generated when the namecall is initially fired and saved in remoteFrame objects
2351 - blacklisted remotes will be ignored directly in namecall (less lag)
2352 - the properties of a 'remoteFrame' object:
2353 {
2354 Name: (string) The name of the Remote
2355 GenScript: (string) The generated script that appears in the codebox (generated when namecall fired)
2356 Source: (Instance (LocalScript)) The script that fired/invoked the remote
2357 Remote: (Instance (RemoteEvent) | Instance (RemoteFunction)) The remote that was fired/invoked
2358 Log: (Instance (TextButton)) The button being used for the remote (same as 'selected.Log')
2359 }
2360 - globals list: (contact @exx#9394 for more information or if you have suggestions for more to be added)
2361 - closed: (boolean) whether or not the GUI is currently minimized
2362 - logs: (table[remoteFrame]) full of remoteFrame objects (properties listed above)
2363 - selected: (remoteFrame) the currently selected remoteFrame (properties listed above)
2364 - blacklist: (string[] | Instance[] (RemoteEvent) | Instance[] (RemoteFunction)) an array of blacklisted names and remotes
2365 - codebox: (Instance (TextBox)) the textbox that holds all the code- cleared often
2366]]
2367-- Copies the contents of the codebox
2368newButton("Copy Code", function()
2369 return "Click to copy code"
2370end, function()
2371 setclipboard(codebox:getString())
2372 TextLabel.Text = "Copied successfully!"
2373end)
2374
2375--- Copies the source script (that fired the remote)
2376newButton("Copy Remote", function()
2377 return "Click to copy the path of the remote"
2378end, function()
2379 if selected then
2380 setclipboard(v2s(selected.Remote.remote))
2381 TextLabel.Text = "Copied!"
2382 end
2383end)
2384
2385-- Executes the contents of the codebox through loadstring
2386newButton("Run Code", function()
2387 return "Click to execute code"
2388end, function()
2389 local orText = "Click to execute code"
2390 TextLabel.Text = "Executing..."
2391 local succeeded = pcall(function()
2392 return loadstring(codebox:getString())()
2393 end)
2394 if succeeded then
2395 TextLabel.Text = "Executed successfully!"
2396 else
2397 TextLabel.Text = "Execution error!"
2398 end
2399end)
2400
2401--- Gets the calling script (not super reliable but w/e)
2402newButton("Get Script", function()
2403 return "Click to copy calling script to clipboard\nWARNING: Not super reliable, nil == could not find"
2404end, function()
2405 if selected then
2406 setclipboard(SimpleSpy:ValueToString(selected.Source))
2407 TextLabel.Text = "Done!"
2408 end
2409end)
2410
2411--- Decompiles the script that fired the remote and puts it in the code box
2412newButton("Function Info", function()
2413 return "Click to view calling function information"
2414end, function()
2415 if selected then
2416 if selected.Function then
2417 codebox:setRaw(
2418 "-- Calling function info\n-- Generated by the SimpleSpy serializer\n\n" .. tostring(selected.Function)
2419 )
2420 end
2421 TextLabel.Text = "Done! Function info generated by the SimpleSpy Serializer."
2422 end
2423end)
2424
2425--- Clears the Remote logs
2426newButton("Clr Logs", function()
2427 return "Click to clear logs"
2428end, function()
2429 TextLabel.Text = "Clearing..."
2430 logs = {}
2431 for _, v in pairs(LogList:GetChildren()) do
2432 if not v:IsA("UIListLayout") then
2433 v:Destroy()
2434 end
2435 end
2436 codebox:setRaw("")
2437 selected = nil
2438 TextLabel.Text = "Logs cleared!"
2439end)
2440
2441--- Excludes the selected.Log Remote from the RemoteSpy
2442newButton("Exclude (i)", function()
2443 return "Click to exclude this Remote.\nExcluding a remote makes SimpleSpy ignore it, but it will continue to be usable."
2444end, function()
2445 if selected then
2446 blacklist[selected.Remote.remote] = true
2447 TextLabel.Text = "Excluded!"
2448 end
2449end)
2450
2451--- Excludes all Remotes that share the same name as the selected.Log remote from the RemoteSpy
2452newButton("Exclude (n)", function()
2453 return "Click to exclude all remotes with this name.\nExcluding a remote makes SimpleSpy ignore it, but it will continue to be usable."
2454end, function()
2455 if selected then
2456 blacklist[selected.Name] = true
2457 TextLabel.Text = "Excluded!"
2458 end
2459end)
2460
2461--- clears blacklist
2462newButton("Clr Blacklist", function()
2463 return "Click to clear the blacklist.\nExcluding a remote makes SimpleSpy ignore it, but it will continue to be usable."
2464end, function()
2465 blacklist = {}
2466 TextLabel.Text = "Blacklist cleared!"
2467end)
2468
2469--- Prevents the selected.Log Remote from firing the server (still logged)
2470newButton("Block (i)", function()
2471 return "Click to stop this remote from firing.\nBlocking a remote won't remove it from SimpleSpy logs, but it will not continue to fire the server."
2472end, function()
2473 if selected then
2474 if selected.Remote.remote then
2475 blocklist[selected.Remote.remote] = true
2476 TextLabel.Text = "Excluded!"
2477 else
2478 TextLabel.Text = "Error! Instance may no longer exist, try using Block (n)."
2479 end
2480 end
2481end)
2482
2483--- Prevents all remotes from firing that share the same name as the selected.Log remote from the RemoteSpy (still logged)
2484newButton("Block (n)", function()
2485 return "Click to stop remotes with this name from firing.\nBlocking a remote won't remove it from SimpleSpy logs, but it will not continue to fire the server."
2486end, function()
2487 if selected then
2488 blocklist[selected.Name] = true
2489 TextLabel.Text = "Excluded!"
2490 end
2491end)
2492
2493--- clears blacklist
2494newButton("Clr Blocklist", function()
2495 return "Click to stop blocking remotes.\nBlocking a remote won't remove it from SimpleSpy logs, but it will not continue to fire the server."
2496end, function()
2497 blocklist = {}
2498 TextLabel.Text = "Blocklist cleared!"
2499end)
2500
2501--- Attempts to decompile the source script
2502newButton("Decompile", function()
2503 return "Attempts to decompile source script\nWARNING: Not super reliable, nil == could not find"
2504end, function()
2505 if selected then
2506 if selected.Source then
2507 codebox:setRaw(decompile(selected.Source))
2508 TextLabel.Text = "Done!"
2509 else
2510 TextLabel.Text = "Source not found!"
2511 end
2512 end
2513end)
2514
2515newButton("Disable Info", function()
2516 return string.format(
2517 "[%s] Toggle function info (because it can cause lag in some games)",
2518 funcEnabled and "ENABLED" or "DISABLED"
2519 )
2520end, function()
2521 funcEnabled = not funcEnabled
2522 TextLabel.Text = string.format(
2523 "[%s] Toggle function info (because it can cause lag in some games)",
2524 funcEnabled and "ENABLED" or "DISABLED"
2525 )
2526end)
2527
2528newButton("Autoblock", function()
2529 return string.format(
2530 "[%s] [BETA] Intelligently detects and excludes spammy remote calls from logs",
2531 autoblock and "ENABLED" or "DISABLED"
2532 )
2533end, function()
2534 autoblock = not autoblock
2535 TextLabel.Text = string.format(
2536 "[%s] [BETA] Intelligently detects and excludes spammy remote calls from logs",
2537 autoblock and "ENABLED" or "DISABLED"
2538 )
2539 history = {}
2540 excluding = {}
2541end)
2542
2543newButton("CallingScript", function()
2544 return string.format(
2545 "[%s] [UNSAFE] Uses 'getcallingscript' to get calling script for Decompile and GetScript. Much more reliable, but opens up SimpleSpy to detection and/or instability.",
2546 useGetCallingScript and "ENABLED" or "DISABLED"
2547 )
2548end, function()
2549 useGetCallingScript = not useGetCallingScript
2550 TextLabel.Text = string.format(
2551 "[%s] [UNSAFE] Uses 'getcallingscript' to get calling script for Decompile and GetScript. Much more reliable, but opens up SimpleSpy to detection and/or instability.",
2552 useGetCallingScript and "ENABLED" or "DISABLED"
2553 )
2554end)
2555
2556newButton("KeyToString", function()
2557 return string.format(
2558 "[%s] [BETA] Uses an experimental new function to replicate Roblox's behavior when a non-primitive type is used as a key in a table. Still in development and may not properly reflect tostringed (empty) userdata.",
2559 keyToString and "ENABLED" or "DISABLED"
2560 )
2561end, function()
2562 keyToString = not keyToString
2563 TextLabel.Text = string.format(
2564 "[%s] [BETA] Uses an experimental new function to replicate Roblox's behavior when a non-primitive type is used as a key in a table. Still in development and may not properly reflect tostringed (empty) userdata.",
2565 keyToString and "ENABLED" or "DISABLED"
2566 )
2567end)
2568
2569newButton("ToggleReturnValues", function()
2570 return string.format(
2571 "[%s] [EXPERIMENTAL] Enables recording of return values for 'GetReturnValue'\n\nUse this method at your own risk, as it could be detectable.",
2572 recordReturnValues and "ENABLED" or "DISABLED"
2573 )
2574end, function()
2575 recordReturnValues = not recordReturnValues
2576 TextLabel.Text = string.format(
2577 "[%s] [EXPERIMENTAL] Enables recording of return values for 'GetReturnValue'\n\nUse this method at your own risk, as it could be detectable.",
2578 recordReturnValues and "ENABLED" or "DISABLED"
2579 )
2580end)
2581
2582newButton("GetReturnValue", function()
2583 return "[Experimental] If 'ReturnValues' is enabled, this will show the recorded return value for the RemoteFunction (if available)."
2584end, function()
2585 if selected then
2586 codebox:setRaw(SimpleSpy:ValueToVar(selected.ReturnValue, "returnValue"))
2587 end
2588end)