Getting Started
Macros
Soteria exposes a set of compile-time macros that let you query and control the runtime environment directly from your Luau scripts. Each macro either expands (its call site is replaced with generated source) or annotates (it records an instruction for a later pass and unwraps to its inner value). After the build, the macro identifier never survives in the output.
Get Started
Before writing any macro logic, paste this snippet at the top of your script. It stubs out the SOTR macro functions so your code runs normally in an unobfuscated environment.
SOTR_OBFUSCATED
Type: boolean
Returns true if the current script is obfuscated, and false otherwise. Useful for toggling debug behavior or hiding sensitive logic paths in plain builds.
Aliases: LPH_OBFUSCATED, MV_OBFUSCATED, WYNF_OBFUSCATED
if SOTR_OBFUSCATED then
print("Obfuscated by Soteria")
endSOTR_LINE
Type: () → number
Expands to the current source line number at the point of use. Helpful for logging, error reporting, and runtime diagnostics without relying on debug.traceback.
Aliases: LPH_LINE (variable), MV_LINE (variable), WYNF_LINE (function)
local function assert_eq(a, b)
if a ~= b then
error("Assertion failed at line " .. SOTR_LINE() .. ": expected " .. tostring(b) .. ", got " .. tostring(a))
end
end
assert_eq(1 + 1, 2)SOTR_KILL
Type: () → never
Immediately crashes the Lua VM. Use this as a hard tripwire, placing it behind anti-tamper checks, license validation, or integrity guards ensures the process cannot continue if the condition is violated.
Aliases: SOTR_CRASH, LPH_CRASH, MV_CRASH, WYNF_CRASH
local expired = true
if expired then
SOTR_KILL()
endSOTR_GUID
Type: string
Expands to a random hex string generated once at obfuscation time. Every obfuscation run produces a different value, but all uses of SOTR_GUID within the same run expand to the same string. Useful for unique script instance identification or anti-leak fingerprinting.
local scriptId = SOTR_GUID
print("Script ID:", scriptId) --> e.g. "817d3b823e8f867"SOTR_TIMESTAMP
Type: number
Expands to the Unix timestamp of when the script was obfuscated. Useful for expiry logic, build tracking, or logging when a particular build was generated.
local obfuscatedAt = SOTR_TIMESTAMP
local ageInDays = (os.time() - obfuscatedAt) / 86400
print(string.format("This script was obfuscated %.1f days ago", ageInDays))SOTR_VERSION
Type: string
Expands to the current Soteria obfuscator version string at the time of obfuscation. Useful for debugging, watermarking, or asserting a minimum obfuscator version at runtime.
print("Protected with Soteria " .. SOTR_VERSION)SOTR_TARGET
Type: string
Expands to the target the script was obfuscated for, such as Luau, Roblox, or Studio. Useful for branching behavior that only makes sense on a specific runtime.
if SOTR_TARGET == "Roblox" then
print("Running on Roblox")
endSOTR_ENC_STR
Type: (string, key?, runtimeKey?) → string
Rewrites a string literal into self-decrypting source, so the plaintext never appears in the compiled output. An optional static key (a build-time literal) folds in an extra XOR layer; an optional runtime key is instead XORed at runtime, so the output only decrypts when your runtime key provider returns the same value. A runtime key requires a static key to encrypt against.
Aliases: LPH_ENCSTR, MV_ENC_STR, WYNF_ENC_STRING
local pw = SOTR_ENC_STR("hunter2") -- no key: pure obfuscation
local pw2 = SOTR_ENC_STR("hunter2", 123) -- integer key baked in
local pw3 = SOTR_ENC_STR("hunter2", "MYKEY") -- string key baked in
local pw4 = SOTR_ENC_STR("hunter2", "MYKEY", getKey()) -- only decrypts if getKey() == "MYKEY"SOTR_ENC_NUM
Type: (number, key?, runtimeKey?) → number
Rewrites a numeric literal so it is rebuilt at runtime via rotate and double-XOR steps instead of appearing as a plain constant. Takes the same optional static/runtime key layers as SOTR_ENC_STR. Useful for obscuring license codes, version checks, or magic constants.
Aliases: LPH_ENCNUM, WYNF_ENC_NUM
local licenseCode = SOTR_ENC_NUM(209715200)
local n = SOTR_ENC_NUM(1337, "MYKEY")
if code == licenseCode then
print("License valid")
endSOTR_ENC_FUNC
Type: (function, a, b) → function
Splices an equality guard into the start of the function body: if a ~= b then return end. The two values are evaluated wherever the function is called, so the body only proceeds when they match, otherwise the call silently returns early.
Aliases: LPH_ENCFUNC, MV_ENC_FUNC, WYNF_ENC_FUNC
local secureHandler = SOTR_ENC_FUNC(function(eventType, payload, ...)
print(eventType, payload, ...)
end, getKey(), "expected-key")
-- runs normally when getKey() == "expected-key", otherwise returns immediately
secureHandler("AUTH_EVENT", "user_login", 42)SOTR_EXPOSE
Type: (function) → function
Wraps a function to exclude it from virtualization entirely (no-virtualize), keeping the body native and readable. Use this for hot paths where virtualization overhead is unacceptable and the logic inside has nothing worth hiding.
Aliases: SOTR_NO_VIRTUALIZE, LPH_NO_VIRTUALIZE, MV_OMIT_VM, WYNF_NO_VIRTUALIZE
local multiply = SOTR_EXPOSE(function(a, b, size)
local result = {}
for i = 1, size do
result[i] = {}
for j = 1, size do
local sum = 0
for k = 1, size do
sum = sum + a[i][k] * b[k][j]
end
result[i][j] = sum
end
end
return result
end)
local out = multiply(matA, matB, 64)SOTR_SECURE_CALL
Type: (function) → function
Splices a stack-root guard into the function body. If the closure is lifted out of its original call chain and replayed from a foreign stack root (a common technique for hooking or re-firing callbacks), the guard trips and the call bails.
Aliases: WYNF_SECURE_CALL
local secureHandler = SOTR_SECURE_CALL(function(eventType, payload, ...)
print(eventType, payload, ...)
end)
-- Invoke normally, bails if replayed from outside its original stack root
secureHandler("AUTH_EVENT", "user_login", 42)SOTR_ENC_BUF
Type: (buffer | string, key?, runtimeKey?) → buffer
Encrypts buffer bytes (or a string, wrapped into a buffer) at compile-time and rewraps them at runtime. The raw byte contents are never present in the compiled output. Takes the same optional static/runtime key layers as SOTR_ENC_STR.
Aliases: LPH_ENCBUF
local payload = SOTR_ENC_BUF(buffer.fromstring("secret-payload"))
print(buffer.tostring(payload)) --> "secret-payload" (decrypted at runtime)SOTR_ENC_UDIM
Type: (UDim | UDim2, key?) → UDim | UDim2
Encrypts each numeric component of a UDim/UDim2 constructor at compile-time, keeping the constructor call itself intact. Useful for hiding layout values that would otherwise leak information about a GUI's structure to someone reading the compiled output.
local size = SOTR_ENC_UDIM(UDim2.new(0.5, 0, 0.5, 0))
frame.Size = sizeSOTR_ENC_VEC
Type: (Vector2 | Vector3 | vector, key?) → Vector2 | Vector3 | vector
Encrypts each numeric component of a Vector2/Vector3/vector constructor at compile-time, keeping the constructor call itself intact. Non-numeric components (a variable passed in place of a literal) are left as-is. Useful for obscuring spawn points, hitbox sizes, or other positional data embedded in a script.
local spawnPoint = SOTR_ENC_VEC(Vector3.new(120, 4, -85))
character:PivotTo(CFrame.new(spawnPoint))SOTR_INLINE
Type: (function, …args) → any
Rewrites the call site into an immediately-invoked function expression, so the body runs in place instead of through a real function call. Best suited for small, one-off blocks where the call overhead itself is what you want to avoid.
Aliases: LPH_INLINE
local x = SOTR_INLINE(function()
return heavy()
end)
local y = SOTR_INLINE(function(a, b)
return a + b
end, 2, 3)SOTR_CLEAR_STACK
Type: () → nil
Assigns nil to every local declared in the enclosing block so far, scrubbing them from the Lua stack so they cannot be recovered by a debugger or memory scanner once they've gone out of logical use.
local key = "super-secret-key"
local verified = validate(key)
SOTR_CLEAR_STACK() -- key, verified = nil, nil
if verified then
print("Access granted")
endSOTR_PRECHECK
Type: (() → any, expected?) → nil
Hoists an integrity check to the top of the chunk. Runs the function, and if its result doesn't equal expected (a scalar or a table compared element-wise), the whole script returns immediately instead of continuing. With no expected argument, the function just runs for its side effects. Multiple SOTR_PRECHECK calls chain in source order, any mismatch halts the script.
Aliases: LPH_PRECHECK
SOTR_PRECHECK(function()
return { game.PlaceId, plr.UserId }
end, { 0xAAAA, 0xBBBB })
SOTR_PRECHECK(function()
return typeof(getgenv) == "nil"
end, true)SOTR_REWRITE
Type: (number, opts?) → number
Rewrites a non-negative integer constant less than 2^32 as an equivalent mixed boolean-arithmetic (MBA) expression at compile-time, so the literal value never appears in the compiled output. Any other value (a float, a non-constant, or out of that range) is left exactly as written. The optional opts.preset controls nesting depth: fast, standard/strong (default), or extreme.
Aliases: LPH_REWRITE
local threshold = SOTR_REWRITE(100)
local deep = SOTR_REWRITE(987654, { preset = "extreme" })
if score >= threshold then
print("Passed")
endSOTR_NO_UPVALUES
Type: (function) → function
Wraps a function so the resulting callable has zero upvalues, for compatibility with tools that hook or inspect via hookfunction/hookmetamethod and expect an upvalue-free target.
Aliases: LPH_NO_UPVALUES
local handler = SOTR_NO_UPVALUES(function(...)
print(...)
end)SOTR_JIT
Type: (function) → function
Wraps a function to keep it native for speed, while still control-flow flattening it with a plain dispatch, so it's fast without shipping as cleartext the way SOTR_EXPOSE does. Use this on hot, performance-critical functions where the overhead of interpretation is measurable.
Aliases: LPH_JIT
local hotPath = SOTR_JIT(function()
-- performance-critical work
end)SOTR_JIT_MAX
Type: (function) → function
Like SOTR_JIT, but flattens the control flow at full opacity instead of a plain dispatch. Trades a heavier flatten for peak runtime performance while staying native.
Aliases: LPH_JIT_MAX
local process = SOTR_JIT_MAX(function(data)
-- work that benefits from aggressive optimization
end)