Remote Spy
Every FireServer and InvokeServer, with arguments decoded and grouped by remote.
Remote Spy captures every call the game makes through a RemoteEvent or
RemoteFunction, with its arguments, its caller and a timestamp.
Turning it on#
Capture starts off, and a fresh injection resets it to off. Press Capture OFF in the toolbar to turn it on. Rows start arriving immediately.
What a row gives you#
| Column | What it tells you |
|---|---|
| Count | How many times this remote has fired this session |
| Type | Remote, Function, Event or Namecall |
| Method | FireServer, InvokeServer, OnClientEvent, or the method name |
| Path | ReplicatedStorage.Remotes.BuyItem |
| Last caller | The script that fired it |
| Last args | Decoded and typed, not a stringified blob |
Calls are grouped by path:method, so a remote fired two hundred times is one row
with a count. The last twenty calls are kept, and the Log tab in the row's
popup shows them so you can see what varies between them.
Columns sort and resize. The Type chips filter to one kind: Remotes
(FireServer), Functions (InvokeServer), Events (incoming OnClientEvent /
OnClientInvoke), Bindables (BindableEvent:Fire / BindableFunction:Invoke)
and Namecalls (everything else, with All Namecalls on). Roblox's own engine
calls (PlayerModule, CameraModule, CoreGui and the like) are never listed.
Rows read from network packets carry a small antenna and show packet as their
caller.
Inbound remotes#
OnClientEvent and OnClientInvoke traffic, meaning what the server sends you,
is captured by the same capture toggle and shows up under the Events chip. It is
often where the interesting data is.
Refiring a remote#
Click a row, edit the arguments (they are prefilled with the last call), and
fire. InvokeServer replies are shown underneath.
Refires are fired by index, not by namecall
A refire calls remote[method](remote, ...) rather than remote:FireServer(...),
so it never re-enters the capture hook. The row you see is logged explicitly and
marked ▶ you (fired).
Inspecting the caller#
The row popup's Caller tab dumps the closure that fired the remote: its source location, upvalues, constants and nested functions. Scalar upvalues and constants are editable there, and the edit hits the live closure. Remote does the same for every handler connected to the remote on the client.
Blocking, hiding, removing#
Hover a row for three actions:
- Block drops the call before it leaves the client. It matches on the path as
a substring and only stops
FireServerandInvokeServer. - Hide moves the row out of Active.
- Remove deletes the captured aggregate. A new fire re-adds it.
The Hidden and Disabled tabs list what you have hidden and blocked. Both lists persist per game.
Capture via packets#
Packets reads remote calls out of the game's network packets instead of
hooking Luau functions. With it on, every FireServer and InvokeServer that
leaves the client and every OnClientEvent and OnClientInvoke that arrives is
decoded from Roblox's replication packets (0x83), however a script made the
call, and no Luau function is hooked for it.
- What you lose: a packet does not say which script sent it, so these rows
show
packetas the caller, and the caller view has nothing to open. - Returns: an
InvokeServerreply is matched to its request by invoke id. - Needs: an executor with
raknetsend and receive hooks. Without them the button is unavailable and capture carries on through Luau hooks. - First use: the client downloads the decoder from rbx.lol (source) and keeps it for the rest of the game.
- Experimental. It asks once before turning on. If the hooks or the download fail, the toolbar says "packets unavailable" and capture falls back to Luau hooks.
All namecalls#
All Namecalls widens capture from remotes to every obj:Method() call in the
game. Turning it on turns capture on with it.
This mode is expensive
Every namecall means thousands per frame. Calls are queued and flushed on a timer rather than sent inline, and it is still the most expensive thing here. Turn it on to answer a specific question, then turn it off.