SignalSpy
Key RequiredUniversal

SignalSpy

Universal Scriptgoodcurrygoodcurry 7.2K views 240 executesUpdated Jul 18, 2025
3.8/ 5(4 votes)
3175% Positive
Lua Script•131 lines (3730 chars)
--please give credits if you use in projects -> discord: cool.guy.69
local settings = {
	copytoclipboard = true,
	copyasscript = true,
	logfireserver = true,
	loginvokeserver = true,
	logonclientevent = false, --half working
	logonclientinvoke = true,
	logindexing = true,
}

print("[signalspy] loaded")

local safetostring = function(v)
	local t = typeof(v)
	if t == "string" then
		return '"' .. v:gsub('"', '\\"') .. '"'
	elseif t == "number" or t == "boolean" or t == "nil" then
		return tostring(v)
	elseif t == "Instance" then
		return v:GetFullName()
	elseif t == "EnumItem" then
		return tostring(v)
	elseif t == "table" then
		local parts = {}
		for k, val in pairs(v) do
			local kt = type(k) == "string" and '"' .. k:gsub('"', '\\"') .. '"' or tostring(k)
			local vt = safetostring(val)
			table.insert(parts, "[" .. kt .. "]=" .. vt)
		end
		return "{" .. table.concat(parts, ",") .. "}"
	elseif t == "vector" then
		return string.format("Vector3.new(%g, %g, %g)", v.X, v.Y, v.Z)
	else
		return "[" .. t .. "]"
	end
end

local function makescript(remote, method, args)
	local goodcurry = remote:GetFullName() .. ":" .. method
	if #args == 0 then
		return goodcurry .. "()"
	end
	local arguwu = {}
	for _, v in ipairs(args) do
		table.insert(arguwu, safetostring(v))
	end
	return goodcurry .. "(" .. table.concat(arguwu, ", ") .. ")"
end

local function tts(tbl)
	local str = ""
	for i, v in ipairs(tbl) do
		str = str .. "arg[" .. i .. "] = " .. safetostring(v) .. "\n"
	end
	return str
end

local function isvalidremote(self)
	return typeof(self) == "Instance" and (self:IsA("RemoteEvent") or self:IsA("RemoteFunction"))
end

local function log(where, remote, method, ...)
	local args = {...}
	local out = "[" .. where .. "] " .. remote:GetFullName() .. ":" .. method .. "\n" .. tts(args)
	print(out)
	if settings.copytoclipboard and setclipboard then setclipboard(out) end
	if settings.copyasscript and setclipboard then
		local script = makescript(remote, method, args)
		setclipboard(script)
	end
end

local namecallorg
namecallorg = hookmetamethod(game, "__namecall", newcclosure(function(self, ...)
	if oth and oth.is_hook_thread and oth.is_hook_thread() then
		return namecallorg(self, ...)
	end
	if not isvalidremote(self) then
		return namecallorg(self, ...)
	end
	local method = getnamecallmethod()
	if method == "FireServer" and settings.logfireserver then
		log("client -> server", self, method, ...)
	elseif method == "InvokeServer" and settings.loginvokeserver then
		log("client -> server", self, method, ...)
	end
	return namecallorg(self, ...)
end))

local indexorg
indexorg = hookmetamethod(game, "__index", newcclosure(function(self, key)
	if oth and oth.is_hook_thread and oth.is_hook_thread() then
		return indexorg(self, key)
	end
	if not isvalidremote(self) then
		return indexorg(self, key)
	end
	if settings.logindexing and (key == "FireServer" or key == "InvokeServer") then
		print("[signalspy] accessed -> " .. self:GetFullName() .. "." .. key)
	end
	if key == "OnClientInvoke" and settings.logonclientinvoke and self:IsA("RemoteFunction") then
		return function(...)
			log("server -> client", self, "OnClientInvoke", ...)
		end
	end
	return indexorg(self, key)
end))

if settings.logonclientevent then
	for _, r in ipairs(game:GetDescendants()) do
		if r:IsA("RemoteEvent") then
			pcall(function()
				r.OnClientEvent:Connect(function(...)
					log("server -> client", r, "OnClientEvent", ...)
				end)
			end)
		end
	end
	game.DescendantAdded:Connect(function(obj)
		if obj:IsA("RemoteEvent") then
			task.wait(0.1)
			pcall(function()
				obj.OnClientEvent:Connect(function(...)
					log("server -> client", obj, "OnClientEvent", ...)
				end)
			end)
		end
	end)
end
--faded was here

Features & Information

signalspy is a cool clientside logger by me ofc that hooks into metamethods to log remote calls and whatever blah blah changelogs - did what the admin guy told me to do 09/06/2026 this thing is unstable, so wouldnt be surprised if bugs.
#simplespy#remotespy#signalspy

Community Comments (10)

goodcurrygoodcurryJul 18, 2025

vxsty pls no sue

melonmelonJul 18, 2025

ewwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww, never cook again

zxcvzxcvJul 18, 2025

Ohio spy

goodcurrygoodcurryJul 18, 2025

u guys so rudeee

depsodepsoadminJul 18, 2025

Anything but Sigma Spy 💔

goodcurrygoodcurryJul 18, 2025

💔

PlaguePlagueAug 4, 2025

ANYTHING but sigma spy :broken_heart:

BaselineBaselineadminSep 6, 2026(edited)

this got re-reviewed. using tostring on arguments can lead to environment escapes (and does in this case). you should also check the type of self since that also leads to an environment escape. look into oth.hook since thats a thing now. it's on the volt docs and in multiple execs and avoids many common detection vectors

Replying to @Baseline
goodcurrygoodcurrySep 6, 2026

@Sprocket haven't updated in a while, but will do what u told me to do thanks

Replying to @goodcurry
BaselineBaselineadminSep 6, 2026(edited)

@goodcurry I believe the right syntax for it would be like oth.hook(getrawmetatable(game).__namecall, function(...) do stuff end) also tables can have arbitrary keys, so doing tostring(k) leads to detections and env escapes if the game does something like this local a = setmetatable({}, {__concat = function()print("detected")end, __tostring = function() print("detected") return "meow" end}) local tab = {[a] = "hello"}

More Scripts for Universal Scripts