atherhubather.hub
Back to Guides
SUNC
10 min read
May 11, 2026

SUNC Closure Internals

Almost every defensive hook in Roblox scripting is built on three primitives: newcclosure, iscclosure (and its sibling islclosure), and checkcaller. They look unrelated at first glance, but they all answer the same question — "what kind of function is this, and who called it?" — which is the question every detection routine eventually asks.

Ather
Ather
Lead developer at Atherhub. Writes about Roblox internals, Luau, script engineering, and platform security.Last updated May 11, 2026

C closures vs Lua closures

Luau has two function flavours. C closures are functions implemented in C and registered with the VM as callable values — print, type, table.insert, and every method on a Roblox instance. Lua closures are functions you wrote in script. They have different memory layouts, different calling conventions, and the VM can tell them apart cheaply.

Two SUNC functions surface that distinction directly:

luau
iscclosure(fn: function): boolean
islclosure(fn: function): boolean
Returnsboolean
True if the function is the matching closure type. They're mutually exclusive — a function is exactly one of the two.
print(iscclosure(print))           --> true
print(islclosure(function() end))  --> true

Detection routines lean on this constantly. "Is game.HttpGet still a C closure?" is the quickest way to tell whether someone has replaced it with a Lua hook.

newcclosure: wearing a C closure disguise

newcclosure takes a Lua function and returns a wrapper that looks like a C closure to inspection. The wrapper itself is a real C closure registered with the VM; when called, it dispatches to your original Lua function. Outside the wrapper everything sees a C closure; inside it, your Lua code runs normally.

luau
newcclosure(fn: (...any) -> ...any): (...any) -> ...any
Returns(...any) -> ...any
A wrapper closure that proxies to fn but reports as a C closure to iscclosure / islclosure / debug.info.
local hidden = function(...) return ... end
local wrapped = newcclosure(hidden)

print(iscclosure(hidden))   --> false  (still a Lua closure)
print(iscclosure(wrapped))  --> true   (now looks like C)
print(wrapped(1, 2, 3))     --> 1  2  3
When to wrap
Wrap when your hook is replacing a C closure that game code can observe (almost any built-in). Skip the wrap when your hook is private to your own script and never seen by anyone else.

The mechanics of newcclosure

Under the hood, newcclosure creates a fresh C closure with a single upvalue: the Lua function you handed it. When the C closure is called, it pushes its arguments onto the stack, invokes the upvalue, and returns whatever the upvalue returned. From the perspective of any inspection that asks "what type of closure is this?", the answer is C.

The thing this doesn't hide is the upvalue itself. Tools that walk a function's upvalue list — likedebug.getupvalues on the wrapper — can see the inner Lua closure and inspect it further. That's a deeper detection path that most games don't take, but it exists, and it's why "wrap with newcclosure" is necessary but not always sufficient.

checkcaller: who called this?

The third primitive is the one that quietly makes anti-detection tractable.

luau
checkcaller(): boolean
Returnsboolean
True when the call originated from a thread the executor controls (your script). False when the call originated from a game thread (a Roblox script, a connection callback the engine fired, or an internal C path).
if checkcaller() then
    -- We're the ones running this; safe to do executor-only things
else
    -- Game code called us; pass through transparently
end

This is the lever that lets a hook be invisible to game probes. Detection routines run from game threads, so checkcaller() returns false during them. Your hook delegates to the original. The detection sees the original behaviour. Pass.

Conversely, when you call the hooked function from your own script, checkcaller() is true and your hook can do whatever modification it was built for. Game probes and your real usage take different code paths through the same function.

The canonical hook idiom

Combining all three primitives, the textbook robust hook looks like this:

luau
local original
original = hookfunction(target, newcclosure(function(...)
    if checkcaller() then
        return doMyThing(original, ...)
    end
    return original(...)
end))
  1. hookfunction replaces target in place.
  2. newcclosure wraps the hook so the replaced function still reports as a C closure.
  3. checkcaller guards the "do my thing" path so game-originated calls pass through untouched.
  4. Delegating to the captured original means probes that check argument shape, return values, or error messages still see Roblox's real behaviour.

clonefunction: hand out copies, not references

One more closure primitive worth knowing. clonefunction returns a copy of a function — same implementation, different identity. Useful when you want to give game code a function that "looks like" the original but isn't the same reference, so identity checks on your side don't conflict with theirs.

luau
clonefunction(fn: function): function
Returnsfunction
A new function value with the same implementation. fn ~= clonefunction(fn) — equality is by reference.
local copy = clonefunction(print)
print(copy == print)  --> false
copy("hello")          --> hello

A common use case: you saved an original function pre-hook, and you want to expose the original behaviour from your script without revealing that you have the reference. Handing out clonefunction(original) gives callers something they can use without being able to compare it to the "real" original they might have stashed elsewhere.

Common mistakes

  • Wrapping twice. Calling newcclosure(newcclosure(fn)) stacks two wrappers, each adding one level of indirection. Harmless in tests, ugly in production. The outer wrapper isn't necessary if the inner one already covers the case.
  • Forgetting checkcaller in hot paths. A __namecall hook that doesn't early-exit on checkcaller runs your logic on every method call in the game, including the engine's own. The performance hit is immediate, and the detection surface grows massively.
  • Trusting iscclosure on unknown functions. iscclosure(unknown) tells you what the closure type currently is. A game might have wrapped its own function in newcclosure for legitimate reasons.iscclosure alone isn't a tampering signal — change in identity over time is.

Wrap-up

The three primitives are small but they cover most of what a robust hook needs to be invisible. The mental model: closures are two-flavoured, newcclosure changes the visible flavour, and checkcaller tells you whose code is running right now. Wire those into a hook idiomatically and the detection routines covered in our namecall detections article all pass automatically.

Ather
Written by Ather

Ather is the lead developer behind Atherhub. He's been writing Luau and Roblox tooling for the better part of a decade, with a focus on the messy interface between game-script internals and the platforms that host them. Have feedback on this article? Drop it in the Discord.