Skip to main content

Capturing Remotes with rSpy

rSpy is the single most useful tool for understanding what a game does. It intercepts every RemoteEvent and RemoteFunction call in real time and logs them with their arguments. Within 5 minutes of using it, you'll understand how most of the game's systems work.

Getting rSpy

rSpy is available in most executor hubs. Inject it before playing — it hooks into the remote system at startup so it catches everything from the beginning.

What rSpy Shows

After injection, a window (or console output) shows lines like:

[FireServer] → AttackRemote nil
[FireServer] → EquipWeapon "Sword", 3
[InvokeServer] → GetStats
[OnClientEvent] ← HealthSync 100, 80
[FireServer] → AnimationRemote "run_forward"
[FireServer] → AnimationRemote "run_forward"
[FireServer] → AnimationRemote "idle"
[FireServer] → AttackRemote nil
[InvokeServer] → ShopAction "buy", 7832, 1

Each line shows:

  • Direction: = you → server (FireServer/InvokeServer), = server → you (OnClientEvent/OnClientInvoke)
  • Remote name: what to use in WaitForChild
  • Arguments: the values sent with the call

The Capture Workflow

  1. Inject rSpy before doing anything
  2. Clear the log so it's empty
  3. Do the action once — use an ability, buy an item, attack an enemy
  4. Read the log — find the remote that fired exactly once
  5. Note the name and arguments
  6. Wire it into your script

This is how you find every remote in any game. The process is always the same.

Reading Arguments

Arguments appear after the remote name, separated by commas. Common types:

What you seeWhat it isHow to reproduce
nilNo argumentsremote:FireServer()
"string"String literalremote:FireServer("string")
42Numberremote:FireServer(42)
true / falseBooleanremote:FireServer(true)
Part (workspace.Map.Tree)Instance referenceGet the instance with FindFirstChild etc.
{...}TableReconstruct the table manually

When the argument is an Instance, you need to find that instance in the game tree to pass it. Often it's the HumanoidRootPart of a target, or a specific part in workspace.

-- rSpy showed: [FireServer] → AttackRemote Part (workspace.Enemies.Goblin.HumanoidRootPart)
-- So you pass the target's HRP:
local target = workspace.Enemies:FindFirstChild("Goblin")
local targetHRP = target and target:FindFirstChild("HumanoidRootPart")
if targetHRP then
attackRemote:FireServer(targetHRP)
end

RemoteEvent vs RemoteFunction

TypeDirectionHow it works
RemoteEvent:FireServer(...)Client → Server, no replyFire and forget
RemoteFunction:InvokeServer(...)Client → Server, waits for returnSynchronous — yields until server responds
RemoteEvent.OnClientEventServer → ClientIncoming notification

In rSpy, [InvokeServer] entries are RemoteFunctions. They often return a value:

-- If rSpy shows an InvokeServer, it returns something
local result = shopRemote:InvokeServer("buy", itemId, 1)
print("Server returned:", result)
-- result might be: true (success), false (not enough money), or a table of data

Filtering the Noise

Games fire dozens of remotes per second — animations, position sync, chat. To find what you want, look for:

  • Remotes that fire exactly once when you do the specific action
  • Descriptive names: Attack, UseAbility, Purchase, EquipItem, Jump, Interact
  • Skip these (usually noise): anything named Animate, CharacterSync, Replicate, HeartbeatSync, Ping

If the log is too cluttered, clear it and do only the specific action you're investigating.

Replay Attacks

The most basic technique: capture a remote call, then fire it yourself whenever you want.

-- rSpy captured: [FireServer] → RageAbility nil
-- Now fire it yourself on keypress:
local RS = game.ReplicatedStorage
local rageRemote = RS:WaitForChild("RageAbility")

game:GetService("UserInputService").InputBegan:Connect(function(input, processed)
if processed then return end
if input.KeyCode == Enum.KeyCode.R then
rageRemote:FireServer()
end
end)

This works for any no-cooldown remote. Remotes with server-side cooldowns will be ignored if fired too fast.

Argument Variations

Some remotes use the same name but vary behavior based on the first argument (an "action" pattern):

[FireServer] → GameAction "attack", targetId
[FireServer] → GameAction "block"
[FireServer] → GameAction "roll", directionVector
[FireServer] → GameAction "useItem", "potion"

All of these go to one GameAction remote. Fire the one you want:

gameAction:FireServer("attack", enemyId)
gameAction:FireServer("useItem", "potion")

Finding the Remote Object

Once you know the name from rSpy, get the actual remote object. Use WaitForChild with a timeout so your script doesn't hang forever if the remote doesn't exist in this game:

local RS = game:GetService("ReplicatedStorage")

-- Check common locations
local function findRemote(name)
-- Check ReplicatedStorage directly
local r = RS:FindFirstChild(name, true) -- true = recursive search
if r then return r end
-- Check workspace
r = workspace:FindFirstChild(name, true)
if r then return r end
return nil
end

local attackRemote = findRemote("AttackRemote")

Incoming Server Events

rSpy also catches OnClientEvent — messages the server sends to you:

[OnClientEvent] ← DamageEvent 75, "fire"
[OnClientEvent] ← GameState "boss_phase_2"
[OnClientEvent] ← EnemyDied "Goblin1"

You can listen to these too:

local damageEvent = RS:WaitForChild("DamageEvent")
damageEvent.OnClientEvent:Connect(function(amount, damageType)
print("Took", amount, "damage of type", damageType)
end)

This is useful for reacting to server state changes — enemy deaths, boss phases, game events.