1 | -- CREDIT TO https://github.com/Upbolt/Hydroxide/ FOR INSPIRATION AND A FEW FORKED PSEUDOCODE FUNCTIONS |
2 | |
3 | --[[ TO DO: |
4 | * Add tuple support for return value in recieving pseudocode |
5 | * Make main window remote list use popups (depends on OnRightClick) |
6 | * Make arg list use right click (depends on defcon) |
7 | * Currently, deepClone sets unclonable objects to be userdatas with custom __tostring metamethods, but if the call is repeated, the userdatas get thrown away (for being in the args of the new call), making them not appear in the second call. One solution is to somehow pass a key to the userdata that will verify it is from Synapse, another is to check the caller whenever allowing userdatas, but neither of these solutions seem perfectly clean to me. Once a good solution is found, it will be implemented. Best solution is likely to set one of the metamethods to a function stored in the remotespy, so that it can be compared later. It'd be impossible for the game to get the value because of how the arg gets filtered out by roblox. |
8 | |
9 | * Need to rewrite remotespy to break it down into multiple files |
10 | - One file for frontend, one for backend, one for pseudocode generation, one for initiation. |
11 | - Backend stores the data safely and calls the frontend's functions to tell it when to render. |
12 | - Frontend renders the calls |
13 | - Frontend renders buttons that call pseudcode generation functions with data from the backend. |
14 | ]] |
15 | print("by lulas") |
16 | local mt = getrawmetatable(game) |
17 | if islclosure(mt.__namecall) or islclosure(mt.__index) or islclosure(mt.__newindex) then |
18 | error("script incompatibility detected, one of your scripts has set the game's metamethods to a luaclosure, please run the remotespy prior to that script") |
19 | end |
20 | |
21 | if not RenderWindow then |
22 | error("EXPLOIT NOT SUPPORTED - GET SYNAPSE V3") |
23 | end |
24 | |
25 | local function cleanUpSpy() |
26 | for _,v in _G.remoteSpyCallbackHooks do |
27 | restorefunction(v) |
28 | end |
29 | |
30 | for _,v in _G.remoteSpySignalHooks do |
31 | if issignalhooked(v) then |
32 | restoresignal(v) |
33 | end |
34 | end |
35 | |
36 | _G.remoteSpyMainWindow:Clear() |
37 | _G.remoteSpySettingsWindow:Clear() |
38 | _G.remoteSpyMainWindow = nil |
39 | _G.remoteSpySettingsWindow = nil |
40 | |
41 | local oldHooks = _G.remoteSpyHooks |
42 | |
43 | local unHook = syn.oth.unhook |
44 | unHook(Instance.new("RemoteEvent").FireServer, oldHooks.FireServer) |
45 | unHook(Instance.new("RemoteFunction").InvokeServer, oldHooks.InvokeServer) |
46 | unHook(Instance.new("BindableEvent").Fire, oldHooks.Fire) |
47 | unHook(Instance.new("BindableFunction").Invoke, oldHooks.Invoke) |
48 | |
49 | unHook(mt.__namecall, oldHooks.Namecall) |
50 | unHook(mt.__index, oldHooks.Index) |
51 | unHook(mt.__newindex, oldHooks.NewIndex) |
52 | end |
53 | |
54 | if _G.remoteSpyMainWindow or _G.remoteSpySettingsWindow then |
55 | cleanUpSpy() |
56 | end |
57 | |
58 | local HttpService = cloneref(game:GetService("HttpService")) |
59 | local Players = cloneref(game:GetService("Players")) |
60 | |
61 | local client, clientid |
62 | if not client then -- autoexec moment |
63 | task.spawn(function() |
64 | if not game:IsLoaded() then game.Loaded:Wait() end |
65 | client = cloneref(Players.LocalPlayer) |
66 | clientid = client:GetDebugId() |
67 | end) |
68 | end |
69 | |
70 | local Settings = { |
71 | FireServer = true, |
72 | InvokeServer = true, |
73 | Fire = false, |
74 | Invoke = false, |
75 | |
76 | OnClientEvent = false, |
77 | OnClientInvoke = false, |
78 | OnEvent = false, |
79 | OnInvoke = false, |
80 | |
81 | Paused = false, |
82 | AlwaysOnTop = true, |
83 | |
84 | CallbackButtons = false, |
85 | LogHiddenRemotesCalls = false, |
86 | MoreRepeatCallOptions = false, |
87 | CacheLimit = true, |
88 | MaxCallAmount = 1000, |
89 | ArgLimit = 25, |
90 | StoreCallStack = false, |
91 | GetCallingScriptV2 = false, |
92 | |
93 | DecompilerOutput = 1, |
94 | ConnectionsOutput = 1, |
95 | PseudocodeOutput = 1, |
96 | CallStackOutput = 1, |
97 | |
98 | PseudocodeLuaUTypes = false, |
99 | PseudocodeWatermark = true, |
100 | PseudocodeInliningMode = 2, |
101 | PseudocodeInlineRemote = true, |
102 | PseudocodeInlineHiddenNils = true, |
103 | PseudocodeFormatTables = true, |
104 | InstanceTrackerMode = 1, |
105 | OptimizedInstanceTracker = false |
106 | } |
107 | |
108 | local function saveConfig() |
109 | if not isfolder("Remote Spy Settings") then |
110 | makefolder("Remote Spy Settings") |
111 | end |
112 | writefileasync("Remote Spy Settings/Settings.json", HttpService:JSONEncode(Settings)) |
113 | end |
114 | |
115 | if not isfile("Remote Spy Settings/Settings.json") then |
116 | saveConfig() |
117 | else |
118 | local tempSettings = HttpService:JSONDecode(readfile("Remote Spy Settings/Settings.json")) |
119 | for i,v in tempSettings do -- this is in case I add new settings |
120 | if Settings[i] ~= nil and type(Settings[i]) == type(v) then |
121 | Settings[i] = v |
122 | end |
123 | end |
124 | end |
125 | |
126 | if isfile("Remote Spy Settings/Icons.ttf") then |
127 | delfile("Remote Spy Settings/Icons.ttf") -- no longer caching stuff, i'll do that later once i have a good system to cache the script and the icons |
128 | end |
129 | |
130 | local fontData = syn.request({ Url = "https://raw.githubusercontent.com/GameGuyThrowaway/RemoteSpy/main/Icons.ttf" }).Body |
131 | |
132 | Drawing:WaitForRenderer() |
133 | |
134 | local RemoteIconFont = DrawFont.Register(fontData, { |
135 | Scale = false, |
136 | Bold = false, |
137 | UseStb = false, |
138 | PixelSize = 18, |
139 | Glyphs = { |
140 | {0xE000, 0xE007} |
141 | } |
142 | }) |
143 | |
144 | local CallerIconFont = DrawFont.Register(fontData, { |
145 | Scale = false, |
146 | Bold = false, |
147 | UseStb = false, |
148 | PixelSize = 27, |
149 | Glyphs = { |
150 | {0xE008, 0xE009} |
151 | } |
152 | }) |
153 | |
154 | local fontSize = 18 |
155 | local DefaultTextFont = DrawFont.RegisterDefault("NotoSans_Regular", { |
156 | Scale = false, |
157 | Bold = false, |
158 | UseStb = true, |
159 | PixelSize = fontSize |
160 | }) |
161 | |
162 | local isHookThread, getCallStack, getOriginalThread, getDebugId, getThreadIdentity, setThreadIdentity, getn, ceil, floor, colorHSV, colorRGB, tableInsert, tableClear, tableRemove, deferFunc, spawnFunc, gsub, rep, sub, split, strformat, lower, match = syn.oth.is_hook_thread, debug.getcallstack, syn.oth.get_original_thread, game.GetDebugId, syn.get_thread_identity, syn.set_thread_identity, table.getn, math.ceil, math.floor, Color3.fromHSV, Color3.fromRGB, table.insert, table.clear, table.remove, task.defer, task.spawn, string.gsub, string.rep, string.sub, string.split, string.format, string.lower, string.match |
163 | |
164 | local IsDescendantOf = game.IsDescendantOf |
165 | |
166 | local oldIndex; -- this is for signal indexing |
167 | local oldNewIndex; -- this is for OnClientInvoke hooks |
168 | |
169 | local watermarkString = "--Pseudocode Generated by GameGuy's Remote Spy\n" |
170 | |
171 | local inf, neginf = (1/0), (-1/0) |
172 | |
173 | local othHook = syn.oth.hook |
174 | |
175 | local optimizedInstanceTrackerFunctionString = [[local function GetInstancesFromDebugIds(...) |
176 | local ids = {...} |
177 | local instances = {} |
178 | local idCount = #ids -- micro optimizations |
179 | local find = table.find |
180 | |
181 | for _,v in getnilinstances() do |
182 | local discovery = find(ids, v:GetDebugId()) |
183 | if discovery then |
184 | instances[discovery] = v |
185 | if #instances == idCount then |
186 | return unpack(instances) |
187 | end |
188 | end |
189 | end |
190 | |
191 | for _,v in getweakdescendants(game) do |
192 | local discovery = find(ids, v:GetDebugId()) |
193 | if discovery then |
194 | instances[discovery] = v |
195 | if #instances == idCount then |
196 | return unpack(instances) |
197 | end |
198 | end |
199 | end |
200 | |
201 | return unpack(instances) |
202 | end]] |
203 | |
204 | local instanceTrackerFunctionString = [[local function GetInstanceFromDebugId(id: string): Instance |
205 | for _,v in getnilinstances() do |
206 | if v:GetDebugId() == id then |
207 | return v |
208 | end |
209 | end |
210 | |
211 | for _,v in getweakdescendants(game) do |
212 | if v:GetDebugId() == id then |
213 | return v |
214 | end |
215 | end |
216 | end]] |
217 | |
218 | local red = colorRGB(255, 0, 0) |
219 | local green = colorRGB(0, 255, 0) |
220 | local white = colorRGB(255, 255, 255) |
221 | local grey = colorRGB(144, 144, 144) |
222 | local black = colorRGB() |
223 | |
224 | local styleOptions = { |
225 | WindowRounding = 5, |
226 | WindowTitleAlign = Vector2.new(0.5, 0.5), |
227 | WindowBorderSize = 1, |
228 | FrameRounding = 3, |
229 | ButtonTextAlign = Vector2.new(0, 0.5), |
230 | PopupRounding = 5, |
231 | PopupBorderSize = 1 |
232 | } |
233 | |
234 | local colorOptions = { |
235 | Border = {black, 1}, |
236 | TitleBgActive = {colorRGB(35, 35, 38), 1}, |
237 | TitleBg = {colorRGB(35, 35, 38), 1}, |
238 | TitleBgCollapsed = {colorRGB(35, 35, 38), 1}, |
239 | WindowBg = {colorRGB(50, 50, 53), 1}, |
240 | PopupBg = {colorRGB(20, 20, 23), 0.95}, |
241 | Button = {colorRGB(75, 75, 78), 1}, |
242 | ButtonHovered = {colorRGB(85, 85, 88), 1}, |
243 | ButtonActive = {colorRGB(115, 115, 118), 1}, |
244 | Text = {white, 1}, |
245 | ResizeGrip = {colorRGB(65, 65, 68), 1}, |
246 | ResizeGripActive = {colorRGB(115, 115, 118), 1}, |
247 | ResizeGripHovered = {colorRGB(85, 85, 88), 1}, |
248 | CheckMark = {white, 1}, |
249 | FrameBg = {colorRGB(20, 20, 23), 1}, |
250 | FrameBgHovered = {colorRGB(22, 22, 25), 1}, |
251 | FrameBgActive = {colorRGB(30, 30, 35), 1}, |
252 | Tab = {colorRGB(33, 36, 38), 1}, |
253 | TabActive = {colorRGB(20, 20, 23), 1}, |
254 | TabHovered = {colorRGB(119, 119, 119), 1}, |
255 | TabUnfocused = {colorRGB(60, 60, 60), 1}, |
256 | TabUnfocusedActive = {colorRGB(20, 20, 23), 1}, |
257 | HeaderHovered = {colorRGB(55, 55, 55), 1}, |
258 | HeaderActive = {colorRGB(75, 75, 75), 1}, |
259 | } |
260 | |
261 | local function resizeText(original: string, newWidth: number, proceedingChars: string, font: RenderFont) -- my fix using this and purifyString is pretty garbage as it brute forces the string size, but before that it makes a really rough guess on the maximum length of the string, allowing for general optimization for speed, but it isn't perfect. The current system is **enough**, taking approx 370 microseconds, but could be improved greatly. Fundamentally, this fix is also flawed because of how hard coded it is, and how unnecessarily it passes args through getArgString. |
262 | if type(original) ~= "string" then warn("non string text passed to resizeText"); return original end |
263 | |
264 | local charSize = font:GetTextBounds(fontSize, original).X |
265 | if charSize < newWidth then return original end |
266 | |
267 | local newCharCount = floor(newWidth/(charSize/#original)) |
268 | local bestText = sub(original, 1, newCharCount) |
269 | |
270 | if proceedingChars == "... " and fontSize == 18 then |
271 | newWidth -= 18 |
272 | else |
273 | newWidth -= font:GetTextBounds(fontSize, proceedingChars).X |
274 | end |
275 | |
276 | local steps = 0 |
277 | |
278 | local newSize = font:GetTextBounds(fontSize, bestText).X |
279 | if newSize <= newWidth then |
280 | local res = ceil((newWidth - newSize)/fontSize) |
281 | local oldText = "" |
282 | while true do |
283 | steps += 1 |
284 | local newText = sub(original, 1, newCharCount+res) |
285 | local newerSize = font:GetTextBounds(fontSize, newText).X |
286 | |
287 | if newerSize > newWidth then |
288 | if res == 1 then |
289 | return oldText .. proceedingChars |
290 | else |
291 | res = ceil(res/2) |
292 | end |
293 | else |
294 | newCharCount = #newText |
295 | oldText = newText |
296 | end |
297 | end |
298 | else |
299 | local res = ceil((newSize - newWidth)/fontSize) |
300 | while true do |
301 | steps += 1 |
302 | local newText = sub(original, 1, newCharCount-res) |
303 | local newerSize = font:GetTextBounds(fontSize, newText).X |
304 | |
305 | if newerSize < newWidth then |
306 | if res == 1 then |
307 | return newText .. proceedingChars |
308 | else |
309 | res = floor(res/2) |
310 | end |
311 | else |
312 | newCharCount = #newText |
313 | end |
314 | end |
315 | end |
316 | end |
317 | |
318 | local function newHookMetamethod(toHook, mtmethod: string, hookFunction, filter: FilterBase) |
319 | local oldFunction |
320 | |
321 | local func = getfilter(filter, function(...) |
322 | return oldFunction(...) |
323 | end, hookFunction) |
324 | |
325 | restorefunction(getrawmetatable(toHook)[mtmethod]) -- restores any old hooks |
326 | oldFunction = othHook(getrawmetatable(toHook)[mtmethod], func) -- hookmetamethod(toHook, mtmethod, func) |
327 | return oldFunction |
328 | end |
329 | |
330 | local function filteredOth(toHook, hookFunction, filter: FilterBase) |
331 | local oldFunction |
332 | |
333 | local func = getfilter(filter, function(...) |
334 | return oldFunction(...) |
335 | end, hookFunction) |
336 | |
337 | restorefunction(toHook) |
338 | oldFunction = othHook(toHook, func) |
339 | return oldFunction |
340 | end |
341 | |
342 | local function pushError(title: string, message: string) |
343 | syn.toast_notification({ |
344 | Type = ToastType.Error, |
345 | Duration = 5, |
346 | Title = message and title or "RemoteSpy", |
347 | Content = message or title |
348 | }) |
349 | end |
350 | |
351 | local function pushSuccess(message: string) |
352 | syn.toast_notification({ |
353 | Type = ToastType.Success, |
354 | Duration = 5, |
355 | Title = "Remote Spy", |
356 | Content = message |
357 | }) |
358 | end |
359 | |
360 | local function outputData(source: string, destination: number, destinationTitle: string, successMessage: string) |
361 | if destination == 1 then |
362 | setclipboard(source) |
363 | pushSuccess(successMessage .. " to Clipboard") |
364 | elseif destination == 2 then |
365 | createuitab(destinationTitle, source) |
366 | pushSuccess(successMessage .. " to External UI") |
367 | elseif destination == 3 then |
368 | pushError("Internal UI Output Not Yet Supported") |
369 | end |
370 | end |
371 | |
372 | local specialTypes = { |
373 | RBXScriptConnection = "Connection", |
374 | RBXScriptSignal = "EventInstance", |
375 | BrickColor = "int", |
376 | Rect = "Rect2D", |
377 | EnumItem = "token", |
378 | Enums = "void", |
379 | Enum = "void", |
380 | OverlapParams = "void", |
381 | userdata = "void", |
382 | RotationCurveKey = "void", |
383 | ["function"] = "Function", |
384 | table = "Table" |
385 | } |
386 | |
387 | local axis = Enum.Axis |
388 | local normalId = Enum.NormalId |
389 | |
390 | local bindableUserdataClone = { |
391 | Axes = function(original: Axes): Axes |
392 | local args = {} |
393 | if original.X and not original.Left and not original.Right then |
394 | tableInsert(args, axis.X) |
395 | elseif original.Left then |
396 | tableInsert(args, normalId.Left) |
397 | end |
398 | if original.Right then |
399 | tableInsert(args, normalId.Right) |
400 | end |
401 | |
402 | if original.Y and not original.Top and not original.Bottom then |
403 | tableInsert(args, axis.Y) |
404 | elseif original.Top then |
405 | tableInsert(args, normalId.Top) |
406 | end |
407 | if original.Bottom then |
408 | tableInsert(args, normalId.Bottom) |
409 | end |
410 | |
411 | if original.Z and not original.Front and not original.Back then |
412 | tableInsert(args, axis.Z) |
413 | elseif original.Front then |
414 | tableInsert(args, normalId.Front) |
415 | end |
416 | if original.Back then |
417 | tableInsert(args, normalId.Back) |
418 | end |
419 | |
420 | return Axes.new(unpack(args)) |
421 | end, |
422 | BrickColor = function(original: BrickColor): BrickColor |
423 | return BrickColor.new(original.Number) |
424 | end, |
425 | CatalogSearchParams = function(original: CatalogSearchParams): CatalogSearchParams |
426 | local clone: CatalogSearchParams = CatalogSearchParams.new() |
427 | clone.AssetTypes = original.AssetTypes |
428 | clone.BundleTypes = original.BundleTypes |
429 | clone.CategoryFilter = original.CategoryFilter |
430 | clone.MaxPrice = original.MaxPrice |
431 | clone.MinPrice = original.MinPrice |
432 | clone.SearchKeyword = original.SearchKeyword |
433 | clone.SortType = original.SortType |
434 | |
435 | return clone |
436 | end, |
437 | CFrame = function(original: CFrame): CFrame |
438 | return CFrame.fromMatrix(original.Position, original.XVector, original.YVector, original.ZVector) |
439 | end, |
440 | Color3 = function(original: Color3): Color3 |
441 | return colorRGB(original.R, original.G, original.B) |
442 | end, |
443 | ColorSequence = function(original: ColorSequence): ColorSequence |
444 | return ColorSequence.new(original.Keypoints) |
445 | end, |
446 | ColorSequenceKeypoint = function(original: ColorSequenceKeypoint): ColorSequenceKeypoint |
447 | return ColorSequenceKeypoint.new(original.Time, original.Value) |
448 | end, |
449 | DateTime = function(original: DateTime): DateTime |
450 | return DateTime.fromUnixTimestamp(original.UnixTimestamp) |
451 | end, |
452 | DockWidgetPluginGuiInfo = function(original: DockWidgetPluginGuiInfo): DockWidgetPluginGuiInfo |
453 | local arguments = split(tostring(original), " ") |
454 | local dockState: string = sub(arguments[1], 18, -1) |
455 | local initialEnabled: boolean = tonumber(sub(arguments[2], 16, -1)) ~= 0 |
456 | local initialShouldOverride: boolean = tonumber(sub(arguments[3], 38, -1)) ~= 0 |
457 | local floatX: number = tonumber(sub(arguments[4], 15, -1)) |
458 | local floatY: number = tonumber(sub(arguments[5], 15, -1)) |
459 | local minWidth: number = tonumber(sub(arguments[6], 10, -1)) |
460 | local minHeight: number = tonumber(sub(arguments[7], 11, -1)) |
461 | -- can't read the properties so i have to tostring first :( |
462 | |
463 | return DockWidgetPluginGuiInfo.new(Enum.InitialDockState[dockState], initialEnabled, initialShouldOverride, floatX, floatY, minWidth, minHeight) |
464 | end, |
465 | Enum = function(original: Enum): Enum |
466 | --return original -- enums don't gc |
467 | return false -- doesn't get sent |
468 | end, |
469 | EnumItem = function(original: EnumItem): EnumItem |
470 | return original -- enums don't gc |
471 | end, |
472 | Enums = function(original: Enums): Enums |
473 | --return original -- enums don't gc |
474 | return false -- doesn't get sent |
475 | end, |
476 | Faces = function(original: Faces): Faces |
477 | local args = {} |
478 | if original.Top then |
479 | tableInsert(args, normalId.Top) |
480 | end |
481 | if original.Bottom then |
482 | tableInsert(args, normalId.Bottom) |
483 | end |
484 | if original.Left then |
485 | tableInsert(args, normalId.Left) |
486 | end |
487 | if original.Right then |
488 | tableInsert(args, normalId.Right) |
489 | end |
490 | if original.Back then |
491 | tableInsert(args, normalId.Back) |
492 | end |
493 | if original.Front then |
494 | tableInsert(args, normalId.Front) |
495 | end |
496 | |
497 | return Faces.new(unpack(args)) |
498 | end, |
499 | FloatCurveKey = function(original: FloatCurveKey): FloatCurveKey |
500 | return FloatCurveKey.new(original.Time, original.Value, original.Interpolation) |
501 | end, |
502 | Font = function(original: Font): Font |
503 | local clone: Font = Font.new(original.Family, original.Weight, original.Style) |
504 | clone.Bold = original.Bold |
505 | |
506 | return clone |
507 | end, |
508 | Instance = function(original: Instance): Instance |
509 | return cloneref(original) |
510 | end, |
511 | NumberRange = function(original: NumberRange): NumberRange |
512 | return NumberRange.new(original.Min, original.Max) |
513 | end, |
514 | NumberSequence = function(original: NumberSequence): NumberSequence |
515 | return NumberSequence.new(original.Keypoints) |
516 | end, |
517 | NumberSequenceKeypoint = function(original: NumberSequenceKeypoint): NumberSequenceKeypoint |
518 | return NumberSequenceKeypoint.new(original.Time, original.Value, original.Envelope) |
519 | end, |
520 | OverlapParams = function(original: OverlapParams): OverlapParams |
521 | --[[local clone: OverlapParams = OverlapParams.new() |
522 | clone.CollisionGroup = original.CollisionGroup |
523 | clone.FilterDescendantsInstances = original.FilterDescendantsInstances |
524 | clone.FilterType = original.FilterType |
525 | clone.MaxParts = original.MaxParts |
526 | |
527 | return clone]] |
528 | return false -- doesn't get sent |
529 | end, |
530 | PathWaypoint = function(original: PathWaypoint): PathWaypoint |
531 | return PathWaypoint.new(original.Position, original.Action) |
532 | end, |
533 | PhysicalProperties = function(original: PhysicalProperties): PhysicalProperties |
534 | return PhysicalProperties.new(original.Density, original.Friction, original.Elasticity, original.FrictionWeight, original.ElasticityWeight) |
535 | end, |
536 | Random = function(original: Random): Random |
537 | --return original:Clone() |
538 | return false -- doesn't get sent |
539 | end, |
540 | Ray = function(original: Ray): Ray |
541 | return Ray.new(original.Origin, original.Direction) |
542 | end, |
543 | RaycastParams = function(original: RaycastParams): RaycastParams |
544 | local clone: RaycastParams = RaycastParams.new() |
545 | clone.CollisionGroup = original.CollisionGroup |
546 | clone.FilterDescendantsInstances = original.FilterDescendantsInstances |
547 | clone.FilterType = original.FilterType |
548 | clone.IgnoreWater = original.IgnoreWater |
549 | |
550 | return clone |
551 | end, |
552 | RaycastResult = function(original: RaycastResult): RaycastResult |
553 | local params: RaycastParams = RaycastParams.new() |
554 | params.IgnoreWater = original.Material.Name ~= "Water" |
555 | params.FilterType = Enum.RaycastFilterType.Whitelist |
556 | params.FilterDescendantsInstances = { original.Instance } |
557 | |
558 | local startPos: Vector3 = original.Position+(original.Distance*original.Normal) |
559 | |
560 | return workspace:Raycast(startPos, CFrame.lookAt(startPos, original.Position).LookVector*math.ceil(original.Distance), params) |
561 | end, |
562 | RBXScriptConnection = function(original: RBXScriptConnection): RBXScriptConnection |
563 | return nil -- can't be sent, unsupported, another option is to send the original, but that's detectable |
564 | end, |
565 | RBXScriptSignal = function(original: RBXScriptSignal): RBXScriptSignal |
566 | return nil -- can't be sent, unsupported |
567 | end, |
568 | Rect = function(original: Rect): Rect |
569 | return Rect.new(original.Min, original.Max) |
570 | end, |
571 | Region3 = function(original: Region3): Region3 |
572 | local center = original.CFrame.Position |
573 | |
574 | return Region3.new(center-original.Size/2, center+original.Size/2) |
575 | end, |
576 | Region3int16 = function(original: Region3int16): Region3int16 |
577 | return Region3int16.new(original.Min, original.Max) |
578 | end, |
579 | RotationCurveKey = function(original: RotationCurveKey): RotationCurveKey |
580 | --return RotationCurveKey.new(original.Time, original.Value, original.Interpolation) |
581 | return false -- doesn't get sent |
582 | end, |
583 | TweenInfo = function(original: TweenInfo): TweenInfo |
584 | return TweenInfo.new(original.Time, original.EasingStyle, original.EasingDirection, original.RepeatCount, original.Reverses, original.DelayTime) |
585 | end, |
586 | UDim = function(original: UDim): UDim |
587 | return UDim.new(original.Scale, original.Offset) |
588 | end, |
589 | UDim2 = function(original: UDim2): UDim2 |
590 | -- I've tested it and confirmed that even though they share identical X and Y userdata properties, they do not reference the same thing, and so gcing will not detect this. |
591 | return UDim2.new(original.X, original.Y) |
592 | end, |
593 | userdata = function(original) -- no typechecking for userdatas like this (newproxy) |
594 | return false -- doesn't get sent |
595 | end, |
596 | Vector2 = function(original: Vector2): Vector2 |
597 | return Vector2.new(original.X, original.Y) |
598 | end, |
599 | Vector2int16 = function(original: Vector2int16): Vector2int16 |
600 | return Vector2int16.new(original.X, original.Y) |
601 | end, |
602 | Vector3 = function(original: Vector3): Vector3 |
603 | return Vector3.new(original.X, original.Y, original.Z) |
604 | end, |
605 | Vector3int16 = function(original: Vector3int16): Vector3int16 |
606 | return Vector3int16.new(original.X, original.Y, original.Z) |
607 | end |
608 | } |
609 | |
610 | local remoteUserdataClone = { |
611 | Axes = function(original: Axes): Axes |
612 | local args = {} |
613 | if original.X and not original.Left and not original.Right then |
614 | tableInsert(args, axis.X) |
615 | elseif original.Left then |
616 | tableInsert(args, normalId.Left) |
617 | end |
618 | if original.Right then |
619 | tableInsert(args, normalId.Right) |
620 | end |
621 | |
622 | if original.Y and not original.Top and not original.Bottom then |
623 | tableInsert(args, axis.Y) |
624 | elseif original.Top then |
625 | tableInsert(args, normalId.Top) |
626 | end |
627 | if original.Bottom then |
628 | tableInsert(args, normalId.Bottom) |
629 | end |
630 | |
631 | if original.Z and not original.Front and not original.Back then |
632 | tableInsert(args, axis.Z) |
633 | elseif original.Front then |
634 | tableInsert(args, normalId.Front) |
635 | end |
636 | if original.Back then |
637 | tableInsert(args, normalId.Back) |
638 | end |
639 | |
640 | return Axes.new(unpack(args)) |
641 | end, |
642 | BrickColor = function(original: BrickColor): BrickColor |
643 | return BrickColor.new(original.Number) |
644 | end, |
645 | CatalogSearchParams = function(original: CatalogSearchParams): CatalogSearchParams |
646 | --[[local clone: CatalogSearchParams = CatalogSearchParams.new() |
647 | clone.AssetTypes = original.AssetTypes |
648 | clone.BundleTypes = original.BundleTypes |
649 | clone.CategoryFilter = original.CategoryFilter |
650 | clone.MaxPrice = original.MaxPrice |
651 | clone.MinPrice = original.MinPrice |
652 | clone.SearchKeyword = original.SearchKeyword |
653 | clone.SortType = original.SortType |
654 | |
655 | return clone]] |
656 | return false -- doesn't get sent |
657 | end, |
658 | CFrame = function(original: CFrame): CFrame |
659 | return CFrame.fromMatrix(original.Position, original.XVector, original.YVector, original.ZVector) |
660 | end, |
661 | Color3 = function(original: Color3): Color3 |
662 | return colorRGB(original.R, original.G, original.B) |
663 | end, |
664 | ColorSequence = function(original: ColorSequence): ColorSequence |
665 | return ColorSequence.new(original.Keypoints) |
666 | end, |
667 | ColorSequenceKeypoint = function(original: ColorSequenceKeypoint): ColorSequenceKeypoint |
668 | return ColorSequenceKeypoint.new(original.Time, original.Value) |
669 | end, |
670 | DateTime = function(original: DateTime): DateTime |
671 | return DateTime.fromUnixTimestamp(original.UnixTimestamp) |
672 | end, |
673 | DockWidgetPluginGuiInfo = function(original: DockWidgetPluginGuiInfo): DockWidgetPluginGuiInfo |
674 | --[[local arguments = split(tostring(original), " ") |
675 | local dockState: string = sub(arguments[1], 18, -1) |
676 | local initialEnabled: boolean = tonumber(sub(arguments[2], 16, -1)) ~= 0 |
677 | local initialShouldOverride: boolean = tonumber(sub(arguments[3], 38, -1)) ~= 0 |
678 | local floatX: number = tonumber(sub(arguments[4], 15, -1)) |
679 | local floatY: number = tonumber(sub(arguments[5], 15, -1)) |
680 | local minWidth: number = tonumber(sub(arguments[6], 10, -1)) |
681 | local minHeight: number = tonumber(sub(arguments[7], 11, -1)) |
682 | -- can't read the properties so i have to tostring first :( |
683 | |
684 | return DockWidgetPluginGuiInfo.new(Enum.InitialDockState[dockState], initialEnabled, initialShouldOverride, floatX, floatY, minWidth, minHeight)]] |
685 | return false -- doesn't get sent |
686 | end, |
687 | Enum = function(original: Enum): Enum |
688 | --return original -- enums don't gc |
689 | return false -- doesn't get sent |
690 | end, |
691 | EnumItem = function(original: EnumItem): EnumItem |
692 | return original -- enums don't gc |
693 | end, |
694 | Enums = function(original: Enums): Enums |
695 | --return original -- enums don't gc |
696 | return false -- doesn't get sent |
697 | end, |
698 | Faces = function(original: Faces): Faces |
699 | local args = {} |
700 | if original.Top then |
701 | tableInsert(args, normalId.Top) |
702 | end |
703 | if original.Bottom then |
704 | tableInsert(args, normalId.Bottom) |
705 | end |
706 | if original.Left then |
707 | tableInsert(args, normalId.Left) |
708 | end |
709 | if original.Right then |
710 | tableInsert(args, normalId.Right) |
711 | end |
712 | if original.Back then |
713 | tableInsert(args, normalId.Back) |
714 | end |
715 | if original.Front then |
716 | tableInsert(args, normalId.Front) |
717 | end |
718 | |
719 | return Faces.new(unpack(args)) |
720 | end, |
721 | FloatCurveKey = function(original: FloatCurveKey): FloatCurveKey |
722 | --return FloatCurveKey.new(original.Time, original.Value, original.Interpolation) |
723 | return false -- doesn't get sent |
724 | end, |
725 | Font = function(original: Font): Font |
726 | local clone: Font = Font.new(original.Family, original.Weight, original.Style) |
727 | clone.Bold = original.Bold |
728 | |
729 | return clone |
730 | end, |
731 | Instance = function(original: Instance): Instance |
732 | return cloneref(original) |
733 | end, |
734 | NumberRange = function(original: NumberRange): NumberRange |
735 | return NumberRange.new(original.Min, original.Max) |
736 | end, |
737 | NumberSequence = function(original: NumberSequence): NumberSequence |
738 | return NumberSequence.new(original.Keypoints) |
739 | end, |
740 | NumberSequenceKeypoint = function(original: NumberSequenceKeypoint): NumberSequenceKeypoint |
741 | return NumberSequenceKeypoint.new(original.Time, original.Value, original.Envelope) |
742 | end, |
743 | OverlapParams = function(original: OverlapParams): OverlapParams |
744 | --[[local clone: OverlapParams = OverlapParams.new() |
745 | clone.CollisionGroup = original.CollisionGroup |
746 | clone.FilterDescendantsInstances = original.FilterDescendantsInstances |
747 | clone.FilterType = original.FilterType |
748 | clone.MaxParts = original.MaxParts |
749 | |
750 | return clone]] |
751 | return false -- doesn't get sent |
752 | end, |
753 | PathWaypoint = function(original: PathWaypoint): PathWaypoint |
754 | return PathWaypoint.new(original.Position, original.Action) |
755 | end, |
756 | PhysicalProperties = function(original: PhysicalProperties): PhysicalProperties |
757 | return PhysicalProperties.new(original.Density, original.Friction, original.Elasticity, original.FrictionWeight, original.ElasticityWeight) |
758 | end, |
759 | Random = function(original: Random): Random |
760 | --return original:Clone() |
761 | return false -- doesn't get sent |
762 | end, |
763 | Ray = function(original: Ray): Ray |
764 | return Ray.new(original.Origin, original.Direction) |
765 | end, |
766 | RaycastParams = function(original: RaycastParams): RaycastParams |
767 | --[[local clone: RaycastParams = RaycastParams.new() |
768 | clone.CollisionGroup = original.CollisionGroup |
769 | clone.FilterDescendantsInstances = original.FilterDescendantsInstances |
770 | clone.FilterType = original.FilterType |
771 | clone.IgnoreWater = original.IgnoreWater |
772 | |
773 | return clone]] |
774 | return false -- doesn't get sent |
775 | end, |
776 | RaycastResult = function(original: RaycastResult): RaycastResult |
777 | --[[local params: RaycastParams = RaycastParams.new() |
778 | params.IgnoreWater = original.Material.Name ~= "Water" |
779 | params.FilterType = Enum.RaycastFilterType.Whitelist |
780 | params.FilterDescendantsInstances = { original.Instance } |
781 | |
782 | local startPos: Vector3 = original.Position+(original.Distance*original.Normal) |
783 | |
784 | return workspace:Raycast(startPos, CFrame.lookAt(startPos, original.Position).LookVector*math.ceil(original.Distance), params)]] |
785 | return false -- doesn't get sent |
786 | end, |
787 | RBXScriptConnection = function(original: RBXScriptConnection): RBXScriptConnection |
788 | return false -- doesn't get sent |
789 | end, |
790 | RBXScriptSignal = function(original: RBXScriptSignal): RBXScriptSignal |
791 | return false -- doesn't get sent |
792 | end, |
793 | Rect = function(original: Rect): Rect |
794 | return Rect.new(original.Min, original.Max) |
795 | end, |
796 | Region3 = function(original: Region3): Region3 |
797 | local center = original.CFrame.Position |
798 | |
799 | return Region3.new(center-original.Size/2, center+original.Size/2) |
800 | end, |
801 | Region3int16 = function(original: Region3int16): Region3int16 |
802 | return Region3int16.new(original.Min, original.Max) |
803 | end, |
804 | RotationCurveKey = function(original: RotationCurveKey): RotationCurveKey |
805 | --return RotationCurveKey.new(original.Time, original.Value, original.Interpolation) |
806 | return false -- doesn't get sent |
807 | end, |
808 | TweenInfo = function(original: TweenInfo): TweenInfo |
809 | --return TweenInfo.new(original.Time, original.EasingStyle, original.EasingDirection, original.RepeatCount, original.Reverses, original.DelayTime) |
810 | return false -- doesn't get sent |
811 | end, |
812 | UDim = function(original: UDim): UDim |
813 | return UDim.new(original.Scale, original.Offset) |
814 | end, |
815 | UDim2 = function(original: UDim2): UDim2 |
816 | -- I've tested it and confirmed that even though they share identical X and Y userdata properties, they do not reference the same thing, and so gcing will not detect this. |
817 | return UDim2.new(original.X, original.Y) |
818 | end, |
819 | userdata = function(original) -- no typechecking for userdatas like this (newproxy) |
820 | return false -- doesn't get sent |
821 | end, |
822 | Vector2 = function(original: Vector2): Vector2 |
823 | return Vector2.new(original.X, original.Y) |
824 | end, |
825 | Vector2int16 = function(original: Vector2int16): Vector2int16 |
826 | return Vector2int16.new(original.X, original.Y) |
827 | end, |
828 | Vector3 = function(original: Vector3): Vector3 |
829 | return Vector3.new(original.X, original.Y, original.Z) |
830 | end, |
831 | Vector3int16 = function(original: Vector3int16): Vector3int16 |
832 | return Vector3int16.new(original.X, original.Y, original.Z) |
833 | end |
834 | } |
835 | |
836 | local function cloneUserdata(userdata: any, remoteType: string): any |
837 | local cloneTable = (remoteType == "BindableEvent" or remoteType == "BindableFunction") and bindableUserdataClone or remoteUserdataClone |
838 | local userdataType = typeof(userdata) |
839 | local func = cloneTable[userdataType] |
840 | if not func then -- func was false |
841 | pushError("Unknown Userdata: \"" .. userdataType .. ",\" please report to GameGuy#5286") |
842 | local clone = newproxy(true) |
843 | getmetatable(clone).__tostring = function() |
844 | return userdataType |
845 | end |
846 | return clone -- userdata that I have never seen before |
847 | else |
848 | local clone = func(userdata) |
849 | if clone == nil then |
850 | clone = newproxy(true) |
851 | getmetatable(clone).__tostring = function() |
852 | return userdataType -- be careful here, if I were to put typeof(userdata) in, it would pass userdata as an upvalue, which would cause it to never gc, leading to a detection. |
853 | end |
854 | return clone -- userdatas are reserved for unclonable types because they can never be sent to the server |
855 | elseif not clone then |
856 | return -- userdata isn't sent to the server |
857 | else |
858 | return clone -- good userdata |
859 | end |
860 | end |
861 | end |
862 | |
863 | local function getSpecialKey(index: any): string |
864 | local prefix = specialTypes[typeof(index)] or typeof(index) |
865 | |
866 | local oldMt = getrawmetatable(index) |
867 | local returnStr = "" |
868 | if oldMt then |
869 | local wasReadOnly = isreadonly(oldMt) |
870 | if wasReadOnly then setreadonly(oldMt, false) end |
871 | local oldToString = rawget(oldMt, "__tostring") |
872 | |
873 | rawset(oldMt, "__tostring", nil) |
874 | returnStr = "<" .. prefix .. ">" .. " (" .. tostring(index) .. ")" |
875 | rawset(oldMt, "__tostring", oldToString) |
876 | if wasReadOnly then setreadonly(oldMt, true) end |
877 | else |
878 | returnStr = "<" .. prefix .. ">" .. " (" .. tostring(index) .. ")" |
879 | end |
880 | |
881 | return returnStr |
882 | end |
883 | |
884 | local function cloneData(data: any, callType: string) |
885 | local primType: string = type(data) |
886 | if primType == "userdata" or primType == "vector" then |
887 | if typeof(data) == "Instance" and not IsDescendantOf(data, game) then |
888 | return cloneUserdata(data, callType), true |
889 | end |
890 | |
891 | return cloneUserdata(data, callType) |
892 | elseif primType == "thread" then |
893 | return -- can't be sent |
894 | elseif primType == "function" and (callType == "RemoteEvent" or callType == "RemoteFunction") then |
895 | return -- can't be sent |
896 | else |
897 | return data -- any non cloneables (numbers, strings, etc) |
898 | end |
899 | end |
900 | |
901 | local function createIndex(index: any) -- from my testing, calltype doesn't affect indexes |
902 | local primType = type(index) |
903 | if primType == "userdata" or primType == "vector" or primType == "function" or primType == "table" then |
904 | return getSpecialKey(index) |
905 | else |
906 | return index -- threads and nils are unhandled in this function because neither can be indexed by |
907 | end |
908 | end |
909 | |
910 | -- this function should only be used on deepClone({...}), and only on the first table, where we can be sure that it should be all number indices, this is unsafe to use on any other tables. The first table from deepClone may not be in order (due to nils being weird), so we need a comparison of indices. |
911 | local function getLastIndex(tbl): number |
912 | local final: number = 0 |
913 | for i in tbl do |
914 | if i > final then |
915 | final = i |
916 | end |
917 | end |
918 | |
919 | return final |
920 | end |
921 | |
922 | local function deepClone(myTable, callType: string, stack: number?) -- cyclic check built in |
923 | stack = stack or 0 -- you can offset stack by setting the starting parameter to a number |
924 | local newTable = {} |
925 | local hasTable = false |
926 | local hasNilParentedInstance = false |
927 | local started = false |
928 | local originalDepth = stack |
929 | local consecutiveIndices = getn(myTable) |
930 | local isConsecutive = (consecutiveIndices ~= 0) and (stack ~= -1) -- consecutives don't count when it's the original data (nils break stuff) |
931 | |
932 | if stack == 300 then -- this stack overflow check doesn't really matter as a stack overflow check, it's just here to make sure there are no cyclic tables. While I could just check for cyclics directly, this is faster. |
933 | return false, stack |
934 | end |
935 | for i,v in next, myTable do |
936 | if not isConsecutive or (type(i) == "number" and i <= consecutiveIndices) then |
937 | if not started then started = true; stack += 1 end |
938 | local index = createIndex(i) |
939 | if index then |
940 | local value = nil |
941 | if type(v) == "table" then |
942 | hasTable = true |
943 | local newTab, maxStack, _, subHasNilParentedInstance = deepClone(v, callType, originalDepth+1) |
944 | hasNilParentedInstance = hasNilParentedInstance or subHasNilParentedInstance |
945 | if maxStack > stack then |
946 | stack = maxStack |
947 | end |
948 | |
949 | if newTab then |
950 | value = newTab |
951 | else |
952 | return false, stack -- stack overflow |
953 | end |
954 | else |
955 | local nilParented |
956 | value, nilParented = cloneData(v, callType) |
957 | hasNilParentedInstance = hasNilParentedInstance or nilParented |
958 | end |
959 | newTable[index] = value |
960 | end |
961 | end |
962 | end |
963 | |
964 | return newTable, stack, hasTable, hasNilParentedInstance |
965 | end |
966 | |
967 | local function pushTheme(window: RenderChildBase) |
968 | for i,v in styleOptions do |
969 | window:SetStyle(RenderStyleOption[i], v) |
970 | end |
971 | |
972 | for i,v in colorOptions do |
973 | window:SetColor(RenderColorOption[i], v[1], v[2]) |
974 | end |
975 | end |
976 | |
977 | local function addSpacer(window, amt: number) |
978 | local bufferMain = window:Dummy() |
979 | bufferMain:SetColor(RenderColorOption.Button, black, 0) |
980 | bufferMain:SetColor(RenderColorOption.ButtonActive, black, 0) |
981 | bufferMain:SetColor(RenderColorOption.ButtonHovered, black, 0) |
982 | local buffer = bufferMain:Button() |
983 | buffer.Size = Vector2.new(10, amt) |
984 | return bufferMain |
985 | end |
986 | |
987 | local asciiFilteredCharacters = { |
988 | ["\""] = "\\\"", |
989 | ["\\"] = "\\\\", |
990 | ["\a"] = "\\a", |
991 | ["\b"] = "\\b", |
992 | ["\t"] = "\\t", |
993 | ["\n"] = "\\n", |
994 | ["\v"] = "\\v", |
995 | ["\f"] = "\\f", |
996 | ["\r"] = "\\r" |
997 | } |
998 | |
999 | for Index = 0, 255 do |
1000 | if (Index < 32 or Index > 126) then -- only non printable ascii characters |
1001 | local character = string.char(Index) |
1002 | if not asciiFilteredCharacters[character] then |
1003 | asciiFilteredCharacters[character] = "\\" .. Index |
1004 | end |
1005 | end |
1006 | end |
1007 | |
1008 | local function purifyString(str: string, quotes: boolean, maxLength: number) |
1009 | if type(maxLength) == "number" then |
1010 | str = sub(str, 1, maxLength) |
1011 | end |
1012 | str = gsub(str, "[\"\\\0-\31\127-\255]", asciiFilteredCharacters) |
1013 | if type(maxLength) == "number" then |
1014 | str = sub(str, 1, maxLength) |
1015 | end |
1016 | --[[ |
1017 | This gsub can be broken down into multiple steps. |
1018 | It filters quotations (\") and backslashes "\\" to be replaced, |
1019 | Then it filters characters 0-31, and 127-255, replacing them all with their escape sequences |
1020 | ]] |
1021 | if quotes then |
1022 | return "\"" .. str .. "\"" |
1023 | else |
1024 | return str |
1025 | end |
1026 | end |
1027 | |
1028 | local gameId, workspaceId = getDebugId(game), getDebugId(workspace) |
1029 | |
1030 | local function instanceParentedToNil(instance: Instance) -- too cursed to use (insanely slow in certain games) |
1031 | local instanceId = getDebugId(instance) |
1032 | for _,v in getnilinstances() do |
1033 | if getDebugId(v) == instanceId then |
1034 | return true |
1035 | end |
1036 | end |
1037 | end |
1038 | |
1039 | local function getInstancePath(instance: Instance) -- FORKED FROM HYDROXIDE |
1040 | if not instance then return "NIL INSTANCE" end -- probably is impossible, cant be bothered to confirm |
1041 | local s = tick() |
1042 | setThreadIdentity(8) |
1043 | local id = getDebugId(instance) |
1044 | |
1045 | local name = instance.Name |
1046 | local head = (#name > 0 and '.' .. name) or "['']" |
1047 | |
1048 | if not instance.Parent and id ~= gameId then |
1049 | return "(nil)" .. head .. " --[[ INSTANCE DELETED/PARENTED TO NIL ]]", false |
1050 | --if not instanceParentedToNil(instance) then |
1051 | --return "(nil)" .. head .. " --[[ INSTANCE DELETED FROM GAME ]]", false |
1052 | --else |
1053 | --return "(nil)" .. head .. " --[[ PARENTED TO NIL ]]", false |
1054 | --end |
1055 | end |
1056 | |
1057 | if id == gameId then |
1058 | return "game", true, true |
1059 | elseif id == workspaceId then |
1060 | return "workspace", true, true |
1061 | else |
1062 | local plr = Players:GetPlayerFromCharacter(instance) |
1063 | if plr then |
1064 | if getDebugId(plr) == clientid then |
1065 | return 'game:GetService("Players").LocalPlayer.Character', true, true |
1066 | else |
1067 | if tonumber(sub(plr.Name, 1, 1)) then |
1068 | return 'game:GetService("Players")["'..plr.Name..'"]".Character', true, true |
1069 | else |
1070 | return 'game:GetService("Players").'..plr.Name..'.Character', true, true |
1071 | end |
1072 | end |
1073 | end |
1074 | local _success, result = pcall(game.GetService, game, instance.ClassName) |
1075 | |
1076 | if _success and result then |
1077 | return 'game:GetService("' .. instance.ClassName .. '")', true, true |
1078 | elseif id == clientid then -- cloneref moment |
1079 | return 'game:GetService("Players").LocalPlayer', true, true |
1080 | else |
1081 | local nonAlphaNum = gsub(name, '[%w_]', '') |
1082 | local noPunct = gsub(nonAlphaNum, '[%s%p]', '') |
1083 | |
1084 | if tonumber(sub(name, 1, 1)) or (#nonAlphaNum ~= 0 and #noPunct == 0) then |
1085 | head = '[' .. purifyString(name, true) .. ']' |
1086 | elseif #nonAlphaNum ~= 0 and #noPunct > 0 then |
1087 | head = '[' .. purifyString(name, true) .. ']' |
1088 | end |
1089 | end |
1090 | end |
1091 | |
1092 | return (getInstancePath(instance.Parent) .. head), true |
1093 | end |
1094 | |
1095 | local tableToString; |
1096 | local makeUserdataConstructor = { |
1097 | Axes = function(original: Axes): Axes |
1098 | local constructor: string = "Axes.new(" |
1099 | if original.X and not original.Left and not original.Right then |
1100 | constructor ..= "Enum.Axis.X, " |
1101 | elseif original.Left then |
1102 | constructor ..= "Enum.NormalId.Left, " |
1103 | end |
1104 | if original.Right then |
1105 | constructor ..= "Enum.NormalId.Right, " |
1106 | end |
1107 | |
1108 | if original.Y and not original.Top and not original.Bottom then |
1109 | constructor ..= "Enum.Axis.Y, " |
1110 | elseif original.Top then |
1111 | constructor ..= "Enum.NormalId.Top, " |
1112 | end |
1113 | if original.Bottom then |
1114 | constructor ..= "Enum.NormalId.Bottom, " |
1115 | end |
1116 | |
1117 | if original.Z and not original.Front and not original.Back then |
1118 | constructor ..= "Enum.Axis.Z, " |
1119 | elseif original.Front then |
1120 | constructor ..= "Enum.NormalId.Front, " |
1121 | end |
1122 | if original.Back then |
1123 | constructor ..= "Enum.NormalId.Back, " |
1124 | end |
1125 | |
1126 | return (constructor ~= "Axes.new(" and sub(constructor, 0, -3) or constructor) .. ")" |
1127 | end, |
1128 | BrickColor = function(original: BrickColor): string |
1129 | return "BrickColor.new(\"" .. original.Name .. "\")" |
1130 | end, |
1131 | CatalogSearchParams = function(original: CatalogSearchParams): string |
1132 | return strformat("(function() local clone: CatalogSearchParams = CatalogSearchParams.new(); clone.AssetTypes = %s; clone.BundleTimes = %s; clone.CategoryFilter = %s; clone.MaxPrice = %s; clone.MinPrice = %s; clone.SearchKeyword = %s; clone.SortType = %s; return clone end)()", tostring(original.AssetTypes), tostring(original.BundleTypes), tostring(original.CategoryFilter), original.MaxPrice, original.MinPrice, original.SearchKeyword, tostring(original.SortType)) |
1133 | end, |
1134 | CFrame = function(original: CFrame): string |
1135 | return "CFrame.new(" .. tostring(original) .. ")" |
1136 | end, |
1137 | Color3 = function(original: Color3): string |
1138 | return "Color3.new(" .. tostring(original) .. ")" |
1139 | end, |
1140 | ColorSequence = function(original: ColorSequence): string |
1141 | return "ColorSequence.new(" .. tableToString(original.Keypoints, false) ..")" |
1142 | end, |
1143 | ColorSequenceKeypoint = function(original: ColorSequenceKeypoint): string |
1144 | return "ColorSequenceKeypoint.new(" .. original.Time .. ", Color3.new(" .. tostring(original.Value) .. "))" |
1145 | end, |
1146 | DateTime = function(original: DateTime): string |
1147 | return "DateTime.fromUnixTimestamp(" .. tostring(original.UnixTimestamp) .. ")" |
1148 | end, |
1149 | DockWidgetPluginGuiInfo = function(original: DockWidgetPluginGuiInfo): string |
1150 | local arguments = split(tostring(original), " ") |
1151 | local dockState: string = sub(arguments[1], 18, -1) |
1152 | local initialEnabled: boolean = tonumber(sub(arguments[2], 16, -1)) ~= 0 |
1153 | local initialShouldOverride: boolean = tonumber(sub(arguments[3], 38, -1)) ~= 0 |
1154 | local floatX: number = tonumber(sub(arguments[4], 15, -1)) |
1155 | local floatY: number = tonumber(sub(arguments[5], 15, -1)) |
1156 | local minWidth: number = tonumber(sub(arguments[6], 10, -1)) |
1157 | local minHeight: number = tonumber(sub(arguments[7], 11, -1)) |
1158 | -- can't read the properties so i have to tostring first :( |
1159 | |
1160 | return strformat("DockWidgetPluginGuiInfo.new(%s, %s, %s, %s, %s, %s, %s)", "Enum.InitialDockState." .. dockState, tostring(initialEnabled), tostring(initialShouldOverride), tostring(floatX), tostring(floatY), tostring(minWidth), tostring(minHeight)) |
1161 | end, |
1162 | Enum = function(original: Enum): string |
1163 | return "Enum." .. tostring(original) |
1164 | end, |
1165 | EnumItem = function(original: EnumItem): string |
1166 | return tostring(original) |
1167 | end, |
1168 | Enums = function(original: Enums): string |
1169 | return "Enum" |
1170 | end, |
1171 | Faces = function(original: Faces): string |
1172 | local constructor = "Faces.new(" |
1173 | if original.Top then |
1174 | constructor ..= "Enum.NormalId.Top" |
1175 | end |
1176 | if original.Bottom then |
1177 | constructor ..= "Enum.NormalId.Bottom" |
1178 | end |
1179 | if original.Left then |
1180 | constructor ..= "Enum.NormalId.Left" |
1181 | end |
1182 | if original.Right then |
1183 | constructor ..= "Enum.NormalId.Right" |
1184 | end |
1185 | if original.Back then |
1186 | constructor ..= "Enum.NormalId.Back" |
1187 | end |
1188 | if original.Front then |
1189 | constructor ..= "Enum.NormalId.Front" |
1190 | end |
1191 | |
1192 | return (constructor ~= "Faces.new(" and sub(constructor, 0, -3) or constructor) .. ")" |
1193 | end, |
1194 | FloatCurveKey = function(original: FloatCurveKey): string |
1195 | return "FloatCurveKey.new(" .. tostring(original.Time) .. ", " .. tostring(original.Value) .. ", " .. tostring(original.Interpolation) .. ")" |
1196 | end, |
1197 | Font = function(original: Font): string |
1198 | return strformat("(function() local clone: Font = Font.new(%s, %s, %s); clone.Bold = %s; return clone end)()", '"' .. original.Family .. '"', tostring(original.Weight), tostring(original.Style), tostring(original.Bold)) |
1199 | end, |
1200 | Instance = function(original: Instance): string |
1201 | return getInstancePath(original) |
1202 | end, |
1203 | NumberRange = function(original: NumberRange): string |
1204 | return "NumberRange.new(" .. tostring(original.Min) .. ", " .. tostring(original.Max) .. ")" |
1205 | end, |
1206 | NumberSequence = function(original: NumberSequence): string |
1207 | return "NumberSequence.new(" .. tableToString(original.Keypoints, false) .. ")" |
1208 | end, |
1209 | NumberSequenceKeypoint = function(original: NumberSequenceKeypoint): string |
1210 | return "NumberSequenceKeypoint.new(" .. tostring(original.Time) .. ", " .. tostring(original.Value) .. ", " .. tostring(original.Envelope) .. ")" |
1211 | end, |
1212 | OverlapParams = function(original: OverlapParams): OverlapParams |
1213 | return strformat("(function(): OverlapParams local clone: OverlapParams = OverlapParams.new(); clone.CollisionGroup = %s; clone.FilterDescendantInstances = %s; clone.FilterType = %s; clone.MaxParts = %s; return clone end)()", original.CollisionGroup, tableToString(original.FilterDescendantsInstances, false, Settings.InstanceTrackerMode), tostring(original.FilterType), tostring(original.MaxParts)) |
1214 | end, |
1215 | PathWaypoint = function(original: PathWaypoint): string |
1216 | return "PathWaypoint.new(Vector3.new(" .. tostring(original.Position) .. "), " .. tostring(original.Action) .. ")" |
1217 | end, |
1218 | PhysicalProperties = function(original: PhysicalProperties): string |
1219 | return "PhysicalProperties.new(" .. tostring(original) .. ")" |
1220 | end, |
1221 | Random = function(original: Random): string |
1222 | return "Random.new()" -- detectable cause of seed change |
1223 | end, |
1224 | Ray = function(original: Ray): string |
1225 | return "Ray.new(Vector3.new(" .. tostring(original.Origin) .. "), Vector3.new(" .. tostring(original.Direction) .. "))" |
1226 | end, |
1227 | RaycastParams = function(original: RaycastParams): string |
1228 | return strformat("(function(): RaycastParams local clone: RaycastParams = RaycastParams.new(); clone.CollisionGroup = %s; clone.FilterDescendantsInstances = %s; clone.FilterType = %s; clone.FilterWater = %s; return clone end)()", original.CollisionGroup, tableToString(original.FilterDescendantsInstances, false, Settings.InstanceTrackerMode), tostring(original.FilterType), tostring(original.IgnoreWater)) |
1229 | end, |
1230 | RaycastResult = function(original: RaycastResult): string |
1231 | return strformat("(function(): RaycastParams local params: RaycastParams = RaycastParams.new(); params.IgnoreWater = %s; params.FilterType = %s; params.FilterDescendantsInstances = %s; local startPos: Vector3 = %s; return workspace:Raycast(startPos, CFrame.lookAt(startPos, %s).LookVector*math.ceil(%s), params) end)()", tostring(original.Material.Name ~= "Water"), tostring(Enum.RaycastFilterType.Whitelist), tableToString(original.Instance, false, Settings.InstanceTrackerMode), "Vector3.new(" .. original.Position+(original.Distance*original.Normal) .. ")", "Vector3.new(" .. original.Position .. ")", "Vector3.new(" .. original.Distance .. ")") |
1232 | end, |
1233 | RBXScriptConnection = function(original: RBXScriptConnection): string |
1234 | return "nil --[[ RBXScriptConnection is Unsupported ]]" |
1235 | end, |
1236 | RBXScriptSignal = function(original: RBXScriptSignal): string |
1237 | return "nil --[[ RBXScriptSignal is Unsupported ]]" |
1238 | end, |
1239 | Rect = function(original: Rect): string |
1240 | return "Rect.new(Vector2.new(" .. tostring(original.Min) .. "), Vector2.new(" .. tostring(original.Max) .. "))" |
1241 | end, |
1242 | Region3 = function(original: Region3): string |
1243 | local center = original.CFrame.Position |
1244 | |
1245 | return "Region3.new(Vector3.new(" .. tostring(center-original.Size/2) .. "), Vector3.new(" .. tostring(center+original.Size/2) .. "))" |
1246 | end, |
1247 | Region3int16 = function(original: Region3int16): string |
1248 | return "Region3int16.new(Vector3int16.new(" .. tostring(original.Min) .. "), Vector3int16.new(" .. tostring(original.Max) .. "))" |
1249 | end, |
1250 | RotationCurveKey = function(original: RotationCurveKey): RotationCurveKey |
1251 | return "RotationCurveKey.new(" .. tostring(original.Time) .. ", CFrame.new(" .. tostring(original.Value) .. "), " .. tostring(original.Interpolation) .. ")" |
1252 | end, |
1253 | TweenInfo = function(original: TweenInfo): string |
1254 | return "TweenInfo.new(" .. tostring(original.Time) .. ", " .. tostring(original.EasingStyle) .. ", " .. tostring(original.EasingDirection) .. ", " .. tostring(original.RepeatCount) .. ", " .. tostring(original.Reverses) .. ", " .. tostring(original.DelayTime) .. ")" |
1255 | end, |
1256 | UDim = function(original: UDim): string |
1257 | return "UDim.new(" .. tostring(original) .. ")" |
1258 | end, |
1259 | UDim2 = function(original: UDim2): string |
1260 | return "UDim2.new(" .. tostring(original) .. ")" |
1261 | end, |
1262 | userdata = function(original): string -- no typechecking for userdatas like this (newproxy) |
1263 | return "nil --[[ " .. tostring(original) .. " is Unsupported ]]" -- newproxies can never be sent, and as such are reserved by the remotespy to be used when a type that could not be deepCloned was sent. The tostring metamethod should've been modified to refelct the original type. |
1264 | end, |
1265 | Vector2 = function(original: Vector2): string |
1266 | return "Vector2.new(" .. tostring(original) .. ")" |
1267 | end, |
1268 | Vector2int16 = function(original: Vector2int16): string |
1269 | return "Vector2int16.new(" .. tostring(original) .. ")" |
1270 | end, |
1271 | Vector3 = function(original: Vector3): string |
1272 | return "Vector3.new(" .. tostring(original) .. ")" |
1273 | end, |
1274 | Vector3int16 = function(original: Vector3int16): string |
1275 | return "Vector3int16.new(" .. tostring(original) .. ")" |
1276 | end |
1277 | } |
1278 | |
1279 | local function getUserdataConstructor(userdata: any): string |
1280 | local userdataType = typeof(userdata) |
1281 | local constructorCreator = makeUserdataConstructor[userdataType] |
1282 | |
1283 | if constructorCreator then |
1284 | return constructorCreator(userdata) |
1285 | else |
1286 | return "nil --[[ " .. userdataType .. " is Unsupported ]]" |
1287 | end |
1288 | end |
1289 | |
1290 | -- localized elsewhere |
1291 | |
1292 | tableToString = function(data: any, format: boolean, debugMode: boolean, root: any, indents: number) -- FORKED FROM HYDROXIDE |
1293 | local dataType = type(data) |
1294 | |
1295 | format = format == nil and true or format |
1296 | |
1297 | if dataType == "userdata" or dataType == "vector" then |
1298 | if typeof(data) == "Instance" then |
1299 | local str, parented, bypasses = getInstancePath(data) |
1300 | if (debugMode == 3 or (debugMode == 2 and parented)) and not bypasses then |
1301 | return ("GetInstanceFromDebugId(\"" .. getDebugId(data) .."\")") .. (" --[[ Original Path: " .. str..(parented and " ]]" or "")) |
1302 | else |
1303 | return str |
1304 | end |
1305 | else |
1306 | return getUserdataConstructor(data) |
1307 | end |
1308 | elseif dataType == "string" then |
1309 | local success, result = pcall(purifyString, data, true) |
1310 | return (success and result) or tostring(data) |
1311 | elseif dataType == "table" then |
1312 | indents = indents or 1 |
1313 | root = root or data |
1314 | |
1315 | local head = format and '{\n' or '{ ' |
1316 | local indent = rep('\t', indents) |
1317 | local consecutiveIndices = (#data ~= 0) |
1318 | local elementCount = 0 |
1319 | -- moved checkCyclic check to hook |
1320 | if format then |
1321 | if consecutiveIndices then |
1322 | for i,v in data do |
1323 | elementCount += 1 |
1324 | if type(i) ~= "number" then continue end |
1325 | |
1326 | if i ~= elementCount then |
1327 | for _ = 1, (i-elementCount) do |
1328 | head ..= (indent .. "nil,\n") |
1329 | end |
1330 | elementCount = i |
1331 | end |
1332 | head ..= strformat("%s%s,\n", indent, tableToString(v, true, debugMode, root, indents + 1)) |
1333 | end |
1334 | else |
1335 | for i,v in data do |
1336 | head ..= strformat("%s[%s] = %s,\n", indent, tableToString(i, true, debugMode, root, indents + 1), tableToString(v, true, debugMode, root, indents + 1)) |
1337 | end |
1338 | end |
1339 | else |
1340 | if consecutiveIndices then |
1341 | for i,v in data do |
1342 | elementCount += 1 |
1343 | if type(i) ~= "number" then continue end |
1344 | |
1345 | if i ~= elementCount then |
1346 | for _ = 1, (i-elementCount) do |
1347 | head ..= "nil, " |
1348 | end |
1349 | elementCount = i |
1350 | end |
1351 | head ..= (tableToString(v, false, debugMode, root, indents + 1) .. ", ") |
1352 | end |
1353 | else |
1354 | for i,v in data do |
1355 | head ..= strformat("[%s] = %s, ", tableToString(i, false, debugMode, root, indents + 1), tableToString(v, false, debugMode, root, indents + 1)) |
1356 | end |
1357 | end |
1358 | end |
1359 | |
1360 | if format then |
1361 | return #head > 2 and strformat("%s\n%s", sub(head, 1, -3), rep('\t', indents - 1) .. '}') or "{}" |
1362 | else |
1363 | return #head > 2 and (sub(head, 1, -3) .. ' }') or "{}" |
1364 | end |
1365 | elseif dataType == "function" then -- functions are only receivable through bindables, not remotes |
1366 | return 'nil --[[ ' .. tostring(data) .. " ]]" -- just in case |
1367 | elseif dataType == "number" then |
1368 | local dataStr = tostring(data) |
1369 | if not match(dataStr, "%d") then |
1370 | if data == inf then |
1371 | return "(1/0)" |
1372 | elseif data == neginf then |
1373 | return "(-1/0)" |
1374 | elseif dataStr == "nan" then |
1375 | return "(0/0)" |
1376 | else |
1377 | return ("tonumber(\"" .. dataStr .. "\")") |
1378 | end |
1379 | else |
1380 | return dataStr |
1381 | end |
1382 | else |
1383 | return tostring(data) |
1384 | end |
1385 | end |
1386 | |
1387 | local types = { |
1388 | ["string"] = { colorHSV(29/360, 0.8, 1), function(obj, maxLength) |
1389 | return purifyString(obj, true, maxLength) |
1390 | end }, |
1391 | ["number"] = { colorHSV(120/360, 0.8, 1), function(obj) |
1392 | return tostring(obj) |
1393 | end }, |
1394 | ["boolean"] = { colorHSV(211/360, 0.8, 1), function(obj) |
1395 | return tostring(obj) |
1396 | end }, |
1397 | ["table"] = { white, function(obj) |
1398 | return tostring(obj) |
1399 | end }, |
1400 | |
1401 | --[[["userdata"] = { colorHSV(258/360, 0.8, 1), function(obj) |
1402 | return "Unprocessed Userdata: " .. typeof(obj) .. ": " .. tostring(obj) |
1403 | end }, |
1404 | ["Instance"] = { colorHSV(57/360, 0.8, 1), function(obj) |
1405 | return tostring(obj) |
1406 | end },]] |
1407 | |
1408 | ["function"] = { white, function(obj) |
1409 | -- functions can't be received by Remotes, but can be received by Bindables |
1410 | return tostring(obj) |
1411 | end }, |
1412 | ["nil"] = { colorHSV(360/360, 0.8, 1), function(obj) |
1413 | return "nil" |
1414 | end } |
1415 | } |
1416 | |
1417 | local function getArgString(arg: any, maxLength: number) |
1418 | local t = type(arg) |
1419 | |
1420 | if types[t] and t ~= "userdata" then |
1421 | local st = types[t] |
1422 | return st[2](arg, maxLength), st[1] |
1423 | elseif t == "userdata" or t == "vector" then |
1424 | local st = getUserdataConstructor(arg) |
1425 | return st, (typeof(arg) == "Instance" and colorHSV(57/360, 0.8, 1)) or colorHSV(314/360, 0.8, 1) |
1426 | else |
1427 | return ("Unprocessed Lua Type: " .. tostring(t)), colorRGB(1, 1, 1) |
1428 | end |
1429 | end |
1430 | |
1431 | local spaces = " " |
1432 | local spaces2 = " " -- 8 spaces |
1433 | |
1434 | local idxs = { |
1435 | FireServer = 1, |
1436 | InvokeServer = 2, |
1437 | Fire = 3, |
1438 | Invoke = 4, |
1439 | |
1440 | fireServer = 1, |
1441 | invokeServer = 2, |
1442 | fire = 3, |
1443 | invoke = 4, |
1444 | |
1445 | OnClientEvent = 5, |
1446 | OnClientInvoke = 6, |
1447 | Event = 7, |
1448 | OnInvoke = 8, |
1449 | } |
1450 | |
1451 | local spyFunctions = { |
1452 | { |
1453 | Name = "FireServer", |
1454 | Object = "RemoteEvent", |
1455 | Type = "Call", |
1456 | Method = "FireServer", |
1457 | DeprecatedMethod = "fireServer", |
1458 | Enabled = Settings.FireServer, |
1459 | Icon = "\xee\x80\x80 ", |
1460 | Indent = 0 |
1461 | }, |
1462 | { |
1463 | Name = "InvokeServer", |
1464 | Object = "RemoteFunction", |
1465 | Type = "Call", |
1466 | ReturnsValue = true, |
1467 | Method = "InvokeServer", |
1468 | DeprecatedMethod = "invokeServer", |
1469 | Enabled = Settings.InvokeServer, |
1470 | Icon = "\xee\x80\x81 ", |
1471 | Indent = 153 |
1472 | }, |
1473 | { |
1474 | Name = "Fire", |
1475 | Object = "BindableEvent", |
1476 | Type = "Call", |
1477 | FiresLocally = true, |
1478 | Method = "Fire", |
1479 | DeprecatedMethod = "fire", |
1480 | Enabled = Settings.Fire, |
1481 | Icon = "\xee\x80\x82 ", |
1482 | Indent = 319 |
1483 | }, |
1484 | { |
1485 | Name = "Invoke", |
1486 | Object = "BindableFunction", |
1487 | Type = "Call", |
1488 | ReturnsValue = true, |
1489 | FiresLocally = true, |
1490 | Method = "Invoke", |
1491 | DeprecatedMethod = "invoke", |
1492 | Enabled = Settings.Invoke, |
1493 | Icon = "\xee\x80\x83 ", |
1494 | Indent = 434 |
1495 | }, |
1496 | |
1497 | { |
1498 | Name = "OnClientEvent", |
1499 | Object = "RemoteEvent", |
1500 | HasNoCaller = true, |
1501 | Type = "Connection", |
1502 | Connection = "OnClientEvent", |
1503 | DeprecatedConnection = "onClientEvent", |
1504 | Enabled = Settings.OnClientEvent, |
1505 | Icon = "\xee\x80\x84 ", |
1506 | Indent = 0 |
1507 | }, |
1508 | { |
1509 | Name = "OnClientInvoke", |
1510 | Object = "RemoteFunction", |
1511 | ReturnsValue = true, |
1512 | HasNoCaller = true, |
1513 | Type = "Callback", |
1514 | Callback = "OnClientInvoke", |
1515 | DeprecatedCallback = "onClientInvoke", |
1516 | Enabled = Settings.OnClientInvoke, |
1517 | Icon = "\xee\x80\x85 ", |
1518 | Indent = 153 |
1519 | }, |
1520 | { |
1521 | Name = "OnEvent", |
1522 | Object = "BindableEvent", |
1523 | Type = "Connection", |
1524 | Connection = "Event", -- not OnEvent cause roblox naming is wacky |
1525 | DeprecatedConnection = "event", |
1526 | Enabled = Settings.OnEvent, |
1527 | Icon = "\xee\x80\x86 ", |
1528 | Indent = 319 |
1529 | }, |
1530 | { |
1531 | Name = "OnInvoke", |
1532 | Object = "BindableFunction", |
1533 | ReturnsValue = true, |
1534 | Type = "Callback", |
1535 | Callback = "OnInvoke", |
1536 | DeprecatedCallback = "onInvoke", |
1537 | Enabled = Settings.OnInvoke, |
1538 | Icon = "\xee\x80\x87 ", |
1539 | Indent = 434 |
1540 | } |
1541 | } |
1542 | |
1543 | local repeatCallSteps = { |
1544 | 1, |
1545 | 10, |
1546 | 100, |
1547 | 1000 |
1548 | } |
1549 | |
1550 | local function repeatStringWithIndex(prefix: string, suffix: string, count: number) |
1551 | local retVal = "" |
1552 | for i = 1, count do |
1553 | retVal ..= (prefix .. tostring(i) .. suffix) |
1554 | end |
1555 | |
1556 | return retVal |
1557 | end |
1558 | |
1559 | local function genSendPseudo(rem: Instance, call, spyFunc) |
1560 | local watermark = Settings.PseudocodeWatermark and watermarkString or "" |
1561 | |
1562 | local debugMode = Settings.InstanceTrackerMode |
1563 | |
1564 | if debugMode == 3 or (debugMode == 2 and call.HasInstance) then |
1565 | watermark ..= (--[[Settings.OptimizedInstanceTracker and optimizedInstanceTrackerFunctionString or]] instanceTrackerFunctionString) .. "\n\n" |
1566 | else |
1567 | watermark ..= "\n" |
1568 | end |
1569 | |
1570 | local pathStr, parented = getInstancePath(rem) |
1571 | local remPath = ((debugMode == 3 or ((debugMode == 2) and not parented)) and ("GetInstanceFromDebugId(\"" .. getDebugId(rem) .."\")" .. " -- Original Path: " .. pathStr)) or pathStr |
1572 | |
1573 | if call.NonNilArgCount == 0 and call.NilCount == 0 then |
1574 | if spyFunc.Type == "Call" then |
1575 | return watermark .. (Settings.PseudocodeInlineRemote and ("local remote" .. (Settings.PseudocodeLuaUTypes and (": " .. spyFunc.Object) or "").." = " .. remPath .. "\n\n" .. (spyFunc.ReturnsValue and ("local returnValue = ") or "") .. "remote:") or (remPath .. ":")) .. spyFunc.Method .."()" |
1576 | elseif spyFunc.Type == "Connection" then |
1577 | return watermark .. (Settings.PseudocodeInlineRemote and ("local remote" .. (Settings.PseudocodeLuaUTypes and (": " .. spyFunc.Object) or "").." = " .. remPath .. "\n\nfiresignal(remote.") or ("firesignal(" .. remPath ".")) .. spyFunc.Connection ..")" |
1578 | elseif spyFunc.Type == "Callback" then |
1579 | return watermark .. (Settings.PseudocodeInlineRemote and ("local remote" .. (Settings.PseudocodeLuaUTypes and (": " .. spyFunc.Object) or "").." = " .. remPath .. "\n\ngetcallbackmember(remote, \"") or ("getcallbackmember(" .. remPath .. ", \"")) .. spyFunc.Callback .."\")()" |
1580 | end |
1581 | else |
1582 | local argCalls = {} |
1583 | local argCallCount = {} |
1584 | |
1585 | local pseudocode = "" |
1586 | local addedArg = false |
1587 | |
1588 | for i = 1, call.NonNilArgCount do |
1589 | local arg = call.Args[i] |
1590 | local primTyp = type(arg) |
1591 | local tempTyp = typeof(arg) |
1592 | local typ = (gsub(tempTyp, "^%u", lower)) |
1593 | |
1594 | argCallCount[typ] = argCallCount[typ] and argCallCount[typ] + 1 or 1 |
1595 | |
1596 | local varName = typ .. tostring(argCallCount[typ]) |
1597 | |
1598 | if primTyp == "nil" then |
1599 | tableInsert(argCalls, { typ, primTyp, "", "" }) |
1600 | continue |
1601 | end |
1602 | |
1603 | local varPrefix = "" |
1604 | if primTyp ~= "function" and primTyp ~= "table" and Settings.PseudocodeLuaUTypes then |
1605 | varPrefix = "local " .. varName .. ": ".. tempTyp .." = " |
1606 | else |
1607 | varPrefix = "local " .. varName .." = " |
1608 | end |
1609 | local varConstructor = "" |
1610 | |
1611 | if primTyp == "userdata" or primTyp == "vector" then -- roblox should just get rid of vector already |
1612 | if typeof(arg) == "Instance" then |
1613 | local str, parented, bypasses = getInstancePath(arg) |
1614 | if (debugMode == 3 or (debugMode == 2 and not parented)) and not bypasses then |
1615 | varConstructor = ("GetInstanceFromDebugId(\"" .. getDebugId(arg) .."\")") .. (" -- Original Path: " .. str) |
1616 | else |
1617 | varConstructor = str |
1618 | end |
1619 | else |
1620 | varConstructor = getUserdataConstructor(arg) |
1621 | end |
1622 | elseif primTyp == "table" then |
1623 | varConstructor = tableToString(arg, Settings.PseudocodeFormatTables, debugMode) |
1624 | elseif primTyp == "string" then |
1625 | varConstructor = purifyString(arg, true) |
1626 | elseif primTyp == "function" then |
1627 | varConstructor = 'nil -- [[ ' .. tostring(arg) .. ' ]]' -- functions can be sent by bindables, but I can't exactly generate pseudocode for them |
1628 | elseif primTyp == "number" then |
1629 | local dataStr = tostring(arg) |
1630 | if not match(dataStr, "%d") then |
1631 | if arg == inf then |
1632 | varConstructor = "(1/0)" |
1633 | elseif arg == neginf then |
1634 | varConstructor = "(-1/0)" |
1635 | elseif dataStr == "nan" then |
1636 | varConstructor = "(0/0)" |
1637 | else |
1638 | varConstructor = ("tonumber(\"" .. dataStr .. "\")") |
1639 | end |
1640 | else |
1641 | varConstructor = dataStr |
1642 | end |
1643 | else |
1644 | varConstructor = tostring(arg) |
1645 | end |
1646 | |
1647 | tableInsert(argCalls, { typ, primTyp, varConstructor, varName }) |
1648 | |
1649 | if Settings.PseudocodeInliningMode == 3 and primTyp == "table" then |
1650 | pseudocode ..= (varPrefix .. (varConstructor .. "\n")) |
1651 | addedArg = true |
1652 | elseif Settings.PseudocodeInliningMode == 2 and (primTyp == "table" or primTyp == "userdata") then |
1653 | pseudocode ..= (varPrefix .. (varConstructor .. "\n")) |
1654 | addedArg = true |
1655 | elseif Settings.PseudocodeInliningMode == 1 then |
1656 | pseudocode ..= (varPrefix .. (varConstructor .. "\n")) |
1657 | addedArg = true |
1658 | end |
1659 | end |
1660 | |
1661 | if Settings.PseudocodeInlineHiddenNils then |
1662 | for i = 1, call.NilCount do |
1663 | pseudocode ..= "local hiddenNil" .. tostring(i) .. " = nil -- games can detect if this is missing, but likely won't.\n" |
1664 | addedArg = true |
1665 | end |
1666 | end |
1667 | if spyFunc.Type == "Call" then |
1668 | pseudocode ..= Settings.PseudocodeInlineRemote and ((addedArg and "\n" or "") .. "local remote" .. (Settings.PseudocodeLuaUTypes and (": " .. spyFunc.Object) or "").." = " .. remPath .. "\n" .. (spyFunc.ReturnsValue and "local returnValue = " or "") .. "remote:" .. spyFunc.Method .. "(") or (remPath .. ":" .. spyFunc.Method .. "(") |
1669 | elseif spyFunc.Type == "Connection" then |
1670 | pseudocode ..= Settings.PseudocodeInlineRemote and ((addedArg and "\n" or "") .. "local remote" .. (Settings.PseudocodeLuaUTypes and (": " .. spyFunc.Object) or "").." = " .. remPath .. "\n" .. (spyFunc.ReturnsValue--[[yes i know this is redundant]] and "local returnValue = " or "") .. "firesignal(remote." .. spyFunc.Connection .. ", ") or ("firesignal(" .. remPath .. "." .. spyFunc.Connection .. ", ") |
1671 | elseif spyFunc.Type == "Callback" then |
1672 | pseudocode ..= Settings.PseudocodeInlineRemote and ((addedArg and "\n" or "") .. "local remote" .. (Settings.PseudocodeLuaUTypes and (": " .. spyFunc.Object) or "").." = " .. remPath .. "\n" .. (spyFunc.ReturnsValue and "local returnValue = " or "") .. "getcallbackmember(remote, \"" .. spyFunc.Callback .. "\")(") or ("getcallbackmember(" .. remPath .. ", \"" .. spyFunc.Callback .. "\")(") |
1673 | end |
1674 | |
1675 | if Settings.PseudocodeInliningMode == 4 then |
1676 | for _,v in argCalls do |
1677 | if v[1] == "nil" then |
1678 | pseudocode ..= "nil, " |
1679 | else |
1680 | pseudocode ..= (v[3] .. ", ") |
1681 | end |
1682 | end |
1683 | elseif Settings.PseudocodeInliningMode == 3 then |
1684 | for _,v in argCalls do |
1685 | if v[1] == "nil" then |
1686 | pseudocode ..= "nil, " |
1687 | elseif (v[2] == "table") then |
1688 | pseudocode ..= ( v[4] .. ", " ) |
1689 | else |
1690 | pseudocode ..= ( v[3] .. ", " ) |
1691 | end |
1692 | end |
1693 | elseif Settings.PseudocodeInliningMode == 2 then |
1694 | for _,v in argCalls do |
1695 | if v[1] == "nil" then |
1696 | pseudocode ..= "nil, " |
1697 | elseif (v[2] == "table" or v[2] == "userdata") then |
1698 | pseudocode ..= ( v[4] .. ", " ) |
1699 | else |
1700 | pseudocode ..= ( v[3] .. ", " ) |
1701 | end |
1702 | end |
1703 | else |
1704 | for _,v in argCalls do |
1705 | if v[1] == "nil" then |
1706 | pseudocode ..= "nil, " |
1707 | else |
1708 | pseudocode ..= ( v[4] .. ", " ) |
1709 | end |
1710 | end |
1711 | end |
1712 | |
1713 | if Settings.PseudocodeInlineHiddenNils then |
1714 | for i = 1, call.NilCount do |
1715 | pseudocode ..= ("hiddenNil" .. tostring(i) .. ", ") |
1716 | end |
1717 | else |
1718 | for _ = 1, call.NilCount do |
1719 | pseudocode ..= "nil, " |
1720 | end |
1721 | end |
1722 | |
1723 | return watermark .. (sub(pseudocode, -2, -2) == "," and sub(pseudocode, 1, -3) or pseudocode) .. ")" -- sub gets rid of the last ", " |
1724 | end |
1725 | end |
1726 | |
1727 | local function genReturnValuePseudo(returnTable, spyFunc) |
1728 | local watermark = Settings.PseudocodeWatermark and watermarkString .. "\n" or "" |
1729 | |
1730 | if #returnTable.Args == 0 and returnTable.NilCount == 0 then |
1731 | return watermark .. "return" |
1732 | else |
1733 | local argCalls = {} |
1734 | local argCallCount = {} |
1735 | |
1736 | local pseudocode = "" |
1737 | local addedArg = false |
1738 | |
1739 | for i = 1, #returnTable.Args do |
1740 | local arg = returnTable.Args[i] |
1741 | local primTyp = type(arg) |
1742 | local tempTyp = typeof(arg) |
1743 | local typ = (gsub(tempTyp, "^%u", lower)) |
1744 | |
1745 | argCallCount[typ] = argCallCount[typ] and argCallCount[typ] + 1 or 1 |
1746 | |
1747 | local varName = typ .. tostring(argCallCount[typ]) |
1748 | |
1749 | if primTyp == "nil" then |
1750 | continue |
1751 | end |
1752 | |
1753 | local varPrefix = "" |
1754 | if primTyp ~= "function" and Settings.PseudocodeLuaUTypes then |
1755 | varPrefix = "local " .. varName .. ": ".. tempTyp .." = " |
1756 | else |
1757 | varPrefix = "local " .. varName .." = " |
1758 | end |
1759 | local varConstructor = "" |
1760 | |
1761 | if primTyp == "userdata" or primTyp == "vector" then -- roblox should just get rid of vector already |
1762 | varConstructor = getUserdataConstructor(arg) |
1763 | elseif primTyp == "table" then |
1764 | varConstructor = tableToString(arg, Settings.PseudocodeFormatTables, 1) |
1765 | elseif primTyp == "string" then |
1766 | varConstructor = purifyString(arg, true) |
1767 | elseif primTyp == "function" then |
1768 | varConstructor = 'nil --[[ ' .. tostring(arg) .. ' ]]' |
1769 | elseif primTyp == "number" then |
1770 | local dataStr = tostring(arg) |
1771 | if not match(tostring(arg), "%d") then |
1772 | if arg == inf then |
1773 | varConstructor = "(1/0)" |
1774 | elseif arg == neginf then |
1775 | varConstructor = "(-1/0)" |
1776 | elseif dataStr == "nan" then |
1777 | varConstructor = "(0/0)" |
1778 | else |
1779 | varConstructor = ("tonumber(\"" .. dataStr .. "\")") |
1780 | end |
1781 | else |
1782 | varConstructor = dataStr |
1783 | end |
1784 | else |
1785 | varConstructor = tostring(arg) |
1786 | end |
1787 | |
1788 | tableInsert(argCalls, { typ, primTyp, varConstructor, varName }) |
1789 | |
1790 | if Settings.PseudocodeInliningMode == 3 and primTyp == "table" then |
1791 | pseudocode ..= (varPrefix .. (varConstructor .. "\n")) |
1792 | addedArg = true |
1793 | elseif Settings.PseudocodeInliningMode == 2 and (primTyp == "table" or primTyp == "userdata") then |
1794 | pseudocode ..= (varPrefix .. (varConstructor .. "\n")) |
1795 | addedArg = true |
1796 | elseif Settings.PseudocodeInliningMode == 1 then |
1797 | pseudocode ..= (varPrefix .. (varConstructor .. "\n")) |
1798 | addedArg = true |
1799 | end |
1800 | end |
1801 | |
1802 | if Settings.PseudocodeInlineHiddenNils then |
1803 | for i = 1, returnTable.NilCount do |
1804 | pseudocode ..= "local hiddenNil" .. tostring(i) .. " = nil -- games can detect if this is missing, but likely won't.\n" |
1805 | addedArg = true |
1806 | end |
1807 | end |
1808 | pseudocode ..= (addedArg and "\n" or "") .. "return " |
1809 | |
1810 | if Settings.PseudocodeInliningMode == 4 then |
1811 | for _,v in argCalls do |
1812 | if v[1] == "nil" then |
1813 | pseudocode ..= "nil, " |
1814 | else |
1815 | pseudocode ..= (v[3] .. ", ") |
1816 | end |
1817 | end |
1818 | elseif Settings.PseudocodeInliningMode == 3 then |
1819 | for _,v in argCalls do |
1820 | if v[1] == "nil" then |
1821 | pseudocode ..= "nil, " |
1822 | elseif (v[2] == "table") then |
1823 | pseudocode ..= ( v[4] .. ", " ) |
1824 | else |
1825 | pseudocode ..= ( v[3] .. ", " ) |
1826 | end |
1827 | end |
1828 | elseif Settings.PseudocodeInliningMode == 2 then |
1829 | for _,v in argCalls do |
1830 | if v[1] == "nil" then |
1831 | pseudocode ..= "nil, " |
1832 | elseif (v[2] == "table" or v[2] == "userdata") then |
1833 | pseudocode ..= ( v[4] .. ", " ) |
1834 | else |
1835 | pseudocode ..= ( v[3] .. ", " ) |
1836 | end |
1837 | end |
1838 | else |
1839 | for _,v in argCalls do |
1840 | if v[1] == "nil" then |
1841 | pseudocode ..= "nil, " |
1842 | else |
1843 | pseudocode ..= ( v[4] .. ", " ) |
1844 | end |
1845 | end |
1846 | end |
1847 | |
1848 | if Settings.PseudocodeInlineHiddenNils then |
1849 | for i = 1, returnTable.NilCount do |
1850 | pseudocode ..= ("hiddenNil" .. tostring(i) .. ", ") |
1851 | end |
1852 | else |
1853 | for _ = 1, returnTable.NilCount do |
1854 | pseudocode ..= "nil, " |
1855 | end |
1856 | end |
1857 | |
1858 | return watermark .. sub(pseudocode, 1, -3) -- gets rid of last ", " |
1859 | end |
1860 | end |
1861 | |
1862 | local function genRecvPseudo(rem: Instance, call, spyFunc, watermark: string) |
1863 | local watermark = watermark and watermarkString or "" |
1864 | |
1865 | local debugMode = Settings.InstanceTrackerMode |
1866 | |
1867 | if debugMode == 3 or (debugMode == 2 and call.HasInstance) then |
1868 | watermark ..= (--[[Settings.OptimizedInstanceTracker and optimizedInstanceTrackerFunctionString or]] instanceTrackerFunctionString) .. "\n\n" |
1869 | else |
1870 | watermark ..= "\n" |
1871 | end |
1872 | |
1873 | local pathStr = getInstancePath(rem) |
1874 | local remPath = ((debugMode == 3 or ((debugMode == 2) and (sub(pathStr, -2, -1) == "]]"))) and ("GetInstanceFromDebugId(\"" .. getDebugId(rem) .."\")" .. " -- Original Path: " .. pathStr)) or pathStr |
1875 | |
1876 | if spyFunc.Type == "Connection" then |
1877 | local pseudocode = "" |
1878 | |
1879 | pseudocode ..= Settings.PseudocodeInlineRemote and ("local remote" .. (Settings.PseudocodeLuaUTypes and (": " .. spyFunc.Object) or "") .." = " .. remPath .. "\nremote." .. spyFunc.Connection .. ":Connect(function(") or (remPath .. "." .. spyFunc.Connection .. ":Connect(function(") |
1880 | for i = 1,call.NonNilArgCount do |
1881 | pseudocode ..= "p" .. tostring(i) .. ", " |
1882 | end |
1883 | pseudocode = (call.NonNilArgCount > 0 and sub(pseudocode, 1, -3) or pseudocode) .. ")" |
1884 | |
1885 | pseudocode ..= "\n\tprint(\"" .. spyFunc.Connection .. " Connection Fired:\", " |
1886 | for i = 1,call.NonNilArgCount do |
1887 | pseudocode ..= "p"..tostring(i) .. ", " |
1888 | end |
1889 | pseudocode = (sub(pseudocode, 1, -3) .. ")") |
1890 | |
1891 | pseudocode ..= "\nend)" |
1892 | return watermark .. pseudocode |
1893 | elseif spyFunc.Type == "Callback" then |
1894 | local pseudocode = "" |
1895 | |
1896 | pseudocode ..= Settings.PseudocodeInlineRemote and ("local remote" .. (Settings.PseudocodeLuaUTypes and (": " .. spyFunc.Object) or "").." = " .. remPath .. "\nremote." .. spyFunc.Callback .. " = function(") or (remPath .. "." .. spyFunc.Callback .. " = function(") |
1897 | for i = 1,call.NonNilArgCount do |
1898 | pseudocode ..= "p"..tostring(i) .. ", " |
1899 | end |
1900 | pseudocode = (call.NonNilArgCount > 0 and sub(pseudocode, 1, -3) or pseudocode) .. ")" |
1901 | |
1902 | pseudocode ..= "\n\tprint(\"" .. spyFunc.Callback .. " Callback Called:\", " |
1903 | for i = 1,call.NonNilArgCount do |
1904 | pseudocode ..= "p"..tostring(i) .. ", " |
1905 | end |
1906 | pseudocode = (sub(pseudocode, 1, -3) .. ")") |
1907 | |
1908 | pseudocode ..= "\nend" |
1909 | return watermark .. pseudocode |
1910 | end |
1911 | end |
1912 | |
1913 | local otherLines = {} |
1914 | local otherLogs = {} |
1915 | local otherFuncs = {} |
1916 | local callLines = {} |
1917 | local callLogs = {} |
1918 | local callFuncs = {} |
1919 | |
1920 | local originalCallerCache = {} |
1921 | local remoteNameCache = {} |
1922 | local remoteBlacklistCache = setmetatable({}, {__mode="kv"}) -- used to store remotes that aren't replicated to the server |
1923 | -- remoteBlacklistCache[remote] = nil or 1 or 2, 1 = allowed, 2 = blacklisted, nil = not initialized |
1924 | |
1925 | local argLines = {} |
1926 | local callbackButtonline |
1927 | |
1928 | local searchBar -- declared later |
1929 | local function clearFilter() |
1930 | for i,v in callLines do |
1931 | for _,x in spyFunctions do |
1932 | if v[1] == x.Name and (v[3].Label ~= "0" or callLogs[i].Ignored) and x.Enabled then |
1933 | v[2].Visible = true |
1934 | v[4].Visible = true |
1935 | break |
1936 | end |
1937 | end |
1938 | end |
1939 | for i,v in otherLines do |
1940 | for _,x in spyFunctions do |
1941 | if v[1] == x.Name and (v[3].Label ~= "0" or otherLogs[i].Ignored) and x.Enabled then |
1942 | v[2].Visible = true |
1943 | v[4].Visible = true |
1944 | break |
1945 | end |
1946 | end |
1947 | end |
1948 | end |
1949 | |
1950 | local function filterLines(name: string) |
1951 | if name == "" then |
1952 | return clearFilter() |
1953 | end |
1954 | |
1955 | for i,v in callLines do |
1956 | if not match(lower(tostring(i)), lower(name)) then -- check for if the remote actually had a log made |
1957 | v[2].Visible = false |
1958 | v[4].Visible = false |
1959 | elseif spyFunctions[idxs[v[1]]].Enabled then |
1960 | v[2].Visible = true |
1961 | v[4].Visible = true |
1962 | end |
1963 | end |
1964 | for i,v in otherLines do |
1965 | if not match(lower(tostring(i)), lower(name)) then -- check for if the remote actually had a log made |
1966 | v[2].Visible = false |
1967 | v[4].Visible = false |
1968 | elseif spyFunctions[idxs[v[1]]].Enabled then |
1969 | v[2].Visible = true |
1970 | v[4].Visible = true |
1971 | end |
1972 | end |
1973 | end |
1974 | |
1975 | local function updateLines(name: string, enabled: boolean) |
1976 | for i,v in callLines do |
1977 | if v[1] == name then |
1978 | if v[2].Visible ~= enabled then |
1979 | if (enabled and (v[3].Label ~= "0" or callLogs[i].Ignored)) or not enabled then |
1980 | v[2].Visible = enabled |
1981 | v[4].Visible = enabled |
1982 | end |
1983 | end |
1984 | end |
1985 | end |
1986 | for i,v in otherLines do |
1987 | if v[1] == name then |
1988 | if v[2].Visible ~= enabled then |
1989 | if (enabled and (v[3].Label ~= "0" or otherLogs[i].Ignored)) or not enabled then |
1990 | v[2].Visible = enabled |
1991 | v[4].Visible = enabled |
1992 | end |
1993 | end |
1994 | end |
1995 | end |
1996 | filterLines(searchBar.Value) |
1997 | end |
1998 | |
1999 | repeat task.wait() until pcall(function() |
2000 | _G.remoteSpyMainWindow = RenderWindow.new("Remote Spy") |
2001 | end) |
2002 | _G.remoteSpySettingsWindow = RenderWindow.new("Remote Spy Settings") |
2003 | _G.remoteSpyCallbackHooks = {} |
2004 | _G.remoteSpySignalHooks = {} |
2005 | _G.remoteSpyHooks = {} |
2006 | |
2007 | local mainWindow = _G.remoteSpyMainWindow |
2008 | local mainWindowWeakReference = setmetatable({mainWindow}, {__mode="v"}) |
2009 | local settingsWindow = _G.remoteSpySettingsWindow |
2010 | local settingsWindowWeakReference = setmetatable({settingsWindow}, {__mode="v"}) |
2011 | pushTheme(mainWindow) |
2012 | pushTheme(settingsWindow) |
2013 | |
2014 | -- settings page init |
2015 | local settingsWidth = 310 |
2016 | local settingsHeight = 312 |
2017 | settingsWindow.DefaultSize = Vector2.new(settingsWidth, settingsHeight) |
2018 | settingsWindow.CanResize = false |
2019 | settingsWindow.VisibilityOverride = Settings.AlwaysOnTop |
2020 | settingsWindow.Visible = false |
2021 | settingsWindow:SetColor(RenderColorOption.ResizeGrip, black, 0) |
2022 | settingsWindow:SetColor(RenderColorOption.ResizeGripActive, black, 0) |
2023 | settingsWindow:SetColor(RenderColorOption.ResizeGripHovered, black, 0) |
2024 | settingsWindow:SetStyle(RenderStyleOption.WindowPadding, Vector2.new(8, 4)) |
2025 | settingsWindow:SetStyle(RenderStyleOption.ButtonTextAlign, Vector2.new(0.5, 0.5)) |
2026 | |
2027 | -- main page init |
2028 | local width = 562 |
2029 | mainWindow.DefaultSize = Vector2.new(width, 350) |
2030 | mainWindow.MinSize = Vector2.new(width, 356) |
2031 | mainWindow.MaxSize = Vector2.new(width, 5000) |
2032 | mainWindow.VisibilityOverride = Settings.AlwaysOnTop |
2033 | |
2034 | local frontPage = mainWindow:Dummy() |
2035 | local remotePage = mainWindow:Dummy() |
2036 | |
2037 | -- Below this is rendering Settings page |
2038 | |
2039 | local topBar = settingsWindow:SameLine() |
2040 | local exitButtonFrame = topBar:SameLine() |
2041 | exitButtonFrame:SetColor(RenderColorOption.Button, black, 0) |
2042 | --exitButtonFrame:SetColor(RenderColorOption.ButtonHovered, black, 0) |
2043 | --exitButtonFrame:SetColor(RenderColorOption.ButtonActive, black, 0) |
2044 | |
2045 | local exitButton = exitButtonFrame:Indent(settingsWidth-40):Button() |
2046 | exitButton.Label = "\xef\x80\x8d" |
2047 | exitButton.Size = Vector2.new(24, 24) |
2048 | exitButton.OnUpdated:Connect(function() |
2049 | settingsWindowWeakReference[1].Visible = false |
2050 | end) |
2051 | |
2052 | local tabFrame = topBar:SameLine() |
2053 | local settingsTabs = tabFrame:Indent(-1):Indent(1):TabMenu() |
2054 | local generalTab = settingsTabs:Add("General") |
2055 | local pseudocodeTab = settingsTabs:Add("Pseudocode") |
2056 | local outputTab = settingsTabs:Add("Output") |
2057 | local creditsTab = settingsTabs:Add("Credits") |
2058 | do -- general Settings |
2059 | local checkBox = generalTab:CheckBox() |
2060 | checkBox.Label = "Display Callbacks And Connections" |
2061 | checkBox.Value = Settings.CallbackButtons |
2062 | checkBox.OnUpdated:Connect(function(value) |
2063 | Settings.CallbackButtons = value |
2064 | callbackButtonline.Visible = value |
2065 | if not value then |
2066 | for i = 5,8 do |
2067 | local spyFunc = spyFunctions[i] |
2068 | spyFunc.Button.Value = false |
2069 | spyFunc.Enabled = false |
2070 | Settings[spyFunc.Name] = false |
2071 | updateLines(spyFunc.Name, false) |
2072 | end |
2073 | end |
2074 | saveConfig() |
2075 | end) |
2076 | |
2077 | local checkBox4 = generalTab:CheckBox() |
2078 | checkBox4.Label = "Cache Unselected Types' Calls" |
2079 | checkBox4.Value = Settings.LogHiddenRemotesCalls |
2080 | checkBox4.OnUpdated:Connect(function(value) |
2081 | Settings.LogHiddenRemotesCalls = value |
2082 | saveConfig() |
2083 | end) |
2084 | |
2085 | local checkBox6 = generalTab:CheckBox() |
2086 | checkBox6.Label = "Call Cache Amount Limiter (Per Remote)" |
2087 | checkBox6.Value = Settings.CacheLimit |
2088 | checkBox6.OnUpdated:Connect(function(value) |
2089 | Settings.CacheLimit = value |
2090 | saveConfig() |
2091 | end) |
2092 | |
2093 | local slider1 = generalTab:IntSlider() |
2094 | slider1.Label = "Max Calls" |
2095 | slider1.Min = 100 |
2096 | slider1.Max = 10000 -- if you need to cache more than 10k calls, just disable caching |
2097 | slider1.Value = Settings.MaxCallAmount |
2098 | slider1.Clamped = true |
2099 | slider1.OnUpdated:Connect(function(value) |
2100 | if value >= 100 and value <= 10000 then -- incase they're mid way through typing it |
2101 | Settings.MaxCallAmount = value |
2102 | saveConfig() |
2103 | end |
2104 | end) |
2105 | |
2106 | local slider2 = generalTab:IntSlider() |
2107 | slider2.Label = "Max Args" |
2108 | slider2.Min = 10 |
2109 | slider2.Max = 100 |
2110 | slider2.Value = Settings.ArgLimit |
2111 | slider2.Clamped = true |
2112 | slider2.OnUpdated:Connect(function(value) |
2113 | if value >= 10 and value <= 100 then -- incase they're mid way through typing it |
2114 | Settings.ArgLimit = value |
2115 | saveConfig() |
2116 | end |
2117 | end) |
2118 | |
2119 | local checkBox7 = generalTab:CheckBox() |
2120 | checkBox7.Label = "Enable Get Calling Script V2" |
2121 | checkBox7.Value = Settings.GetCallingScriptV2 |
2122 | checkBox7.OnUpdated:Connect(function(value) |
2123 | Settings.GetCallingScriptV2 = value |
2124 | saveConfig() |
2125 | end) |
2126 | |
2127 | local checkBox8 = generalTab:CheckBox() |
2128 | checkBox8.Label = "Enable Get Call Stack" |
2129 | checkBox8.Value = Settings.StoreCallStack |
2130 | checkBox8.OnUpdated:Connect(function(value) |
2131 | Settings.StoreCallStack = value |
2132 | saveConfig() |
2133 | end) |
2134 | |
2135 | local checkBox5 = generalTab:CheckBox() |
2136 | checkBox5.Label = "Extra Repeat Call Amounts" |
2137 | checkBox5.Value = Settings.MoreRepeatCallOptions |
2138 | checkBox5.OnUpdated:Connect(function(value) |
2139 | Settings.MoreRepeatCallOptions = value |
2140 | saveConfig() |
2141 | end) |
2142 | |
2143 | local checkBox6 = generalTab:CheckBox() |
2144 | checkBox6.Label = "Always On Top" |
2145 | checkBox6.Value = Settings.AlwaysOnTop |
2146 | checkBox6.OnUpdated:Connect(function(value) |
2147 | Settings.AlwaysOnTop = value |
2148 | mainWindowWeakReference[1].VisibilityOverride = value |
2149 | settingsWindowWeakReference[1].VisibilityOverride = value |
2150 | saveConfig() |
2151 | end) |
2152 | end -- general settings |
2153 | |
2154 | do -- pseudocode settings |
2155 | |
2156 | local checkBox1 = pseudocodeTab:CheckBox() |
2157 | checkBox1.Label = "Pseudocode Watermark" |
2158 | checkBox1.Value = Settings.PseudocodeWatermark |
2159 | checkBox1.OnUpdated:Connect(function(value) |
2160 | Settings.PseudocodeWatermark = value |
2161 | saveConfig() |
2162 | end) |
2163 | |
2164 | local checkBox2 = pseudocodeTab:CheckBox() |
2165 | checkBox2.Label = "Use LuaU Type Declarations" |
2166 | checkBox2.Value = Settings.PseudocodeLuaUTypes |
2167 | checkBox2.OnUpdated:Connect(function(value) |
2168 | Settings.PseudocodeLuaUTypes = value |
2169 | saveConfig() |
2170 | end) |
2171 | |
2172 | local checkBox5 = pseudocodeTab:CheckBox() |
2173 | checkBox5.Label = "Format Tables" |
2174 | checkBox5.Value = Settings.PseudocodeFormatTables |
2175 | checkBox5.OnUpdated:Connect(function(value) |
2176 | Settings.PseudocodeFormatTables = value |
2177 | saveConfig() |
2178 | end) |
2179 | |
2180 | local checkBox3 = pseudocodeTab:CheckBox() |
2181 | checkBox3.Label = "Inline Remote" |
2182 | checkBox3.Value = Settings.PseudocodeInlineRemote |
2183 | checkBox3.OnUpdated:Connect(function(value) |
2184 | Settings.PseudocodeInlineRemote = value |
2185 | saveConfig() |
2186 | end) |
2187 | |
2188 | local checkBox4 = pseudocodeTab:CheckBox() |
2189 | checkBox4.Label = "Inline Hidden Nils" |
2190 | checkBox4.Value = Settings.PseudocodeInlineHiddenNils |
2191 | checkBox4.OnUpdated:Connect(function(value) |
2192 | Settings.PseudocodeInlineHiddenNils = value |
2193 | saveConfig() |
2194 | end) |
2195 | |
2196 | pseudocodeTab:Label("Pseudocode Inlining Mode") |
2197 | local combo2 = pseudocodeTab:Combo() |
2198 | combo2.Items = { "Everything", "Tables And Userdatas", "Tables Only", "Nothing" } |
2199 | combo2.SelectedItem = Settings.PseudocodeInliningMode |
2200 | combo2.OnUpdated:Connect(function(selection) |
2201 | Settings.PseudocodeInliningMode = selection |
2202 | saveConfig() |
2203 | end) |
2204 | |
2205 | pseudocodeTab:Label("Instance Tracker") |
2206 | local combo3 = pseudocodeTab:Combo() |
2207 | combo3.Items = { "Off", "Nil Parented Only", "All Instances" } |
2208 | combo3.SelectedItem = Settings.InstanceTrackerMode |
2209 | combo3.OnUpdated:Connect(function(selection) |
2210 | Settings.InstanceTrackerMode = selection |
2211 | saveConfig() |
2212 | end) |
2213 | |
2214 | --[[local checkBox5 = pseudocodeTab:CheckBox() |
2215 | checkBox5.Label = "Optimized Instance Tracker" |
2216 | checkBox5.Value = Settings.OptimizedInstanceTracker |
2217 | checkBox5.OnUpdated:Connect(function(value) |
2218 | Settings.OptimizedInstanceTracker = value |
2219 | saveConfig() |
2220 | end)]] |
2221 | end -- pseudocode settings |
2222 | |
2223 | do -- output settings |
2224 | outputTab:Label("Pseudocode Output") |
2225 | local combo1 = outputTab:Combo() |
2226 | combo1.Items = { "Clipboard", "External UI", "Internal UI (Not Implemented)" } |
2227 | combo1.SelectedItem = Settings.PseudocodeOutput |
2228 | combo1.OnUpdated:Connect(function(selection) |
2229 | Settings.PseudocodeOutput = selection |
2230 | saveConfig() |
2231 | end) |
2232 | |
2233 | outputTab:Label("Decompiled Script Output") |
2234 | local combo2 = outputTab:Combo() |
2235 | combo2.Items = { "Clipboard", "External UI", "Internal UI (Not Implemented)" } |
2236 | combo2.SelectedItem = Settings.DecompilerOutput |
2237 | combo2.OnUpdated:Connect(function(selection) |
2238 | Settings.DecompilerOutput = selection |
2239 | saveConfig() |
2240 | end) |
2241 | |
2242 | outputTab:Label("Connections List Output") |
2243 | local combo3 = outputTab:Combo() |
2244 | combo3.Items = { "Clipboard", "External UI", "Internal UI (Not Implemented)" } |
2245 | combo3.SelectedItem = Settings.ConnectionsOutput |
2246 | combo3.OnUpdated:Connect(function(selection) |
2247 | Settings.ConnectionsOutput = selection |
2248 | saveConfig() |
2249 | end) |
2250 | |
2251 | outputTab:Label("Call Stack Output") |
2252 | local combo4 = outputTab:Combo() |
2253 | combo4.Items = { "Clipboard", "External UI", "Internal UI (Not Implemented)" } |
2254 | combo4.SelectedItem = Settings.CallStackOutput |
2255 | combo4.OnUpdated:Connect(function(selection) |
2256 | Settings.CallStackOutput = selection |
2257 | saveConfig() |
2258 | end) |
2259 | end -- theme settings |
2260 | |
2261 | do -- credits |
2262 | creditsTab:Label("Written by GameGuy") |
2263 | creditsTab:Label("With Inspriation from Hydroxide") |
2264 | creditsTab:Separator() |
2265 | |
2266 | creditsTab:Label("Discord: GameGuy#5286 | 515708480661749770") |
2267 | local setDiscordToClipboard = creditsTab:Button() |
2268 | setDiscordToClipboard.Label = "Set Discord ID To Clipboard" |
2269 | setDiscordToClipboard.OnUpdated:Connect(function() |
2270 | setclipboard("515708480661749770") |
2271 | end) |
2272 | |
2273 | creditsTab:Separator() |
2274 | creditsTab:Label("Thank you to all of the Contributors on Github") |
2275 | end -- credits |
2276 | |
2277 | -- Below this is rendering Remote Page |
2278 | |
2279 | remotePage.Visible = false |
2280 | |
2281 | local currentSelectedRemote, currentSelectedRemoteInstance, currentSelectedType |
2282 | |
2283 | local remotePageObjects = { |
2284 | Name = nil, |
2285 | Icon = nil, |
2286 | IconFrame = nil, |
2287 | IgnoreButton = nil, |
2288 | IgnoreButtonFrame = nil, |
2289 | BlockButton = nil, |
2290 | BlockButtonFrame = nil |
2291 | } |
2292 | |
2293 | local remoteViewerMainWindow = nil |
2294 | |
2295 | local function unloadRemote() |
2296 | frontPage.Visible = true |
2297 | remotePage.Visible = false |
2298 | currentSelectedRemote = nil |
2299 | currentSelectedRemoteInstance = nil |
2300 | currentSelectedType = "" |
2301 | for _,v in argLines do |
2302 | v[2]:Clear() |
2303 | end |
2304 | tableClear(argLines) |
2305 | remoteViewerMainWindow:Clear() |
2306 | end |
2307 | |
2308 | local topBar = remotePage:SameLine() |
2309 | |
2310 | local pauseSpyButton -- declared later, referenced here |
2311 | local pauseSpyButton2 |
2312 | |
2313 | do -- topbar code |
2314 | |
2315 | local buttonsFrame = topBar:Dummy():SameLine() |
2316 | buttonsFrame:SetColor(RenderColorOption.Button, black, 0) |
2317 | buttonsFrame:SetStyle(RenderStyleOption.ButtonTextAlign, Vector2.new(0.5, 0.5)) |
2318 | |
2319 | pauseSpyButton2 = buttonsFrame:Indent(width-68):Button() |
2320 | pauseSpyButton2.Size = Vector2.new(24, 24) |
2321 | pauseSpyButton2.Label = Settings.Paused and "\xef\x80\x9d" or "\xef\x8a\x8c" |
2322 | pauseSpyButton2.OnUpdated:Connect(function() |
2323 | if Settings.Paused then |
2324 | Settings.Paused = false |
2325 | pauseSpyButton.Label = "\xef\x8a\x8c" |
2326 | pauseSpyButton2.Label = "\xef\x8a\x8c" |
2327 | else |
2328 | Settings.Paused = true |
2329 | pauseSpyButton.Label = "\xef\x80\x9d" |
2330 | pauseSpyButton2.Label = "\xef\x80\x9d" |
2331 | end |
2332 | end) |
2333 | |
2334 | local exitButton = buttonsFrame:Indent(width-40):Button() |
2335 | exitButton.Size = Vector2.new(24, 24) |
2336 | exitButton.Label = "\xef\x80\x8d" |
2337 | exitButton.OnUpdated:Connect(unloadRemote) |
2338 | |
2339 | local remoteNameFrame = topBar:Dummy() |
2340 | remoteNameFrame:SetStyle(RenderStyleOption.ButtonTextAlign, Vector2.new(0, 0.5)) |
2341 | remoteNameFrame:SetColor(RenderColorOption.Button, black, 0) |
2342 | remoteNameFrame:SetColor(RenderColorOption.ButtonActive, black, 0) |
2343 | remoteNameFrame:SetColor(RenderColorOption.ButtonHovered, black, 0) |
2344 | local remoteName = remoteNameFrame:Indent(26):Button() |
2345 | remoteName.Size = Vector2.new(300, 24) |
2346 | remoteName.Label = "RemoteEvent" |
2347 | |
2348 | local remoteIconFrame = topBar:Dummy():WithFont(RemoteIconFont) |
2349 | remoteIconFrame:SetStyle(RenderStyleOption.ButtonTextAlign, Vector2.new(1, 0.5)) |
2350 | remoteIconFrame:SetColor(RenderColorOption.Button, black, 0) |
2351 | remoteIconFrame:SetColor(RenderColorOption.ButtonActive, black, 0) |
2352 | remoteIconFrame:SetColor(RenderColorOption.ButtonHovered, black, 0) |
2353 | remoteIconFrame:SetColor(RenderColorOption.Text, black, 1) -- temporarily black, gets set later |
2354 | local remoteIcon = remoteIconFrame:Indent(4):Button() |
2355 | remoteIcon.Size = Vector2.new(20, 24) |
2356 | remoteIcon.Label = "\xee\x80\x80" -- default |
2357 | |
2358 | remotePageObjects.Name = remoteName |
2359 | remotePageObjects.Icon = remoteIcon |
2360 | remotePageObjects.IconFrame = remoteIconFrame |
2361 | end |
2362 | |
2363 | local buttonBarFrame = remotePage:SameLine() |
2364 | |
2365 | do -- button bar code |
2366 | local buttonBar = buttonBarFrame:Indent(125):SameLine() |
2367 | |
2368 | local ignoreButtonFrame = buttonBar:Dummy() |
2369 | ignoreButtonFrame:SetColor(RenderColorOption.Text, red, 1) |
2370 | local ignoreButton = ignoreButtonFrame:Button() |
2371 | ignoreButton.Label = "Ignore" |
2372 | |
2373 | ignoreButton.OnUpdated:Connect(function() |
2374 | if currentSelectedRemote then |
2375 | local funcList = (currentSelectedType == "Call") and callFuncs or otherFuncs |
2376 | local logs = (currentSelectedType == "Call") and callLogs or otherLogs |
2377 | |
2378 | if logs[currentSelectedRemote].Ignored then |
2379 | logs[currentSelectedRemote].Ignored = false |
2380 | ignoreButtonFrame:SetColor(RenderColorOption.Text, red, 1) |
2381 | ignoreButton.Label = "Ignore" |
2382 | else |
2383 | logs[currentSelectedRemote].Ignored = true |
2384 | ignoreButtonFrame:SetColor(RenderColorOption.Text, green, 1) |
2385 | ignoreButton.Label = "Unignore" |
2386 | end |
2387 | funcList[currentSelectedRemote].UpdateIgnores() |
2388 | end |
2389 | end) |
2390 | --[[buttonBar = buttonBar:SameLine() |
2391 | buttonBar:SetColor(RenderColorOption.Button, colorRGB(25, 25, 28), 1) |
2392 | buttonBar:SetColor(RenderColorOption.ButtonHovered, colorRGB(55, 55, 58), 1) |
2393 | buttonBar:SetColor(RenderColorOption.ButtonActive, colorRGB(75, 75, 78), 1)]] |
2394 | |
2395 | local blockButtonFrame = buttonBar:Dummy() |
2396 | blockButtonFrame:SetColor(RenderColorOption.Text, red, 1) |
2397 | local blockButton = blockButtonFrame:Button() |
2398 | blockButton.Label = "Block" |
2399 | blockButton.OnUpdated:Connect(function() |
2400 | local logs = (currentSelectedType == "Call") and callLogs or otherLogs |
2401 | local funcList = (currentSelectedType == "Call") and callFuncs or otherFuncs |
2402 | |
2403 | if currentSelectedRemote then |
2404 | if logs[currentSelectedRemote].Blocked then |
2405 | logs[currentSelectedRemote].Blocked = false |
2406 | blockButtonFrame:SetColor(RenderColorOption.Text, red, 1) |
2407 | blockButton.Label = "Block" |
2408 | else |
2409 | logs[currentSelectedRemote].Blocked = true |
2410 | blockButtonFrame:SetColor(RenderColorOption.Text, green, 1) |
2411 | blockButton.Label = "Unblock" |
2412 | end |
2413 | funcList[currentSelectedRemote].UpdateBlocks() |
2414 | end |
2415 | end) |
2416 | local clearLogsButton = buttonBar:Button() |
2417 | clearLogsButton.Label = "Clear Logs" |
2418 | clearLogsButton.OnUpdated:Connect(function() |
2419 | local logs = (currentSelectedType == "Call") and callLogs or otherLogs |
2420 | local lines = (currentSelectedType == "Call") and callLines or otherLines |
2421 | if currentSelectedRemote then |
2422 | do -- updates front menu |
2423 | tableClear(logs[currentSelectedRemote].Calls) |
2424 | lines[currentSelectedRemote][3].Label = "0" |
2425 | if not logs[currentSelectedRemote].Ignored then |
2426 | lines[currentSelectedRemote][2].Visible = false |
2427 | lines[currentSelectedRemote][4].Visible = false |
2428 | end |
2429 | end |
2430 | |
2431 | do -- updates remote menu |
2432 | for _,v in argLines do |
2433 | v[2]:Clear() |
2434 | end |
2435 | tableClear(argLines) |
2436 | remoteViewerMainWindow:Clear() |
2437 | addSpacer(remoteViewerMainWindow, 8) |
2438 | end |
2439 | end |
2440 | end) |
2441 | |
2442 | local copyPathButton = buttonBar:Button() |
2443 | copyPathButton.Label = "Copy Path" |
2444 | copyPathButton.OnUpdated:Connect(function() |
2445 | if currentSelectedRemote then |
2446 | local str = getInstancePath(currentSelectedRemoteInstance) |
2447 | if type(str) == "string" then |
2448 | outputData(str, 1, "", "Copied Path") |
2449 | else |
2450 | pushError("Failed to Copy Path") |
2451 | end |
2452 | end |
2453 | end) |
2454 | |
2455 | remotePageObjects.IgnoreButton = ignoreButton |
2456 | remotePageObjects.IgnoreButtonFrame = ignoreButtonFrame |
2457 | remotePageObjects.BlockButton = blockButton |
2458 | remotePageObjects.BlockButtonFrame = blockButtonFrame |
2459 | end |
2460 | |
2461 | local remoteArgFrame = remotePage:SameLine() |
2462 | |
2463 | do -- arg frame code |
2464 | remoteArgFrame:SetColor(RenderColorOption.ChildBg, colorOptions.TitleBg[1], 1) |
2465 | remoteArgFrame:SetStyle(RenderStyleOption.ChildRounding, 5) |
2466 | remoteViewerMainWindow = remoteArgFrame:Child() |
2467 | remoteViewerMainWindow:SetStyle(RenderStyleOption.ItemSpacing, Vector2.new(4, 0)) |
2468 | remoteViewerMainWindow:SetStyle(RenderStyleOption.ItemSpacing, Vector2.new(0, 0)) |
2469 | end |
2470 | |
2471 | local function createCSButton(window: RenderWindow, call, spyFunc) |
2472 | local frame = window:Dummy() |
2473 | if spyFunc.HasNoCaller or call.FromSynapse then |
2474 | frame:SetColor(RenderColorOption.Text, grey, 1) |
2475 | frame:SetColor(RenderColorOption.HeaderHovered, black, 0) |
2476 | frame:SetColor(RenderColorOption.HeaderActive, black, 0) |
2477 | end |
2478 | local button = frame:Selectable() |
2479 | button.Label = "Get Calling Script" |
2480 | if not (spyFunc.HasNoCaller or call.FromSynapse) then |
2481 | button.OnUpdated:Connect(function() |
2482 | local scr = Settings.GetCallingScriptV2 and call.ScriptV2 or call.Script |
2483 | local str = scr and getInstancePath(scr) -- not sure if getcallingscript can return a ModuleScript, I assume it can't, but adding this just in case |
2484 | if type(str) == "string" then |
2485 | outputData(str, 1, "", "Copied Calling Script") |
2486 | else |
2487 | pushError("Failed to get Calling Script") |
2488 | end |
2489 | end) |
2490 | end |
2491 | end |
2492 | |
2493 | local function createCSDecompileButton(window: RenderWindow, call, spyFunc) |
2494 | local frame = window:Dummy() |
2495 | if spyFunc.HasNoCaller or call.FromSynapse then |
2496 | frame:SetColor(RenderColorOption.Text, grey, 1) |
2497 | frame:SetColor(RenderColorOption.HeaderHovered, black, 0) |
2498 | frame:SetColor(RenderColorOption.HeaderActive, black, 0) |
2499 | end |
2500 | local button = frame:Selectable() |
2501 | button.Label = "Decompile Calling Script" |
2502 | if not (spyFunc.HasNoCaller or call.FromSynapse) then |
2503 | button.OnUpdated:Connect(function() |
2504 | local suc, res = pcall(function() |
2505 | local scr = Settings.GetCallingScriptV2 and call.ScriptV2 or call.Script |
2506 | local str = decompile(scr) |
2507 | local scriptName = scr and getInstancePath(scr) |
2508 | if type(str) == "string" then |
2509 | outputData(str, Settings.DecompilerOutput, scriptName, "Decompiled Calling Script") |
2510 | else |
2511 | pushError("Failed to Decompile Calling Script2") |
2512 | end |
2513 | end) |
2514 | if not suc then |
2515 | pushError("Failed to Decompile Calling Script", res) |
2516 | end |
2517 | end) |
2518 | end |
2519 | end |
2520 | |
2521 | local function repeatCall(call, remote: Instance, remoteId: string, spyFunc, repeatCount: number) |
2522 | if spyFunc.Type == "Call" then |
2523 | local func = spyFunc.Function |
2524 | |
2525 | local success, result = pcall(function() |
2526 | if spyFunc.ReturnsValue then |
2527 | for _ = 1,repeatCount do |
2528 | originalCallerCache[remoteId] = {nil, true} |
2529 | spawnFunc(func, remote, unpack(call.Args, 1, call.NonNilArgCount + call.NilCount)) |
2530 | end |
2531 | else |
2532 | for _ = 1,repeatCount do |
2533 | spawnFunc(func, remote, unpack(call.Args, 1, call.NonNilArgCount + call.NilCount)) -- shouldn't be task.spawned but needs to be because of oth.hook being weird |
2534 | end |
2535 | end |
2536 | end) |
2537 | if not success then |
2538 | pushError("Failed to Repeat Call", result) |
2539 | end |
2540 | elseif spyFunc.Type == "Callback" then |
2541 | local success, result = pcall(function() |
2542 | for _ = 1,repeatCount do |
2543 | spawnFunc(call.CallbackLog.CurrentFunction, unpack(call.Args, 1, call.NonNilArgCount + call.NilCount)) -- always spawned to make callstack look legit |
2544 | end |
2545 | end) |
2546 | if not success then |
2547 | pushError("Failed to Repeat Callback Call", result) |
2548 | end |
2549 | elseif spyFunc.Type == "Connection" then |
2550 | local success, result = pcall(function() |
2551 | for _ = 1,repeatCount do |
2552 | originalCallerCache[remoteId] = {nil, true} |
2553 | cfiresignal(call.Signal, unpack(call.Args, 1, call.NonNilArgCount + call.NilCount)) |
2554 | end |
2555 | end) |
2556 | if not success then |
2557 | pushError("Failed to Repeat Connection", result) |
2558 | end |
2559 | end |
2560 | end |
2561 | |
2562 | local function createRepeatCallButton(window: RenderWindow, call, remote: Instance, remoteId, spyFunc, amt) -- NEEDS TO BE REDONE FOR CONS AND CALLBACKS |
2563 | local button = window:Selectable() |
2564 | button.Label = amt and ("Repeat Call x" .. tostring(amt)) or "Repeat Call" |
2565 | button.Visible = true |
2566 | |
2567 | amt = amt or 1 |
2568 | |
2569 | button.OnUpdated:Connect(function() repeatCall(call, remote, remoteId, spyFunc, amt) end) |
2570 | end |
2571 | |
2572 | local function createGenSendPCButton(window: RenderWindow, call, remote: Instance, spyFunc) |
2573 | local button = window:Selectable() |
2574 | button.Label = "Generate Calling Pseudocode" |
2575 | button.OnUpdated:Connect(function() |
2576 | local suc, ret = pcall(function() |
2577 | outputData(genSendPseudo(remote, call, spyFunc), Settings.PseudocodeOutput, "RS Pseudocode", "Generated Pseudocode") |
2578 | end) |
2579 | if not suc then |
2580 | pushError("Failed to Generate Pseudocode", ret) |
2581 | end |
2582 | end) |
2583 | end |
2584 | |
2585 | local function createGenRecvPCButton(window: RenderWindow, call, remote: Instance, spyFunc) |
2586 | local frame = window:Dummy() |
2587 | if spyFunc.Type == "Call" then |
2588 | frame:SetColor(RenderColorOption.Text, grey, 1) |
2589 | frame:SetColor(RenderColorOption.HeaderHovered, black, 0) |
2590 | frame:SetColor(RenderColorOption.HeaderActive, black, 0) |
2591 | end |
2592 | local button = frame:Selectable() |
2593 | button.Label = "Generate Receiving Pseudocode" |
2594 | if spyFunc.Type ~= "Call" then |
2595 | button.OnUpdated:Connect(function() |
2596 | local suc, res = pcall(function() |
2597 | outputData(genRecvPseudo(remote, call, spyFunc, Settings.PseudocodeWatermark), Settings.PseudocodeOutput, "RS Pseudocode", "Generated Pseudocode") |
2598 | end) |
2599 | if not suc then |
2600 | pushError("Failed to Generate Pseudocode", res) |
2601 | end |
2602 | end) |
2603 | end |
2604 | end |
2605 | |
2606 | local function genCallStackString(callStack) |
2607 | local callStackString = "" |
2608 | if Settings.PseudocodeWatermark then |
2609 | callStackString ..= watermarkString |
2610 | end |
2611 | |
2612 | callStackString ..= "\nlocal CallStack = {" |
2613 | |
2614 | for i,v in callStack do |
2615 | callStackString ..= strformat("\n\t[%s] = {\n\t\tScript = %s,\n\t\tLine = %s,\n\t\tType = %s\n\t},", tostring(i), v.Script and getInstancePath(v.Script) or "\"nil\"", tostring(v.LineNumber), "\"" .. v.Type.. "\"") |
2616 | end |
2617 | |
2618 | return (sub(callStackString, 1, -2) .. "\n}") |
2619 | end |
2620 | |
2621 | local function createGetCallStackButton(window: RenderWindow, call, spyFunc) |
2622 | local frame = window:Dummy() |
2623 | if spyFunc.Type ~= "Call" or call.FromSynapse then |
2624 | frame:SetColor(RenderColorOption.Text, grey, 1) |
2625 | frame:SetColor(RenderColorOption.HeaderHovered, black, 0) |
2626 | frame:SetColor(RenderColorOption.HeaderActive, black, 0) |
2627 | end |
2628 | local button = frame:Selectable() |
2629 | button.Label = "Get Call Stack" |
2630 | if spyFunc.Type == "Call" and not call.FromSynapse then |
2631 | button.OnUpdated:Connect(function() |
2632 | local suc, res = pcall(function() |
2633 | outputData(genCallStackString(call.CallStack), Settings.CallStackOutput, "Call Stack", "Output Call Stack") |
2634 | end) |
2635 | if not suc then |
2636 | pushError("Failed to Output Call Stack", res) |
2637 | end |
2638 | end) |
2639 | end |
2640 | end |
2641 | |
2642 | local function createGetConnectionScriptsButton(window: RenderWindow, call, spyFunc) |
2643 | local frame = window:Dummy() |
2644 | if spyFunc.Type ~= "Connection" then |
2645 | frame:SetColor(RenderColorOption.Text, grey, 1) |
2646 | frame:SetColor(RenderColorOption.HeaderHovered, black, 0) |
2647 | frame:SetColor(RenderColorOption.HeaderActive, black, 0) |
2648 | end |
2649 | local button = frame:Selectable() |
2650 | button.Label = "Get Connections' Creator-Scripts" |
2651 | if spyFunc.Type == "Connection" then |
2652 | button.OnUpdated:Connect(function() |
2653 | local suc, res = pcall(function() |
2654 | local str = Settings.PseudocodeWatermark and watermarkString or "" |
2655 | str ..= "\nlocal Connections = {" |
2656 | local count = 0 |
2657 | for i,v in call.Scripts do |
2658 | count += 1 |
2659 | str ..= strformat("\n\t[%s] = {\n\t\tInstance = %s,\n\t\tAmount = %s\n\t},", tostring(count), typeof(i) == "Instance" and getInstancePath(i) or "nil, -- Created by "..tostring(i), tostring(v)) |
2660 | end |
2661 | str = sub(str, 1, -2) |
2662 | str ..= "\n}" |
2663 | outputData(str, Settings.ConnectionsOutput, "Connection Scripts", "Output Connections' Creator-Scripts List") |
2664 | end) |
2665 | if not suc then |
2666 | pushError("Failed to Get Connection Scripts", res) |
2667 | end |
2668 | end) |
2669 | end |
2670 | end |
2671 | |
2672 | local function createGetRetValButton(window: RenderWindow, call, spyFunc) |
2673 | local frame = window:Dummy() |
2674 | if not spyFunc.ReturnsValue then |
2675 | frame:SetColor(RenderColorOption.Text, grey, 1) |
2676 | frame:SetColor(RenderColorOption.HeaderHovered, black, 0) |
2677 | frame:SetColor(RenderColorOption.HeaderActive, black, 0) |
2678 | end |
2679 | local button = frame:Selectable() |
2680 | button.Label = "Get Return Value" |
2681 | if spyFunc.ReturnsValue then |
2682 | button.OnUpdated:Connect(function() |
2683 | local suc, res = pcall(function() |
2684 | local ret = call.ReturnValue |
2685 | if ret.Args then |
2686 | outputData(genReturnValuePseudo(ret, spyFunc), Settings.PseudocodeOutput, "RS Return Value", "Generated Return Value") |
2687 | else |
2688 | pushError("Failed to Get Return Value (may have never returned)") |
2689 | end |
2690 | end) |
2691 | if not suc then |
2692 | pushError("Failed to Get Return Value", res) |
2693 | end |
2694 | end) |
2695 | end |
2696 | end |
2697 | |
2698 | local function createCBButton(window: RenderWindow, call, spyFunc) |
2699 | local frame = window:Dummy() |
2700 | if spyFunc.Type ~= "Callback" or call.FromSynapse then |
2701 | frame:SetColor(RenderColorOption.Text, grey, 1) |
2702 | frame:SetColor(RenderColorOption.HeaderHovered, black, 0) |
2703 | frame:SetColor(RenderColorOption.HeaderActive, black, 0) |
2704 | end |
2705 | local button = frame:Selectable() |
2706 | button.Label = "Get Callback Creator-Script" |
2707 | if spyFunc.Type == "Callback" and not call.FromSynapse then |
2708 | button.OnUpdated:Connect(function() |
2709 | local str = call.CallbackScript and getInstancePath(call.CallbackScript) -- not sure if getcallingscript can return a ModuleScript, I assume it can't, but adding this just in case |
2710 | if type(str) == "string" then |
2711 | outputData(str, 1, "", "Set Callback Script") |
2712 | else |
2713 | pushError("Failed to get Callback Script") |
2714 | end |
2715 | end) |
2716 | end |
2717 | end |
2718 | |
2719 | local function createCBDecompileButton(window: RenderWindow, call, spyFunc) |
2720 | local frame = window:Dummy() |
2721 | if spyFunc.Type ~= "Callback" or call.FromSynapse then |
2722 | frame:SetColor(RenderColorOption.Text, grey, 1) |
2723 | frame:SetColor(RenderColorOption.HeaderHovered, black, 0) |
2724 | frame:SetColor(RenderColorOption.HeaderActive, black, 0) |
2725 | end |
2726 | local button = frame:Selectable() |
2727 | button.Label = "Decompile Callback Creator-Script" |
2728 | if spyFunc.Type == "Callback" and not call.FromSynapse then |
2729 | button.OnUpdated:Connect(function() |
2730 | local suc, res = pcall(function() |
2731 | local str = decompile(call.CallbackScript) |
2732 | local scriptName = call.CallbackScript and getInstancePath(call.CallbackScript) |
2733 | if type(str) == "string" then |
2734 | outputData(str, Settings.DecompileScriptsToExternal, scriptName, "Set Callback Script") |
2735 | else |
2736 | pushError("Failed to Decompile Callback Script2") |
2737 | end |
2738 | end) |
2739 | if not suc then |
2740 | pushError("Failed to Decompile Callback Script", res) |
2741 | end |
2742 | end) |
2743 | end |
2744 | end |
2745 | |
2746 | local function makeRemoteViewerLog(call: Instance, remote: Instance, remoteId: string) |
2747 | local totalArgCount = call.NonNilArgCount + call.NilCount |
2748 | local spyFunc = spyFunctions[call.TypeIndex] |
2749 | local tempMainDummy = remoteViewerMainWindow:Dummy() |
2750 | local tempMain = tempMainDummy:SameLine() |
2751 | tempMain:SetColor(RenderColorOption.ChildBg, colorRGB(25, 25, 28), 1) |
2752 | |
2753 | local childWindow = tempMain:Indent(8):Child() |
2754 | |
2755 | if totalArgCount < 2 then |
2756 | childWindow.Size = Vector2.new(width-46, 24 + 16) -- 2 lines (top line = 24) + 2x (8px) spacers | -46 because 16 padding on each side, plus 14 wide scrollbar |
2757 | elseif totalArgCount <= 10 then |
2758 | childWindow.Size = Vector2.new(width-46, (totalArgCount * 28) - 4 + 16) -- 24px per line, 4px spacer, 16px header and footer | -46 because 16 padding on each side, plus 14 wide scrollbar |
2759 | else -- 28 pixels per line (24 for arg, 4 for spacer), but -4 because no spacer at end, then +24 because button line, and +24 for top, bottom, and middle spacer |
2760 | childWindow.Size = Vector2.new(width-46, (10 * 28) - 4 + 16) |
2761 | end |
2762 | |
2763 | local pop = mainWindowWeakReference[1]:Popup() |
2764 | |
2765 | createGetRetValButton(pop, call, spyFunc) |
2766 | if Settings.CallbackButtons then |
2767 | createGetConnectionScriptsButton(pop, call, spyFunc) |
2768 | createCBButton(pop, call, spyFunc) |
2769 | createCBDecompileButton(pop, call, spyFunc) |
2770 | end |
2771 | |
2772 | pop:Separator() |
2773 | createCSButton(pop, call, spyFunc) |
2774 | createCSDecompileButton(pop, call, spyFunc) |
2775 | if Settings.StoreCallStack then |
2776 | createGetCallStackButton(pop, call, spyFunc) |
2777 | end |
2778 | if Settings.CallbackButtons then |
2779 | createGenRecvPCButton(pop, call, remote, spyFunc) |
2780 | end |
2781 | createGenSendPCButton(pop, call, remote, spyFunc) |
2782 | if Settings.MoreRepeatCallOptions then |
2783 | pop:Separator() |
2784 | for _,v in repeatCallSteps do |
2785 | createRepeatCallButton(pop, call, remote, remoteId, spyFunc, v) |
2786 | end |
2787 | else |
2788 | createRepeatCallButton(pop, call, remote, remoteId, spyFunc) |
2789 | end |
2790 | |
2791 | local textFrame = childWindow:Dummy() |
2792 | |
2793 | addSpacer(textFrame, 6) |
2794 | |
2795 | local indentFrame = textFrame:Indent(4):SameLine() |
2796 | |
2797 | local temp = indentFrame:Indent(-2):WithFont(CallerIconFont) -- center it |
2798 | temp:SetStyle(RenderStyleOption.FramePadding, Vector2.new(2, 0)) |
2799 | temp:SetColor(RenderColorOption.Button, black, 0) |
2800 | temp:SetColor(RenderColorOption.ButtonActive, black, 0) |
2801 | temp:SetColor(RenderColorOption.ButtonHovered, black, 0) |
2802 | local btn = temp:Button() |
2803 | |
2804 | if call.FromSynapse then |
2805 | btn.Label = "\xee\x80\x89" |
2806 | else |
2807 | btn.Label = "\xee\x80\x88" |
2808 | end |
2809 | btn.Size = Vector2.new(24, 30) |
2810 | |
2811 | local firstArgFrame = indentFrame:Indent(28):Child() -- 1 extra cause -1 later, and using child so I can make the icon line up |
2812 | firstArgFrame.Size = Vector2.new(width-24-38-23, 30) |
2813 | addSpacer(firstArgFrame, 2) |
2814 | firstArgFrame = firstArgFrame:SameLine() |
2815 | |
2816 | if totalArgCount == 0 or totalArgCount == 1 then |
2817 | local argFrame = firstArgFrame:SameLine() |
2818 | |
2819 | local topLine = argFrame:SameLine():Indent(8) |
2820 | topLine:SetColor(RenderColorOption.Button, black, 0) |
2821 | topLine:SetColor(RenderColorOption.ButtonActive, white, 0) |
2822 | topLine:SetColor(RenderColorOption.ButtonHovered, white, 0) |
2823 | local mainButton = topLine:Button() |
2824 | mainButton.OnUpdated:Connect(function() |
2825 | pop:Show() |
2826 | end) |
2827 | |
2828 | local temp2 = argFrame:SameLine() |
2829 | temp2:SetColor(RenderColorOption.ButtonActive, colorOptions.FrameBg[1], 1) |
2830 | temp2:SetColor(RenderColorOption.ButtonHovered, colorOptions.FrameBg[1], 1) |
2831 | temp2:SetColor(RenderColorOption.Button, colorOptions.FrameBg[1], 1) |
2832 | temp2:SetStyle(RenderStyleOption.ButtonTextAlign, Vector2.new(0, 0.5)) |
2833 | |
2834 | local lineContents = temp2:Indent(-1):Indent(1):Button() |
2835 | lineContents.Size = Vector2.new(width-24-38-23, 24) -- 24 = left padding, 38 = right padding, and no scrollbar |
2836 | if totalArgCount == 0 then |
2837 | argFrame:SetColor(RenderColorOption.Text, colorRGB(156, 0, 0), 1) |
2838 | lineContents.Label = spaces2 .. "nil" |
2839 | elseif call.NonNilArgCount == 1 then |
2840 | local text, color = getArgString(call.Args[1], lineContents.Size.X) |
2841 | local str = resizeText(spaces2 .. text, lineContents.Size.X, "... ", DefaultTextFont) |
2842 | lineContents.Label = str |
2843 | argFrame:SetColor(RenderColorOption.Text, color, 1) |
2844 | else |
2845 | lineContents.Label = spaces2 .. "HIDDEN NIL" |
2846 | argFrame:SetColor(RenderColorOption.Text, colorHSV(258/360, 0.8, 1), 1) |
2847 | end |
2848 | mainButton.Size = Vector2.new(lineContents.Size.X, lineContents.Size.Y+4) |
2849 | |
2850 | local temp = argFrame:SameLine() |
2851 | argFrame:SetColor(RenderColorOption.ButtonActive, black, 0) |
2852 | argFrame:SetColor(RenderColorOption.ButtonHovered, black, 0) |
2853 | argFrame:SetColor(RenderColorOption.Button, black, 0) |
2854 | temp:SetStyle(RenderStyleOption.ButtonTextAlign, Vector2.new(1, 0.5)) |
2855 | temp:SetColor(RenderColorOption.Text, colorHSV(179/360, 0.8, 1), 1) |
2856 | |
2857 | local lineNum = temp:Indent(-7):Button() |
2858 | lineNum.Label = "1" |
2859 | lineNum.Size = Vector2.new(32, 24) |
2860 | else |
2861 | local normalSize = Vector2.new(width-24-38, 24)-- 24 = left padding + indent, 38 = right padding (no scrollbar) |
2862 | local normalFirstSize = Vector2.new(width-24-38-23, 24) |
2863 | local scrollSize = Vector2.new(width-24-38-14, 24) -- 14 = scrollbar width, plus read above |
2864 | local scrollFirstSize = Vector2.new(width-24-38-14-23, 24) |
2865 | for i = 1, call.NonNilArgCount do |
2866 | if i > Settings.ArgLimit then break end |
2867 | |
2868 | local x = call.Args[i] |
2869 | |
2870 | local firstLine = (i == 1) |
2871 | local argFrame = ((firstLine and firstArgFrame) or childWindow):SameLine() |
2872 | |
2873 | local topLine = firstLine and argFrame:SameLine():Indent(-1):Indent(1) or argFrame:SameLine():Indent(8) |
2874 | topLine:SetColor(RenderColorOption.Button, black, 0) |
2875 | topLine:SetColor(RenderColorOption.ButtonActive, white, 0) |
2876 | topLine:SetColor(RenderColorOption.ButtonHovered, white, 0) |
2877 | local mainButton = topLine:Button() |
2878 | mainButton.OnUpdated:Connect(function() |
2879 | pop:Show() |
2880 | end) |
2881 | |
2882 | local temp2 = argFrame:SameLine():Indent(-1):Indent(1) |
2883 | temp2:SetColor(RenderColorOption.ButtonActive, colorOptions.FrameBg[1], 1) |
2884 | temp2:SetColor(RenderColorOption.ButtonHovered, colorOptions.FrameBg[1], 1) |
2885 | temp2:SetColor(RenderColorOption.Button, colorOptions.FrameBg[1], 1) |
2886 | temp2:SetStyle(RenderStyleOption.ButtonTextAlign, Vector2.new(0, 0.5)) |
2887 | |
2888 | local lineContents = firstLine and temp2:Indent(-1):Indent(1):Button() or temp2:Indent(8):Button() |
2889 | if totalArgCount < 10 then |
2890 | lineContents.Size = firstLine and normalFirstSize or normalSize |
2891 | else |
2892 | lineContents.Size = firstLine and scrollFirstSize or scrollSize |
2893 | end |
2894 | if i ~= Settings.ArgLimit then |
2895 | local text, color |
2896 | text, color = getArgString(x, lineContents.Size.X) |
2897 | lineContents.Label = resizeText(spaces2 .. text, lineContents.Size.X, "... ", DefaultTextFont) |
2898 | argFrame:SetColor(RenderColorOption.Text, color, 1) |
2899 | else |
2900 | lineContents.Label = spaces2 .. "ARG LIMIT REACHED" |
2901 | argFrame:SetColor(RenderColorOption.Text, Color3.new(1, 0, 0), 1) |
2902 | end |
2903 | mainButton.Size = Vector2.new(lineContents.Size.X, lineContents.Size.Y+4) -- +4 to add spacer |
2904 | |
2905 | local temp = argFrame:SameLine() |
2906 | argFrame:SetColor(RenderColorOption.ButtonActive, black, 0) |
2907 | argFrame:SetColor(RenderColorOption.ButtonHovered, black, 0) |
2908 | argFrame:SetColor(RenderColorOption.Button, black, 0) |
2909 | temp:SetStyle(RenderStyleOption.ButtonTextAlign, Vector2.new(1, 0.5)) |
2910 | temp:SetColor(RenderColorOption.Text, colorHSV(179/360, 0.8, 1), 1) |
2911 | |
2912 | local lineNum = firstLine and temp:Indent(-7):Button() or temp:Indent(1):Button() |
2913 | lineNum.Label = tostring(i) |
2914 | lineNum.Size = Vector2.new(32, 24) |
2915 | --addSpacer(childWindow, 4) |
2916 | end |
2917 | local argAmt = call.NonNilArgCount |
2918 | for i = 1, call.NilCount do |
2919 | if (i + argAmt) > Settings.ArgLimit then break end |
2920 | |
2921 | local firstLine = (i == 1 and argAmt == 0) |
2922 | local argFrame = (firstLine and firstArgFrame:SameLine()) or childWindow:SameLine() |
2923 | |
2924 | local topLine = argFrame:Dummy():Indent(8) |
2925 | topLine:SetColor(RenderColorOption.Button, black, 0) |
2926 | topLine:SetColor(RenderColorOption.ButtonActive, white, 0) |
2927 | topLine:SetColor(RenderColorOption.ButtonHovered, white, 0) |
2928 | local mainButton = topLine:Button() |
2929 | mainButton.OnUpdated:Connect(function() |
2930 | pop:Show() |
2931 | end) |
2932 | |
2933 | local temp2 = argFrame:SameLine() |
2934 | temp2:SetColor(RenderColorOption.ButtonActive, colorOptions.FrameBg[1], 1) |
2935 | temp2:SetColor(RenderColorOption.ButtonHovered, colorOptions.FrameBg[1], 1) |
2936 | temp2:SetColor(RenderColorOption.Button, colorOptions.FrameBg[1], 1) |
2937 | temp2:SetStyle(RenderStyleOption.ButtonTextAlign, Vector2.new(0, 0.5)) |
2938 | |
2939 | local lineContents = firstLine and temp2:Indent(-1):Indent(1):Button() or temp2:Indent(8):Button() |
2940 | if (i + argAmt) == Settings.ArgLimit then |
2941 | lineContents.Label = spaces2 .. "ARG LIMIT REACHED" |
2942 | argFrame:SetColor(RenderColorOption.Text, Color3.new(1, 0, 0), 1) |
2943 | else |
2944 | lineContents.Label = spaces2 .. "HIDDEN NIL" |
2945 | argFrame:SetColor(RenderColorOption.Text, colorHSV(258/360, 0.8, 1), 1) |
2946 | end |
2947 | if totalArgCount < 10 then |
2948 | lineContents.Size = firstLine and normalFirstSize or normalSize |
2949 | else |
2950 | lineContents.Size = firstLine and scrollFirstSize or scrollSize |
2951 | end |
2952 | if firstLine then |
2953 | mainButton.Size = Vector2.new(lineContents.Size.X, lineContents.Size.Y) -- +2 cause type icon adds 2 for some reason |
2954 | else |
2955 | mainButton.Size = Vector2.new(lineContents.Size.X, lineContents.Size.Y+4) -- +4 to add spacer |
2956 | end |
2957 | |
2958 | local temp = argFrame:SameLine() |
2959 | argFrame:SetColor(RenderColorOption.ButtonActive, black, 0) |
2960 | argFrame:SetColor(RenderColorOption.ButtonHovered, black, 0) |
2961 | argFrame:SetColor(RenderColorOption.Button, black, 0) |
2962 | temp:SetStyle(RenderStyleOption.ButtonTextAlign, Vector2.new(1, 0.5)) |
2963 | temp:SetColor(RenderColorOption.Text, colorHSV(179/360, 0.8, 1), 1) |
2964 | |
2965 | local lineNum = firstLine and temp:Indent(-7):Button() or temp:Indent(1):Button() |
2966 | lineNum.Label = tostring(i + argAmt) |
2967 | lineNum.Size = Vector2.new(32, 24) |
2968 | --addSpacer(childWindow, 4) |
2969 | end |
2970 | addSpacer(childWindow, 4) -- account for the space at the end of the arg list so when you scroll all the way down the padding looks good |
2971 | end |
2972 | |
2973 | addSpacer(tempMainDummy, 4) |
2974 | tableInsert(argLines, { tempMainDummy, pop }) |
2975 | end |
2976 | |
2977 | local function loadRemote(remote: Instance, remoteId: string, data) |
2978 | local funcInfo = spyFunctions[data.TypeIndex] |
2979 | local logs = funcInfo.Type == "Call" and callLogs or otherLogs |
2980 | frontPage.Visible = false |
2981 | remotePage.Visible = true |
2982 | currentSelectedRemote = remoteId |
2983 | currentSelectedRemoteInstance = remote |
2984 | currentSelectedType = funcInfo.Type |
2985 | remotePageObjects.Name.Label = remote and resizeText(purifyString(remoteNameCache[remoteId] or remote.Name, false, remotePageObjects.Name.Size.X), remotePageObjects.Name.Size.X, "... ", DefaultTextFont) or "NIL REMOTE" |
2986 | remotePageObjects.Icon.Label = funcInfo.Icon .. " " |
2987 | remotePageObjects.IgnoreButton.Label = (logs[remoteId].Ignored and "Unignore") or "Ignore" |
2988 | remotePageObjects.IgnoreButtonFrame:SetColor(RenderColorOption.Text, (logs[remoteId].Ignored and green) or red, 1) |
2989 | remotePageObjects.BlockButton.Label = (logs[remoteId].Blocked and "Unblock") or "Block" |
2990 | remotePageObjects.BlockButtonFrame:SetColor(RenderColorOption.Text, (logs[remoteId].Blocked and green) or red, 1) |
2991 | |
2992 | addSpacer(remoteViewerMainWindow, 8) |
2993 | |
2994 | for _,v in logs[remoteId].Calls do |
2995 | makeRemoteViewerLog(v, remote, remoteId) |
2996 | end |
2997 | end |
2998 | |
2999 | -- Below this is rendering Front Page |
3000 | local topBar = frontPage:SameLine() |
3001 | local frameWidth = width-150 |
3002 | local searchBarFrame = topBar:Indent(-0.35*frameWidth):Child() |
3003 | searchBarFrame.Size = Vector2.new(frameWidth, 24) |
3004 | searchBarFrame:SetColor(RenderColorOption.ChildBg, black, 0) |
3005 | searchBar = searchBarFrame:Indent(0.35*frameWidth):TextBox() -- localized earlier |
3006 | searchBar.OnUpdated:Connect(filterLines) |
3007 | |
3008 | local searchButton = topBar:Button() |
3009 | searchButton.Label = "Search" |
3010 | searchButton.OnUpdated:Connect(function() |
3011 | filterLines(searchBar.Value) -- redundant because i did it above but /shrug |
3012 | end) |
3013 | |
3014 | local childWindow |
3015 | |
3016 | local clearAllLogsButton = topBar:Button() |
3017 | clearAllLogsButton.Label = "Clear All Logs" |
3018 | clearAllLogsButton.OnUpdated:Connect(function() |
3019 | tableClear(callLines) |
3020 | tableClear(otherLines) |
3021 | for _,v in callLogs do |
3022 | tableClear(v.Calls) |
3023 | end |
3024 | |
3025 | for _,v in otherLogs do |
3026 | tableClear(v.Calls) |
3027 | end |
3028 | childWindow:Clear() |
3029 | addSpacer(childWindow, 8) |
3030 | end) |
3031 | |
3032 | local topRightBar = topBar:Indent(width-96):SameLine() -- -8 for right padding, -8 for previous left indent, -28 per button +4 for left side padding |
3033 | topRightBar:SetColor(RenderColorOption.Button, black, 0) |
3034 | topRightBar:SetStyle(RenderStyleOption.ButtonTextAlign, Vector2.new(0.5, 0.5)) |
3035 | topRightBar:SetStyle(RenderStyleOption.ItemSpacing, Vector2.new(0, 0)) |
3036 | |
3037 | |
3038 | local settingsButton = topRightBar:Button() |
3039 | settingsButton.Label = "\xef\x80\x93" |
3040 | settingsButton.Size = Vector2.new(24, 24) |
3041 | settingsButton.OnUpdated:Connect(function() |
3042 | settingsWindowWeakReference[1].Visible = not settingsWindowWeakReference[1].Visible |
3043 | end) |
3044 | |
3045 | pauseSpyButton = topRightBar:Indent(28):Button() |
3046 | pauseSpyButton.Label = Settings.Paused and "\xef\x80\x9d" or "\xef\x8a\x8c" |
3047 | pauseSpyButton.Size = Vector2.new(24, 24) |
3048 | pauseSpyButton.OnUpdated:Connect(function() |
3049 | if Settings.Paused then |
3050 | Settings.Paused = false |
3051 | pauseSpyButton.Label = "\xef\x8a\x8c" |
3052 | pauseSpyButton2.Label = "\xef\x8a\x8c" |
3053 | else |
3054 | Settings.Paused = true |
3055 | pauseSpyButton.Label = "\xef\x80\x9d" |
3056 | pauseSpyButton2.Label = "\xef\x80\x9d" |
3057 | end |
3058 | end) |
3059 | |
3060 | local exitButton = topRightBar:Indent(56):Button() |
3061 | exitButton.Label = "\xef\x80\x91" |
3062 | exitButton.Size = Vector2.new(24, 24) |
3063 | exitButton.OnUpdated:Connect(function() |
3064 | if messagebox("Are you sure you want to Close/Disconnect the RemoteSpy? You can reexecute later.", "Warning", 4) == 6 then |
3065 | cleanUpSpy() |
3066 | end |
3067 | end) |
3068 | |
3069 | addSpacer(frontPage, 4) |
3070 | |
3071 | local sameLine = frontPage:SameLine() |
3072 | |
3073 | local splitAmt = (floor(#spyFunctions/2)+1) |
3074 | for i,v in spyFunctions do |
3075 | |
3076 | if i == splitAmt then |
3077 | sameLine = frontPage:SameLine() |
3078 | sameLine.Visible = Settings.CallbackButtons |
3079 | callbackButtonline = sameLine |
3080 | end |
3081 | |
3082 | local tempLine = v.Indent == 0 and sameLine:Dummy() or sameLine:Indent(v.Indent):Dummy() |
3083 | |
3084 | local btn = tempLine:WithFont(RemoteIconFont):CheckBox() |
3085 | btn.Label = v.Icon |
3086 | btn.Value = v.Enabled |
3087 | v.Button = btn |
3088 | btn.OnUpdated:Connect(function(enabled) |
3089 | v.Enabled = enabled |
3090 | Settings[v.Name] = enabled |
3091 | updateLines(v.Name, enabled) |
3092 | |
3093 | saveConfig() |
3094 | end) |
3095 | |
3096 | sameLine:Label(v.Name) |
3097 | end |
3098 | |
3099 | frontPage:SetColor(RenderColorOption.ChildBg, colorOptions.TitleBg[1], 1) |
3100 | frontPage:SetStyle(RenderStyleOption.ChildRounding, 5) |
3101 | |
3102 | childWindow = frontPage:Child() |
3103 | childWindow:SetStyle(RenderStyleOption.ItemSpacing, Vector2.new(4, 0)) |
3104 | childWindow:SetStyle(RenderStyleOption.FrameRounding, 3) |
3105 | addSpacer(childWindow, 8) |
3106 | |
3107 | local function makeCopyPathButton(sameLine: RenderSameLine, remote: Instance) |
3108 | local copyPathButton = sameLine:Button() |
3109 | copyPathButton.Label = "Copy Path" |
3110 | |
3111 | copyPathButton.OnUpdated:Connect(function() |
3112 | local str = getInstancePath(remote) |
3113 | if type(str) == "string" then |
3114 | outputData(str, 1, "", "Copied Path") |
3115 | else |
3116 | pushError("Failed to Copy Path") |
3117 | end |
3118 | end) |
3119 | end |
3120 | |
3121 | local function makeClearLogsButton(sameLine: RenderSameLine, remoteId: string, method) |
3122 | local clearLogsButton = sameLine:Button() |
3123 | clearLogsButton.Label = "Clear Logs" |
3124 | |
3125 | local lines = (method == "Call") and callLines or otherLines |
3126 | local logs = (method == "Call") and callLogs or otherLogs |
3127 | |
3128 | clearLogsButton.OnUpdated:Connect(function() |
3129 | tableClear(logs[remoteId].Calls) |
3130 | lines[remoteId][3].Label = "0" |
3131 | if not logs[remoteId].Ignored then |
3132 | lines[remoteId][2].Visible = false |
3133 | lines[remoteId][4].Visible = false |
3134 | end |
3135 | end) |
3136 | end |
3137 | |
3138 | local function makeIgnoreButton(sameLine: RenderSameLine, remoteId: string, method) |
3139 | local spoofLine = sameLine:SameLine() |
3140 | spoofLine:SetColor(RenderColorOption.Text, red, 1) |
3141 | local ignoreButton = spoofLine:Button() |
3142 | ignoreButton.Label = "Ignore" |
3143 | |
3144 | local logs = (method == "Call") and callLogs or otherLogs |
3145 | local funcList = (method == "Call") and callFuncs or otherFuncs |
3146 | |
3147 | funcList[remoteId].UpdateIgnores = function() |
3148 | if logs[remoteId].Ignored then |
3149 | ignoreButton.Label = "Unignore" |
3150 | spoofLine:SetColor(RenderColorOption.Text, green, 1) |
3151 | else |
3152 | ignoreButton.Label = "Ignore" |
3153 | spoofLine:SetColor(RenderColorOption.Text, red, 1) |
3154 | end |
3155 | end |
3156 | |
3157 | ignoreButton.OnUpdated:Connect(function() |
3158 | if logs[remoteId].Ignored then |
3159 | logs[remoteId].Ignored = false |
3160 | ignoreButton.Label = "Ignore" |
3161 | spoofLine:SetColor(RenderColorOption.Text, red, 1) |
3162 | else |
3163 | logs[remoteId].Ignored = true |
3164 | ignoreButton.Label = "Unignore" |
3165 | spoofLine:SetColor(RenderColorOption.Text, green, 1) |
3166 | end |
3167 | end) |
3168 | end |
3169 | |
3170 | local function makeBlockButton(sameLine: RenderSameLine, remoteId: string, method) |
3171 | local spoofLine = sameLine:SameLine() |
3172 | spoofLine:SetColor(RenderColorOption.Text, red, 1) |
3173 | local blockButton = spoofLine:Button() |
3174 | blockButton.Label = "Block" |
3175 | |
3176 | local logs = (method == "Call") and callLogs or otherLogs |
3177 | local funcList = (method == "Call") and callFuncs or otherFuncs |
3178 | |
3179 | funcList[remoteId].UpdateBlocks = function() |
3180 | if logs[remoteId].Blocked then |
3181 | spoofLine:SetColor(RenderColorOption.Text, green, 1) |
3182 | blockButton.Label = "Unblock" |
3183 | else |
3184 | spoofLine:SetColor(RenderColorOption.Text, red, 1) |
3185 | blockButton.Label = "Block" |
3186 | end |
3187 | end |
3188 | |
3189 | blockButton.OnUpdated:Connect(function() |
3190 | if logs[remoteId].Blocked then |
3191 | logs[remoteId].Blocked = false |
3192 | spoofLine:SetColor(RenderColorOption.Text, red, 1) |
3193 | blockButton.Label = "Block" |
3194 | else |
3195 | logs[remoteId].Blocked = true |
3196 | spoofLine:SetColor(RenderColorOption.Text, green, 1) |
3197 | blockButton.Label = "Unblock" |
3198 | end |
3199 | end) |
3200 | end |
3201 | |
3202 | local function renderNewLog(remote: Instance, remoteId: string, data) |
3203 | local spyFunc = spyFunctions[data.TypeIndex] |
3204 | local method = spyFunc.Type |
3205 | local lines, log, funcList |
3206 | if method == "Call" then |
3207 | lines = callLines |
3208 | log = callLogs[remoteId] |
3209 | funcList = callFuncs |
3210 | else |
3211 | lines = otherLines |
3212 | log = otherLogs[remoteId] |
3213 | funcList = otherFuncs |
3214 | end |
3215 | funcList[remoteId] = {} |
3216 | |
3217 | local temp = childWindow:Dummy():Indent(8) |
3218 | temp:SetStyle(RenderStyleOption.ItemSpacing, Vector2.new(4, 0)) |
3219 | temp:SetColor(RenderColorOption.ChildBg, colorRGB(25, 25, 28), 1) |
3220 | temp:SetStyle(RenderStyleOption.SelectableTextAlign, Vector2.new(0, 0.5)) |
3221 | |
3222 | local line = {} |
3223 | line[1] = spyFunc.Name |
3224 | line[2] = temp:Child() |
3225 | sameButtonLine = line[2] |
3226 | sameButtonLine.Visible = spyFunc.Enabled |
3227 | sameButtonLine.Size = Vector2.new(width-32-14, 32) -- minus 32 because 4x 8px spacers, minus 14 because scrollbar |
3228 | addSpacer(sameButtonLine, 4) |
3229 | sameButtonLine = sameButtonLine:SameLine() |
3230 | |
3231 | local remoteButton = sameButtonLine:Indent(6):Selectable() |
3232 | remoteButton.Size = Vector2.new(width-327-4-14, 24) |
3233 | remoteButton.Label = spaces .. (remote and resizeText(purifyString(remoteNameCache[remoteId] or remote.Name, false, remoteButton.Size.X), remoteButton.Size.X, "... ", DefaultTextFont) or "NIL REMOTE") |
3234 | remoteButton.OnUpdated:Connect(function() |
3235 | loadRemote(remote, remoteId, data) |
3236 | end) |
3237 | |
3238 | addSpacer(sameButtonLine, 3) |
3239 | |
3240 | local cloneLine = sameButtonLine:WithFont(RemoteIconFont):Indent(6) |
3241 | |
3242 | cloneLine:Label(spyFunc.Icon .. " ") |
3243 | |
3244 | local cloneLine2 = sameButtonLine:SameLine() |
3245 | cloneLine2:SetColor(RenderColorOption.Text, colorHSV(179/360, 0.8, 1), 1) |
3246 | |
3247 | local callAmt = #log.Calls |
3248 | local callStr = (callAmt < 1000 and tostring(callAmt)) or "999+" |
3249 | line[3] = cloneLine2:Indent(27):Label(callStr) |
3250 | |
3251 | local ind = sameButtonLine:Indent(width-333) |
3252 | |
3253 | makeCopyPathButton(ind, remote) |
3254 | makeClearLogsButton(sameButtonLine, remoteId, method) |
3255 | makeIgnoreButton(sameButtonLine, remoteId, method) |
3256 | makeBlockButton(sameButtonLine, remoteId, method) |
3257 | |
3258 | line[4] = addSpacer(childWindow, 4) |
3259 | line[4].Visible = spyFunc.Enabled |
3260 | line[5] = remoteButton |
3261 | |
3262 | lines[remoteId] = line |
3263 | filterLines(searchBar.Value) |
3264 | end |
3265 | |
3266 | _G.ChangeRemoteSpyRemoteDisplayName = function(remote: Instance, newName: string) |
3267 | local remoteId = remote:GetDebugId() |
3268 | remoteNameCache[remoteId] = newName |
3269 | |
3270 | local line = callLines[remoteId] |
3271 | local line2 = otherLines[remoteId] |
3272 | if line then |
3273 | line[5].Label = spaces .. resizeText(purifyString(newName, false, line[5].Size.X), line[5].Size.X, "... ", DefaultTextFont) |
3274 | end |
3275 | if line2 then |
3276 | line2[5].Label = spaces .. resizeText(purifyString(newName, false, line2[5].Size.X), line2[5].Size.X, "... ", DefaultTextFont) |
3277 | end |
3278 | end |
3279 | |
3280 | local function sendLog(remote: Instance, remoteId: string, data) |
3281 | local spyFunc = spyFunctions[data.TypeIndex] |
3282 | local method = spyFunc.Type |
3283 | local check = (currentSelectedRemote == remoteId and currentSelectedType == method) and true |
3284 | |
3285 | local line, log |
3286 | if method == "Call" then |
3287 | line = callLines[remoteId] |
3288 | log = callLogs[remoteId] |
3289 | else |
3290 | line = otherLines[remoteId] |
3291 | log = otherLogs[remoteId] |
3292 | end |
3293 | |
3294 | tableInsert(log.Calls, data) |
3295 | |
3296 | if Settings.CacheLimit then |
3297 | local callCount = (#log.Calls-Settings.MaxCallAmount) |
3298 | if callCount > 0 then |
3299 | for _ = 1,callCount do |
3300 | if check then |
3301 | argLines[1][2]:Clear() |
3302 | argLines[1][1]:Clear() |
3303 | argLines[1][1].Visible = false |
3304 | tableRemove(argLines, 1) |
3305 | end |
3306 | tableRemove(log.Calls, 1) |
3307 | end |
3308 | end |
3309 | end |
3310 | |
3311 | if line then |
3312 | local callAmt = #log.Calls |
3313 | if callAmt > 0 and spyFunc.Enabled then |
3314 | line[2].Visible = true |
3315 | line[4].Visible = true |
3316 | end |
3317 | local callStr = (callAmt < 1000 and tostring(callAmt)) or "999+" |
3318 | line[3].Label = callStr |
3319 | else |
3320 | renderNewLog(remote, remoteId, data) |
3321 | end |
3322 | |
3323 | if check then |
3324 | makeRemoteViewerLog(data, remote, remoteId) |
3325 | end |
3326 | end |
3327 | |
3328 | local function processReturnValue(callType: string, refTable, ...) |
3329 | spawnFunc(function(...) |
3330 | local args = deepClone({...}, callType, -1) |
3331 | if args then |
3332 | local lastIdx = getLastIndex(args) |
3333 | refTable.Args = args |
3334 | refTable.NonNilArgCount = lastIdx |
3335 | refTable.NilCount = (select("#", ...) - lastIdx) |
3336 | else |
3337 | refTable.Args = false |
3338 | pushError("Impossible error 1 has occurred, please report to GameGuy#5920") |
3339 | end |
3340 | end, ...) |
3341 | |
3342 | return ... |
3343 | end |
3344 | |
3345 | local function createCallStack(callStack) |
3346 | local newCallStack = {} |
3347 | local callStackLength = #callStack |
3348 | |
3349 | for i,v in callStack do -- last index in call stack is the remotespy hook |
3350 | if i ~= callStackLength then |
3351 | local tempScript = rawget(getfenv(v.func), "script") |
3352 | local funcInfo = getinfo(v.func) |
3353 | newCallStack[i] = { |
3354 | Script = typeof(tempScript) == "Instance" and cloneref(tempScript), |
3355 | LineNumber = funcInfo.currentline, |
3356 | Type = funcInfo.what |
3357 | } |
3358 | end |
3359 | end |
3360 | |
3361 | return newCallStack |
3362 | end |
3363 | |
3364 | local function addCall(remote: Instance, remoteId: string, returnValue, spyFunc, caller: boolean, cs: Instance, callStack, ...) |
3365 | if not remoteBlacklistCache[remote] and spyFunc.Object == "RemoteEvent" then |
3366 | if cansignalreplicate(remote.OnServerEvent) then |
3367 | remoteBlacklistCache[remote] = 1 |
3368 | else |
3369 | remoteBlacklistCache[remote] = 2 |
3370 | return |
3371 | end |
3372 | end |
3373 | |
3374 | if not callLogs[remoteId] then |
3375 | callLogs[remoteId] = { |
3376 | Blocked = false, |
3377 | Ignored = false, |
3378 | Calls = {} |
3379 | } |
3380 | end |
3381 | if not callLogs[remoteId].Ignored and (Settings.LogHiddenRemotesCalls or spyFunc.Enabled) then |
3382 | local args, tableDepth, _, hasInstance = deepClone({...}, remote.ClassName, -1) -- 1 deeper total |
3383 | local argCount = select("#", ...) |
3384 | |
3385 | if not args or argCount > 7995 or (tableDepth > 0 and ((argCount + tableDepth) > 298)) then |
3386 | return |
3387 | end |
3388 | |
3389 | local V2Script = callStack[#callStack-1] and rawget(getfenv(callStack[#callStack-1].func), "script") |
3390 | if typeof(V2Script) ~= "Instance" then V2Script = nil end |
3391 | |
3392 | local lastIdx = getLastIndex(args) |
3393 | |
3394 | local data = { |
3395 | HasInstance = hasInstance or (not remote:IsAncestorOf(game)), |
3396 | TypeIndex = idxs[spyFunc.Name], |
3397 | Script = cs, |
3398 | Args = args, -- 2 deeper total |
3399 | NonNilArgCount = lastIdx, |
3400 | ReturnValue = returnValue, |
3401 | NilCount = (argCount - lastIdx), |
3402 | FromSynapse = caller, |
3403 | ScriptV2 = V2Script, |
3404 | CallStack = Settings.StoreCallStack and createCallStack(callStack) |
3405 | } |
3406 | sendLog(remote, remoteId, data) |
3407 | end |
3408 | end |
3409 | |
3410 | local function addCallback(remote: Instance, method: string, func) |
3411 | local oldIdentity = getThreadIdentity() |
3412 | setThreadIdentity(8) |
3413 | if remoteBlacklistCache[remote] ~= 2 then |
3414 | local remoteId = getDebugId(remote) |
3415 | local remoteType = remote.ClassName--isHookThread() and oldIndex(remote, "ClassName") or remote.ClassName |
3416 | |
3417 | if not otherLogs[remoteId] then |
3418 | otherLogs[remoteId] = { |
3419 | Type = "Callback", |
3420 | CurrentFunction = func, |
3421 | Ignored = false, |
3422 | Blocked = false, |
3423 | Calls = {} |
3424 | } |
3425 | elseif otherLogs[remoteId].CurrentFunction then |
3426 | local curFunc = otherLogs[remoteId].CurrentFunction |
3427 | for i,v in _G.remoteSpyCallbackHooks do |
3428 | if v == curFunc then |
3429 | tableRemove(_G.remoteSpyCallbackHooks, i) |
3430 | break |
3431 | end |
3432 | end |
3433 | restorefunction(curFunc) |
3434 | otherLogs[remoteId].CurrentFunction = func |
3435 | end |
3436 | |
3437 | if func then |
3438 | local oldfunc |
3439 | oldfunc = hookfunction(func, function(...) -- lclosure, so oth.hook not applicable |
3440 | if #getCallStack() == 2 then -- check that the function is actually being called by a cclosure |
3441 | local oldLevel = getThreadIdentity() |
3442 | setThreadIdentity(8) -- fix for people passing coregui as an arg, also it's here because I'm too lazy to implement at the start of every hook. Shouldn't be too dangerous because I restore it afterwards |
3443 | |
3444 | if not Settings.Paused then |
3445 | local spyFunc = spyFunctions[idxs[method]] |
3446 | local args, _, _, hasInstance = deepClone({...}, remoteType, -1) |
3447 | if not args then |
3448 | pushError("Impossible error 2 has occurred, please report to GameGuy#5920") |
3449 | return oldfunc(...) |
3450 | end |
3451 | local argCount = select("#", ...) |
3452 | |
3453 | local callingScript = originalCallerCache[remoteId] or {nil, checkcaller()} |
3454 | |
3455 | originalCallerCache[remoteId] = nil |
3456 | |
3457 | local scr = getcallingscript() |
3458 | if scr then scr = cloneref(scr) end |
3459 | |
3460 | local lastIdx = getLastIndex(args) |
3461 | |
3462 | local data = { |
3463 | HasInstance = hasInstance or (not remote:IsAncestorOf(game)), |
3464 | TypeIndex = idxs[method], |
3465 | CallbackScript = scr, |
3466 | Script = callingScript[1], |
3467 | Args = args, -- 2 deeper total |
3468 | NonNilArgCount = lastIdx, |
3469 | CallbackLog = otherLogs[remoteId], |
3470 | NilCount = (argCount - lastIdx), |
3471 | FromSynapse = callingScript[2] |
3472 | } |
3473 | |
3474 | if spyFunc.ReturnsValue and not otherLogs[remoteId].Blocked then |
3475 | local returnValue = {} |
3476 | spawnFunc(function() |
3477 | if not otherLogs[remoteId].Ignored and (Settings.LogHiddenRemotesCalls or spyFunc.Enabled) then |
3478 | data.ReturnValue = returnValue |
3479 | sendLog(remote, remoteId, data) |
3480 | end |
3481 | end) |
3482 | |
3483 | setThreadIdentity(oldLevel) |
3484 | return processReturnValue(remoteType, returnValue, oldfunc(...)) |
3485 | end |
3486 | |
3487 | |
3488 | if not otherLogs[remoteId].Ignored and (Settings.LogHiddenRemotesCalls or spyFunc.Enabled) then |
3489 | sendLog(remote, remoteId, data) |
3490 | end |
3491 | end |
3492 | |
3493 | setThreadIdentity(oldLevel) |
3494 | if otherLogs[remoteId] and otherLogs[remoteId].Blocked then |
3495 | return |
3496 | end |
3497 | end |
3498 | |
3499 | return oldfunc(...) |
3500 | end) |
3501 | tableInsert(_G.remoteSpyCallbackHooks, func) |
3502 | end |
3503 | end |
3504 | setThreadIdentity(oldIdentity) |
3505 | end |
3506 | |
3507 | local function addConnection(remote: Instance, signalType: string, signal: RBXScriptSignal) |
3508 | local oldIdentity = getThreadIdentity() |
3509 | setThreadIdentity(8) |
3510 | if remoteBlacklistCache[remote] ~= 2 then |
3511 | local remoteId = getDebugId(remote) |
3512 | local remoteType = remote.ClassName--isHookThread() and oldIndex(remote, "ClassName") or remote.ClassName |
3513 | |
3514 | if not otherLogs[remoteId] then |
3515 | otherLogs[remoteId] = { |
3516 | Type = "Connection", |
3517 | Ignored = false, |
3518 | Blocked = false, |
3519 | Calls = {} |
3520 | } |
3521 | |
3522 | local scriptCache = setmetatable({}, {__mode = "k"}) |
3523 | local connectionCache = {} -- unused (for now) |
3524 | hooksignal(signal, function(info, ...) |
3525 | if not Settings.Paused then |
3526 | spawnFunc(function(...) |
3527 | local original = getThreadIdentity() |
3528 | setThreadIdentity(8) -- not sure why hooksignal threads aren't level 8, but I restore this later anyways, just to be safe |
3529 | local spyFunc = spyFunctions[idxs[signalType]] |
3530 | if not otherLogs[remoteId].Ignored and (Settings.LogHiddenRemotesCalls or spyFunc.Enabled) then |
3531 | if info.Index == 0 then |
3532 | tableClear(connectionCache) |
3533 | tableClear(scriptCache) |
3534 | end |
3535 | |
3536 | tableInsert(connectionCache, info.Connection) |
3537 | local CS = issynapsethread(coroutine.running()) and "Synapse" or getcallingscript() |
3538 | if CS then |
3539 | if scriptCache[CS] then |
3540 | scriptCache[CS] += 1 |
3541 | else |
3542 | scriptCache[CS] = 1 |
3543 | end |
3544 | end |
3545 | |
3546 | local callingScript = originalCallerCache[remoteId] or {nil, false} |
3547 | |
3548 | originalCallerCache[remoteId] = nil |
3549 | |
3550 | if info.Index == (#getconnections(signal)-1) then -- -1 because base 0 for info.Index |
3551 | local args, _, _, hasInstance = deepClone({...}, remoteType, -1) |
3552 | if not args then |
3553 | pushError("Impossible error 3 has occurred, please report to GameGuy#5920") |
3554 | return true, ... |
3555 | end |
3556 | local argCount = select("#", ...) |
3557 | local lastIdx = getLastIndex(args) |
3558 | local data = { |
3559 | HasInstance = hasInstance or (not remote:IsAncestorOf(game)), |
3560 | TypeIndex = idxs[signalType], |
3561 | Script = callingScript[1], |
3562 | Scripts = scriptCache, |
3563 | Connections = connectionCache, |
3564 | Signal = signal, |
3565 | Args = args, -- 2 deeper total |
3566 | NonNilArgCount = lastIdx, |
3567 | NilCount = (argCount - lastIdx), |
3568 | FromSynapse = callingScript[2] |
3569 | } |
3570 | |
3571 | sendLog(remote, remoteId, data) |
3572 | end |
3573 | end |
3574 | setThreadIdentity(original) |
3575 | end, ...) |
3576 | end |
3577 | |
3578 | if otherLogs[remoteId].Blocked then |
3579 | return false |
3580 | end |
3581 | return true, ... |
3582 | end) |
3583 | tableInsert(_G.remoteSpySignalHooks, signal) |
3584 | end |
3585 | end |
3586 | setThreadIdentity(oldIdentity) |
3587 | end |
3588 | |
3589 | local namecallFilters = {} |
3590 | local newIndexFilters = {} |
3591 | local indexFilters = {} |
3592 | |
3593 | do -- filter setup |
3594 | for _,v in spyFunctions do |
3595 | if v.Type == "Callback" then |
3596 | tableInsert(newIndexFilters, AllFilter.new({ |
3597 | InstanceTypeFilter.new(1, v.Object), |
3598 | AnyFilter.new({ |
3599 | ArgumentFilter.new(2, v.Callback), |
3600 | ArgumentFilter.new(2, v.DeprecatedCallback) |
3601 | }), |
3602 | TypeFilter.new(3, "function") |
3603 | })) |
3604 | elseif v.Type == "Call" then |
3605 | tableInsert(namecallFilters, AllFilter.new({ |
3606 | InstanceTypeFilter.new(1, v.Object), |
3607 | AnyFilter.new({ |
3608 | NamecallFilter.new(v.Method), |
3609 | NamecallFilter.new(v.DeprecatedMethod) |
3610 | }) |
3611 | })) |
3612 | elseif v.Type == "Connection" then |
3613 | tableInsert(indexFilters, AllFilter.new({ |
3614 | InstanceTypeFilter.new(1, v.Object), |
3615 | AnyFilter.new({ |
3616 | ArgumentFilter.new(2, v.Connection), |
3617 | ArgumentFilter.new(2, v.DeprecatedConnection) |
3618 | }) |
3619 | })) |
3620 | end |
3621 | end |
3622 | end |
3623 | -- need to pass an arg telling addCallback/addConnection that the call came from a hook thread, which will use oldIndex, as opposed to being called from the getweakdescendants iteration, where oldIndex will throw an error |
3624 | oldNewIndex = newHookMetamethod(game, "__newindex", function(remote, idx, newidx) |
3625 | spawnFunc(addCallback, cloneref(remote), idx, newidx) |
3626 | |
3627 | return oldNewIndex(remote, idx, newidx) |
3628 | end, AnyFilter.new(newIndexFilters)) |
3629 | _G.remoteSpyHooks.NewIndex = oldNewIndex |
3630 | |
3631 | oldIndex = newHookMetamethod(game, "__index", function(remote, idx) |
3632 | local newSignal = oldIndex(remote, idx) |
3633 | spawnFunc(addConnection, cloneref(remote), idx, newSignal) |
3634 | |
3635 | return newSignal |
3636 | end, AnyFilter.new(indexFilters)) |
3637 | _G.remoteSpyHooks.Index = oldIndex |
3638 | |
3639 | local oldNewInstance |
3640 | oldNewInstance = filteredOth(Instance.new, function(instanceType: string, ...) |
3641 | local newInstance = oldNewInstance(instanceType, ...) |
3642 | remoteBlacklistCache[newInstance] = 2 |
3643 | |
3644 | return newInstance |
3645 | end, AllFilter.new({ |
3646 | AnyFilter.new({ |
3647 | ArgumentFilter.new(1, "RemoteEvent"), |
3648 | ArgumentFilter.new(1, "RemoteFunction") |
3649 | }), |
3650 | AnyFilter.new({ |
3651 | TypeFilter.new(2, "Instance"), |
3652 | ArgCountFilter.new(1) |
3653 | }) |
3654 | })) |
3655 | |
3656 | local oldClone |
3657 | oldClone = filteredOth(Instance.new, function(original: Instance, ...) |
3658 | local newInstance = oldClone(original, ...) |
3659 | remoteBlacklistCache[newInstance] = 2 |
3660 | |
3661 | return newInstance |
3662 | end, AllFilter.new({ |
3663 | AnyFilter.new({ |
3664 | InstanceTypeFilter.new(1, "RemoteEvent"), |
3665 | InstanceTypeFilter.new(1, "RemoteFunction") |
3666 | }) |
3667 | })) |
3668 | |
3669 | table.insert(namecallFilters, AllFilter.new({ -- setup :Clone filter |
3670 | AnyFilter.new({ |
3671 | NamecallFilter.new("Clone"), |
3672 | NamecallFilter.new("clone") |
3673 | }), |
3674 | AnyFilter.new({ |
3675 | InstanceTypeFilter.new(1, "RemoteEvent"), |
3676 | InstanceTypeFilter.new(1, "RemoteFunction") |
3677 | }) |
3678 | })) |
3679 | |
3680 | local initInfo = { |
3681 | RemoteFunction = { "Callback", "OnClientInvoke" }, |
3682 | BindableFunction = { "Callback", "OnInvoke" }, |
3683 | RemoteEvent = { "Connection", "OnClientEvent" }, |
3684 | BindableEvent = { "Connection", "Event" } |
3685 | } |
3686 | |
3687 | do -- init OnClientInvoke and signal index |
3688 | for _,v in getweakdescendants(game) do |
3689 | local data = initInfo[v.ClassName] |
3690 | if data then |
3691 | if data[1] == "Connection" then |
3692 | local _ = v[data[2]] -- calls the OTH of __index which adds the connection |
3693 | elseif data[1] == "Callback" then |
3694 | local func = getcallbackmember(v, data[2]) |
3695 | if func then |
3696 | addCallback(cloneref(v), data[2], func) |
3697 | end |
3698 | end |
3699 | end |
3700 | end |
3701 | |
3702 | for _,v in getnilinstances() do |
3703 | local data = initInfo[v.ClassName] |
3704 | if data then |
3705 | if data[1] == "Connection" then |
3706 | local _ = v[data[2]] -- calls the OTH of __index which adds the connection |
3707 | elseif data[1] == "Callback" then |
3708 | local func = getcallbackmember(v, data[2]) |
3709 | if func then |
3710 | addCallback(cloneref(v), data[2], func) |
3711 | end |
3712 | end |
3713 | end |
3714 | end |
3715 | end |
3716 | |
3717 | do -- namecall and function hooks |
3718 | local oldNamecall |
3719 | oldNamecall = newHookMetamethod(game, "__namecall", newcclosure(function(remote, ...) |
3720 | setThreadIdentity(8) -- oth isn't stock at 8 for some reason |
3721 | |
3722 | local nmcMethod = getnamecallmethod() |
3723 | if nmcMethod == "Clone" or nmcMethod == "clone" then -- faster than string.lower |
3724 | local newInstance: Instance = oldNamecall(remote, ...) |
3725 | if newInstance then |
3726 | remoteBlacklistCache[newInstance] = 2 |
3727 | end |
3728 | |
3729 | return newInstance |
3730 | end |
3731 | |
3732 | if remoteBlacklistCache[remote] ~= 2 then |
3733 | local remoteId = getDebugId(remote) |
3734 | if not Settings.Paused and select("#", ...) < 7996 then |
3735 | local scr = getcallingscript() |
3736 | if scr then scr = cloneref(scr) end |
3737 | |
3738 | local spyFunc = spyFunctions[idxs[nmcMethod]] |
3739 | if spyFunc.Type == "Call" and spyFunc.FiresLocally then |
3740 | local caller = checkcaller() |
3741 | originalCallerCache[remoteId] = originalCallerCache[remoteId] or {(not caller and scr), caller} |
3742 | end |
3743 | -- it will either return true at checkcaller because called from synapse (non remspy), or have already been set by remspy |
3744 | |
3745 | if spyFunc.ReturnsValue and (not callLogs[remoteId] or not callLogs[remoteId].Blocked) then |
3746 | local returnValue = {} |
3747 | spawnFunc(addCall, cloneref(remote), remoteId, returnValue, spyFunc, checkcaller(), scr, getCallStack(getOriginalThread()), ...) |
3748 | |
3749 | return processReturnValue(spyFunc.Object, returnValue, oldNamecall(remote, ...)) -- getproperties(remote).ClassName is not performant at all, but using oldIndex breaks stuff |
3750 | end |
3751 | spawnFunc(addCall, cloneref(remote), remoteId, nil, spyFunc, checkcaller(), scr, getCallStack(getOriginalThread()), ...) |
3752 | end |
3753 | |
3754 | if callLogs[remoteId] and callLogs[remoteId].Blocked then return end |
3755 | end |
3756 | |
3757 | return oldNamecall(remote, ...) |
3758 | end), AnyFilter.new(namecallFilters)) |
3759 | _G.remoteSpyHooks.Namecall = oldNamecall |
3760 | |
3761 | for _,v in spyFunctions do |
3762 | if v.Type == "Call" then |
3763 | local oldFunc |
3764 | local newfunction = function(remote, ...) |
3765 | setThreadIdentity(8) -- oth isn't stock at 8 for some reason |
3766 | if remoteBlacklistCache[remote] ~= 2 then |
3767 | local remoteId = getDebugId(remote) |
3768 | |
3769 | if not Settings.Paused and select("#", ...) < 7996 then |
3770 | local scr = getcallingscript() |
3771 | if scr then scr = cloneref(scr) end |
3772 | |
3773 | if v.Type == "Call" and v.FiresLocally then |
3774 | local caller = checkcaller() |
3775 | originalCallerCache[remoteId] = originalCallerCache[remoteId] or {(not caller and scr), caller} |
3776 | end |
3777 | |
3778 | if v.ReturnsValue and (not callLogs[remoteId] or not callLogs[remoteId].Blocked) then |
3779 | local returnValue = {} |
3780 | spawnFunc(addCall, cloneref(remote), remoteId, returnValue, v, checkcaller(), scr, getCallStack(getOriginalThread()), ...) |
3781 | |
3782 | return processReturnValue(v.Object, returnValue, oldFunc(remote, ...)) |
3783 | end |
3784 | spawnFunc(addCall, cloneref(remote), remoteId, nil, v, checkcaller(), scr, getCallStack(getOriginalThread()), ...) |
3785 | end |
3786 | |
3787 | if callLogs[remoteId] and callLogs[remoteId].Blocked then return end |
3788 | end |
3789 | |
3790 | return oldFunc(remote, ...) |
3791 | end |
3792 | |
3793 | local originalFunc = Instance.new(v.Object)[v.Method] |
3794 | oldFunc = filteredOth(originalFunc, newcclosure(newfunction), InstanceTypeFilter.new(1, v.Object)) |
3795 | |
3796 | v.Function = originalFunc |
3797 | _G.remoteSpyHooks[v.Method] = oldFunc |
3798 | end |
3799 | end |
3800 | end |
3801 | |
3802 | -- CREDIT TO https://github.com/Upbolt/Hydroxide/ FOR INSPIRATION AND A FEW FORKED TOSTRING FUNCTIONS |