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