VINDICTA
Hooking · Function
Bunni.fun
ChocoSploit
Cryptic
Potassium
Seliware
SirHurt
Solara
Velocity
Volcano
Volt
Wave
Xeno

hookfunction

Redirects a function's entry point to a custom hook.

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

Synopsis

How it works

hookfunction performs a deep swap at the Luau VMLuau VMThe Luau Virtual Machine — a register-based bytecode interpreter. Executes compiled Luau instructions sequentially, managing a value stack, call stack, and upvalue chains. Roblox runs one VM per Actor context. level. For Luau closures (LClosure), it swaps the internal Proto* pointer and upvalueUpvalueA variable captured from an enclosing scope. When a function references a local variable from an outer function, that variable becomes an upvalue — stored in an UpVal struct that persists even after the outer function returns. array between the target and a clone, so all existing references to the target now execute the hook's bytecodeBytecodeThe compiled binary representation of Luau source code. A sequence of 32-bit instructions (opcodes + operands) stored in a Proto's code[] array. Executed by the Luau VM interpreter.. For C closures, it swaps the lua_CFunction pointer. The original implementation is returned as a "trampolineTrampolineA wrapper function that bounces (redirects) calls to another function. In executor context, newcclosure creates a C closure trampoline that calls the Luau function stored in its first upvalue.".

VM internals — LClosure swap

An LClosure in the Luau VMLuau VMThe Luau Virtual Machine — a register-based bytecode interpreter. Executes compiled Luau instructions sequentially, managing a value stack, call stack, and upvalue chains. Roblox runs one VM per Actor context. is laid out as:
typedef struct LClosure {
  ClosureHeader;      // GCObject header + isC=0 + nupvalues + env
  Proto* p;           // pointer to compiled bytecode
  UpVal* uprefs[];    // captured variable pointers (flexible array)
} LClosure;
hookfunction(target, hook):
  1. Clones the target's LClosure (allocating a new GC object with identical p and uprefs) — this becomes the trampolineTrampolineA wrapper function that bounces (redirects) calls to another function. In executor context, newcclosure creates a C closure trampoline that calls the Luau function stored in its first upvalue.
  2. Overwrites the target's p pointer with the hook's Proto*
  3. Copies the hook's uprefs[] array into the target's slot
  4. Returns the clone (trampoline)
Because all external references (tables, upvaluesUpvalueA variable captured from an enclosing scope. When a function references a local variable from an outer function, that variable becomes an upvalue — stored in an UpVal struct that persists even after the outer function returns., the GC) still point to the same target LClosure object, they now transparently execute the hook's bytecode.

VM internals — CClosure swap

A CClosure is simpler:
typedef struct CClosure {
  ClosureHeader;      // GCObject header + isC=1 + nupvalues + env
  lua_CFunction f;    // native function pointer
  TValue upvals[];    // C-side upvalues
} CClosure;
For C closures, hookfunction swaps just the f pointer and upvalueUpvalueA variable captured from an enclosing scope. When a function references a local variable from an outer function, that variable becomes an upvalue — stored in an UpVal struct that persists even after the outer function returns. array. The trampolineTrampolineA wrapper function that bounces (redirects) calls to another function. In executor context, newcclosure creates a C closure trampoline that calls the Luau function stored in its first upvalue. gets the original f pointer.

Trampoline pattern

The returned trampolineTrampolineA wrapper function that bounces (redirects) calls to another function. In executor context, newcclosure creates a C closure trampoline that calls the Luau function stored in its first upvalue. is critical for calling the original logic without recursion:
  • Correct: local old = hookfunction(target, hook); old(...)
  • Wrong: hookfunction(target, function(...) target(...) end) — infinite recursion because target now IS the hook
The trampoline is a distinct Closure object in the GC, so it is safe from further hooks on the original target.

Usage

Intercept print
local originalPrint = hookfunction(print, function(...)
  "cc">-- Do something before
  return originalPrint("[Intercepted]", ...)
end)

print("Hello") "cc">-- Output: "[Intercepted] Hello"

Parameters

target function
The function whose execution is to be redirected.
hook function
The replacement function that will be called instead.

Returns

function A trampoline to the original implementation of the target function.
Trampoline Usage
Always use the returned trampoline (not the original symbol) to call the original function inside your hook. Calling the original symbol will re-trigger your hook.