World Executor · Roblox VM

Guide: Hooking

Replacing a function with your own, keeping the original callable, and doing it without leaving obvious traces.

The idea

Hooking swaps the implementation of a function for one of yours, handing you back the original so you can still call it. It is the foundation of almost everything interesting: watching remote calls, blocking an anti-cheat check, changing what a game function returns.

Three tools cover almost every case. hookfunction replaces a plain function. hookmetamethod replaces an entry in an object's metatable — which is how you intercept __namecall, the single most useful hook in Roblox. oth.hook is the low-level path for C closures when you need the original beneath multiple layers.

Replacing a function

hookfunction takes the target and your replacement, and returns the original. Keep that return value — without it you have no way to call through.

A simple hook
local original
original = hookfunction(workspace.FindPartOnRay, function(self, ray, ignore, terrain, water)
    -- Do your own thing, then fall through to the real one.
    print("FindPartOnRay from", getcallingscript())
    return original(self, ray, ignore, terrain, water)
end)

Always call through unless you mean not to

A hook that silently returns nil instead of calling the original will break the game in ways that are hard to trace back to you. Return original(...) unless blocking the call is the whole point.

Hooking __namecall

Roblox compiles object:Method(args) into a single __namecall invocation rather than an index followed by a call. That means one hook on __namecall sees *every* method call on every instance — including FireServer and InvokeServer.

Watching every remote call
local Players = game:GetService("Players")
local old

old = hookmetamethod(game, "__namecall", newcclosure(function(self, ...)
    local method = getnamecallmethod()

    if not checkcaller() and (method == "FireServer" or method == "InvokeServer") then
        print(("%s:%s(%s)"):format(self:GetFullName(), method, table.concat({...}, ", ")))
    end

    return old(self, ...)
end))
  • getnamecallmethod() tells you which method was called — it is only meaningful inside the hook.
  • checkcaller() is false when the call came from the game rather than from you, which is how you avoid logging your own traffic.
  • newcclosure wraps your Luau function so it looks like a C closure to anything inspecting it. Without it, a game that checks iscclosure(getrawmetatable(game).__namecall) sees the hook.

Staying out of sight

A game that is looking will check a handful of things. Each has a countermeasure in this API.

CheckCountermeasure
iscclosure on the hookWrap the replacement in newcclosure so it reports as a C closure.
debug.getinfo source/namedebug.setinfo rewrites the metadata the game reads back.
Stack walking / tracebacksetstackhidden keeps your frames out of debug.traceback output.
isfunctionhookedThere is no hiding this one — it is World's own introspection. Games cannot call it.
Comparing against a stored referenceHook early, before the game captures its own copy of the function.
Two lines that cover most checks
local hook = newcclosure(function(...) return old(...) end)
setstackhidden(true)   -- keep executor frames out of tracebacks

Undoing a hook

restorefunction puts a hooked function back the way it was. Do this when your script unloads — a hook that outlives the script that installed it is a good way to break the next one.

Restoring
local original = hookfunction(target, replacement)

-- ... later, when tearing down
restorefunction(target)

isfunctionhooked tells you whether a function currently carries a hook, which is worth checking before installing a second one on top.

Related reference

The full entry list lives in Closures (hookfunction, newcclosure, restorefunction, setstackhidden), Metatable (hookmetamethod, getnamecallmethod, getrawmetatable) and OTH (oth.hook, oth.get_root_callback).