Skip to main content

Reading Game Scripts

The game's own client code is your best documentation. LocalScripts tell you exactly which remotes are used, what events the game listens to, what values mean, and sometimes reveal the entire game logic. Learning to read scripts accelerates everything else.

Where Scripts Live

On the client, scripts are typically in:

Players.LocalPlayer
└── PlayerScripts ← client scripts the game gave you
├── GameClient ← main game logic (often a LocalScript)
└── Modules ← ModuleScripts with shared logic

Players.LocalPlayer.PlayerGui
└── [GuiName]
└── LocalScript ← UI logic scripts

Workspace
└── [Model]
└── LocalScript ← scripts on specific parts (rare)

Reading LocalScript Source

-- Get source of a LocalScript
local script = game.Players.LocalPlayer.PlayerScripts:FindFirstChild("GameClient")
if script then
print(script.Source)
end

In Dex, click the script and look for .Source in the properties panel.

note

In newer games with Byfron (Roblox's anti-cheat), .Source may be empty or stripped. If so, you'll need a bytecode decompiler that supports the current Roblox version.

Requiring ModuleScripts

ModuleScripts can be executed with require():

local config = require(game.ReplicatedStorage:WaitForChild("GameConfig"))
-- Now you have whatever the module returned
print(config.MaxHealth, config.BaseSpeed)

This only works if the ModuleScript is in a location accessible to LocalScripts. If the module has already been loaded by the game, require() returns the cached result immediately.

What to Look For

When reading a script, scan for these patterns:

Remote References

-- The game declares remotes like this:
local AttackRemote = ReplicatedStorage:WaitForChild("AttackRemote")
local ShopRemote = ReplicatedStorage.Remotes:WaitForChild("Shop")

This tells you exactly where remotes are and what they're called.

FireServer Calls

-- Find all the places the game fires remotes:
AttackRemote:FireServer(target, damage, "sword")
ShopRemote:FireServer("buy", itemId, quantity)

Read the function that calls :FireServer() — the arguments before it are what you need to reproduce.

Anti-Cheat Hooks

Look for functions that check things about the player:

-- Games that do sanity checks often look like:
if humanoid.WalkSpeed > 20 then
-- kick or flag
end

This tells you what limits you can't exceed without triggering the check.

Value References

-- Games read state from values:
local isRaging = char:WaitForChild("RageActive")
if isRaging.Value then
-- do rage behavior
end

Now you know RageActive is a BoolValue on the character you can watch or modify.

Finding What Fires a Remote

If you know the remote name but want to understand when and why it fires, search the script for that name:

-- Search all script sources for a remote name
local function findInScripts(searchTerm)
for _, v in game:GetDescendants() do
if v:IsA("LocalScript") or v:IsA("ModuleScript") then
local src = v.Source
if src and src:find(searchTerm, 1, true) then
print(v:GetFullName())
-- Optional: find the line
for line in src:gmatch("[^\n]+") do
if line:find(searchTerm, 1, true) then
print(" " .. line:match("^%s*(.-)%s*$"))
end
end
end
end
end
end

findInScripts("AttackRemote")

Decompiling Bytecode

When .Source is empty, the script has been compiled to bytecode. You can still get the bytecode and decompile it:

local bytecode = getscriptbytecode(someScript)
-- Then pass bytecode to a decompiler tool
-- Most modern executors include a built-in decompiler

Executors like Synapse X, KRNL, and others include decompiler UIs where you can paste or select a script and get back readable Lua. The output won't be clean (no variable names, no comments) but the logic is preserved.

Reading Module Return Values at Runtime

Instead of reading source, you can intercept what a module returns when required:

-- Hook require to see what each module returns
local oldRequire = require
require = function(module)
local result = oldRequire(module)
if typeof(result) == "table" then
print("Module", module.Name, "returned table with keys:")
for k in pairs(result) do
print(" ", k)
end
end
return result
end

This is especially useful when you want to explore a module's API without reading all its source.

Practical Example: Finding an Ability System

Suppose you want to find how abilities work in a game. Here's the full process:

  1. Open rSpy, use an ability, see [FireServer] → AbilityRemote "dash", 1
  2. Search script source for "AbilityRemote" — find the LocalScript that references it
  3. Read that script — find the function that calls AbilityRemote:FireServer
  4. Understand the argument format"dash" is the ability name, 1 might be a level
  5. Write your script to fire it directly:
local abilityRemote = game.ReplicatedStorage:WaitForChild("AbilityRemote")

-- Map keybinds to abilities
local abilities = {
[Enum.KeyCode.Q] = "dash",
[Enum.KeyCode.E] = "attack",
[Enum.KeyCode.R] = "ultimate",
}

game:GetService("UserInputService").InputBegan:Connect(function(input, processed)
if processed then return end
local ability = abilities[input.KeyCode]
if ability then
abilityRemote:FireServer(ability, 1)
end
end)

Reading the game's scripts turned a guessing game into a precise implementation.