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#

ColumnWhat it tells you
CountHow many times this remote has fired this session
TypeRemote, Function, Event or Namecall
MethodFireServer, InvokeServer, OnClientEvent, or the method name
PathReplicatedStorage.Remotes.BuyItem
Last callerThe script that fired it
Last argsDecoded 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 FireServer and InvokeServer.
  • 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 packet as the caller, and the caller view has nothing to open.
  • Returns: an InvokeServer reply is matched to its request by invoke id.
  • Needs: an executor with raknet send 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.