Closures

Hook, wrap, clone and inspect functions.

9 functionssUNC 100%Lynx 0.0.0

Functions are the unit of behavior in Luau, and these are the tools for changing it. Hooking intercepts a function everywhere it's called. The rest help your hooks behave exactly like the functions they replace.

Hooking

hookfunction

since 1.0.0
hookfunction(target: function, hook: function): function

Replaces target with hook and returns a new function that calls the original.

The replacement happens in place: every existing reference to target, including references held by game scripts, now runs hook.

Also available as replaceclosure.

Parameters

targetfunction
The function to hook. Luau and C functions are both supported.
hookfunction
The function to run instead. When target is a C function, wrap hook in newcclosure.

Returns

function

A function that calls the original, unhooked target.

Example

local function greet(name)
return "Hello, " .. name
end
local original
original = hookfunction(greet, function(name)
return original(name):upper()
end)
print(greet("lynx")) --> HELLO, LYNX
print(original("lynx")) --> Hello, lynx

hookmetamethod

since 1.0.0
hookmetamethod(object: any, method: string, hook: function): function

Hooks the metamethod method on object's metatable and returns the original.

Every Instance shares one metatable, so hooking __namecall or __index on game affects every Instance in the game.

Parameters

objectany
Any value with a metatable. Usually game.
methodstring
The metamethod, such as "__namecall", "__index" or "__newindex".
hookfunction
Receives the same arguments the metamethod would.

Returns

function

The original metamethod.

Example

-- Log every RemoteEvent fired by game scripts.
local original
original = hookmetamethod(game, "__namecall", newcclosure(function(self, ...)
if getnamecallmethod() == "FireServer" and not checkcaller() then
print("FireServer:", self:GetFullName(), ...)
end
return original(self, ...)
end))

newcclosure

since 1.0.0
newcclosure(fn: function): function

Wraps a Luau function in a C closure. The wrapper behaves exactly like fn, but reports itself as a C function to iscclosure, debug.info and stack traces. It can yield.

Parameters

fnfunction
The Luau function to wrap.

Returns

function

A C closure that calls fn.

Example

local add = newcclosure(function(a, b)
return a + b
end)
print(add(2, 3)) --> 5
print(iscclosure(add)) --> true
print(debug.info(add, "s")) --> [C]

Inspection

iscclosure

since 1.0.0
iscclosure(fn: function): boolean

Returns whether fn is a C closure: a Roblox or Lynx built-in, or a function wrapped with newcclosure.

Parameters

fnfunction
The function to check.

Returns

boolean

true for C closures.

Example

print(iscclosure(print)) --> true
print(iscclosure(function() end)) --> false
print(iscclosure(newcclosure(function() end))) --> true

islclosure

since 1.0.0
islclosure(fn: function): boolean

Returns whether fn is a Luau closure. The exact opposite of iscclosure.

Parameters

fnfunction
The function to check.

Returns

boolean

true for Luau closures.

Example

print(islclosure(function() end)) --> true
print(islclosure(print)) --> false

isexecutorclosure

since 1.2.0
isexecutorclosure(fn: function): boolean

Returns whether fn belongs to Lynx: defined in a script you executed, or a Lynx built-in. Functions from game scripts and Roblox built-ins return false.

Also available as checkclosure and isourclosure.

Parameters

fnfunction
The function to check.

Returns

boolean

true for Lynx functions.

Example

print(isexecutorclosure(function() end)) --> true
print(isexecutorclosure(readfile)) --> true
print(isexecutorclosure(print)) --> false

checkcaller

since 1.0.0
checkcaller(): boolean

Returns true when the current function was called from a Lynx thread, and false when a game script called it. Inside a hook, this is how you tell your own calls from the game's.

Returns

boolean

true when called from code you executed.

Example

-- Count how often game scripts read Workspace.CurrentCamera.
local reads = 0
local original
original = hookmetamethod(game, "__index", newcclosure(function(self, key)
if key == "CurrentCamera" and not checkcaller() then
reads += 1
end
return original(self, key)
end))
task.wait(5)
print("CurrentCamera was read", reads, "times in 5 seconds")

getcallingscript

since 1.1.0
getcallingscript(): BaseScript?

Returns the script that called the current function, or nil when the caller is Lynx. Use it in hooks to see which game script triggered a call.

Returns

BaseScript?

The calling script.

Example

local original
original = hookmetamethod(game, "__namecall", newcclosure(function(self, ...)
if getnamecallmethod() == "FireServer" and not checkcaller() then
local caller = getcallingscript()
local name = if caller then caller:GetFullName() else "unknown"
print(self.Name, "fired by", name)
end
return original(self, ...)
end))

Copying

clonefunction

since 1.1.0
clonefunction(fn: function): function

Returns a copy of fn with the same environment, upvalues and behavior. Hooks applied to fn later don't affect the copy, which makes it a reliable way to keep a clean reference.

Parameters

fnfunction
The function to copy.

Returns

function

An independent copy of fn.

Example

local cleanPrint = clonefunction(print)
hookfunction(print, newcclosure(function(...)
return cleanPrint("[hooked]", ...)
end))
print("hi") --> [hooked] hi
cleanPrint("hi") --> hi