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

SUNC Script Inspection

Every running Roblox script has bytecode in memory, a compiled closure, and (often) a known content hash. SUNC exposes all three through a small family of inspection functions that let you discover and interact with code you didn't write — from server scripts to local handlers in the player's GUI.

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

getscriptbytecode

Returns the raw Luau bytecode for a given script.

luau
getscriptbytecode(script: LuaSourceContainer): string
Returnsstring
The Luau bytecode as a binary string. Not human-readable directly, but disassembleable with the right tools.
local local_script = game.StarterPlayer.StarterPlayerScripts.Combat
local bytecode = getscriptbytecode(local_script)
print(#bytecode, "bytes")
-- Typically a few KB for a small script.

The most common use isn't to read the bytecode directly (it's opaque without a disassembler), but to hash it for identity checks or to spot when a script has changed since you last looked. crypt.hash(bytecode, "sha256") gives you a stable identifier that ignores whitespace, comments, and other source-level noise.

getscriptclosure

Returns the compiled function value of a script — the actual callable closure the engine produced from the script's source.

luau
getscriptclosure(script: LuaSourceContainer): function
Returnsfunction
A function that, when called, runs the script's top-level chunk again. Useful when you want to re-run a script with a different environment, or to extract its return value (for ModuleScripts).
local script = game.ReplicatedStorage.MyModule
local closure = getscriptclosure(script)
local fresh = closure()
-- 'fresh' is a new instance of whatever MyModule returns,
-- separate from any cached require() result.

The main use case is re-instantiating ModuleScripts. Roblox caches the return value of a module's firstrequire forever; getscriptclosure lets you bypass that cache to get a fresh copy when you need one. This is invaluable for live-debugging — change the module's source, re-fetch the closure, run it, and see new behaviour without restarting the place.

getscripthash

Returns a content hash of a script. Useful as a stable identifier across sessions.

luau
getscripthash(script: LuaSourceContainer): string
Returnsstring
A hex-encoded hash of the script's bytecode. Two scripts with identical source produce identical hashes; any source change produces a different one.
local hash = getscripthash(game.ServerScriptService.Combat)
print(hash)  --> "3a8f9c..."

Most executors implement this as SHA-256 over the bytecode, which gives stable cross-session identity even if the script's in-memory pointer address changes. Practical use: caching results of expensive script analysis, keyed by hash.

getcallingscript

Returns the script that called the current function. The rare reflection primitive that introspects the call stack rather than a passed argument.

luau
getcallingscript(): LuaSourceContainer?
ReturnsLuaSourceContainer | nil
The script that called into the function this was called from. Nil at the top level (no parent script).
local original
original = hookfunction(workspace.FindFirstChild, function(self, ...)
    local caller = getcallingscript()
    if caller then
        print("FindFirstChild called from", caller:GetFullName())
    end
    return original(self, ...)
end)

Combined with a hook, getcallingscript tells you which game-side script is doing what. That's the quickest way to figure out who's firing a particular RemoteEvent, or which script is constantly calling a property setter.

A worked example: dump every server script

Putting the inspection primitives together: a small script that enumerates every currently-running script alongside its hash and size. Useful for getting a feel for what code is actually live in a game.

luau
local function dumpScripts()
    for _, s in getrunningscripts() do
        local ok, bytecode = pcall(getscriptbytecode, s)
        if ok then
            local hash = getscripthash and getscripthash(s) or "?"
            print(("%-30s  %5d bytes  %s"):format(
                s:GetFullName():sub(1, 30),
                #bytecode,
                hash:sub(1, 12)
            ))
        end
    end
end

dumpScripts()

On a typical game this prints dozens of lines — server scripts in ServerScriptService, LocalScripts in StarterPlayerScripts, ModuleScripts throughout. Each row tells you exactly where the script is and how big it is.

A worked example: re-run a module without the cache

More advanced: take a ModuleScript that's been required, get a fresh copy by going through the closure rather than require, and mutate the fresh copy independently.

luau
local moduleScript
for _, m in getloadedmodules() do
    if m.Name == "Config" then moduleScript = m end
end

if moduleScript then
    -- Standard cached require:
    local cached = require(moduleScript)

    -- Re-run the chunk to get a brand new return value:
    local fresh = getscriptclosure(moduleScript)()

    print(cached == fresh)  --> false (different tables)
    fresh.tweakedValue = "hello"
    -- cached.tweakedValue is still nil; we have a private copy.
end

The two values share nothing — they were produced by two separate executions of the module chunk. This is the mechanism behind "mod a game without changing what other scripts see."

Caveats

  • getscriptbytecode on a script you don't have permission to inspect (e.g., a server script you're looking at from a client) may return an empty string or error, depending on the executor.
  • getscriptclosure returns the closure of thechunk, not the script's top-level scope. Calling it twice produces two independent runs; values assigned to upvalues in the first don't exist in the second.
  • getcallingscript only works from inside a function called from another script. Calling it at the top level of your own chunk just returns your own script.
  • Bytecode is not stable across Roblox versions. A hash captured today may not match a hash captured next week even if the source is unchanged, because the compiler might have changed.

Wrap-up

Script inspection is the SUNC corner that turns a Roblox game from a black box into a partially-readable system. You can find every script, hash it for identity, peek at its bytecode, re-run it from scratch, and trace which script is calling which function at runtime. None of these are flashy on their own, but together they're the difference between "guessing what a game does" and "knowing."

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.