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
- Inject rSpy before doing anything
- Clear the log so it's empty
- Do the action once — use an ability, buy an item, attack an enemy
- Read the log — find the remote that fired exactly once
- Note the name and arguments
- 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 see | What it is | How to reproduce |
|---|---|---|
nil | No arguments | remote:FireServer() |
"string" | String literal | remote:FireServer("string") |
42 | Number | remote:FireServer(42) |
true / false | Boolean | remote:FireServer(true) |
Part (workspace.Map.Tree) | Instance reference | Get the instance with FindFirstChild etc. |
{...} | Table | Reconstruct 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
| Type | Direction | How it works |
|---|---|---|
RemoteEvent:FireServer(...) | Client → Server, no reply | Fire and forget |
RemoteFunction:InvokeServer(...) | Client → Server, waits for return | Synchronous — yields until server responds |
RemoteEvent.OnClientEvent | Server → Client | Incoming 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.