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.
--!nonstrict
if not SOTR_OBFUSCATED then
local function check(cond, msg)
if not cond then error("[soteria-sdk] " .. msg, 3) end
end
SOTR_ENC_STR = function(s) check(type(s) == "string", "SOTR_ENC_STR expects a string literal"); return s end
SOTR_ENC_NUM = function(n) check(type(n) == "number", "SOTR_ENC_NUM expects a number literal"); return n end
SOTR_ENC_FUNC = function(f) check(type(f) == "function", "SOTR_ENC_FUNC expects a function"); return f end
SOTR_ENC_VEC = function(v) check(v ~= nil, "SOTR_ENC_VEC expects a Vector2/Vector3/vector"); return v end
SOTR_ENC_UDIM = function(v) check(v ~= nil, "SOTR_ENC_UDIM expects a UDim/UDim2"); return v end
if buffer then
local buffer_fromstring = buffer.fromstring
SOTR_ENC_BUF = function(s) check(type(s) == "string", "SOTR_ENC_BUF expects a string"); return buffer_fromstring(s) end
else
SOTR_ENC_BUF = function(s) check(type(s) == "string", "SOTR_ENC_BUF expects a string"); return s end
end
SOTR_EXPOSE = function(f) check(type(f) == "function", "SOTR_EXPOSE expects a function"); return f end
SOTR_JIT = function(f) check(type(f) == "function", "SOTR_JIT expects a function"); return f end
SOTR_JIT_MAX = function(f) check(type(f) == "function", "SOTR_JIT_MAX expects a function"); return f end
SOTR_NO_UPVALUES = function(f) check(type(f) == "function", "SOTR_NO_UPVALUES expects a function"); return f end
SOTR_SECURE_CALL = function(f) check(type(f) == "function", "SOTR_SECURE_CALL expects a function"); return f end
SOTR_INLINE = function(f, ...) check(type(f) == "function", "SOTR_INLINE expects a function"); return f(...) end
SOTR_PRECHECK = function(fn) check(type(fn) == "function", "SOTR_PRECHECK expects a function"); fn() end
SOTR_REWRITE = function(n) check(type(n) == "number", "SOTR_REWRITE expects a number"); return n end
SOTR_KILL = function() end
SOTR_CLEAR_STACK = function() end
-- Line macro (call form: SOTR_LINE()):
SOTR_LINE = function()
local line = debug and debug.info and debug.info(2, "l")
return type(line) == "number" and line or 0
end
-- Attributes:
local __attribute = function() end
SOTR_ATTRIBUTES = __attribute
ENCRYPT = __attribute
VM = __attribute
PRESET = __attribute
OPTIMIZE = __attribute
NO_UPVALUES = __attribute
ERROR_HANDLING = __attribute
UNROLL = __attribute
INLINE = __attribute
TRANSFORM = __attribute
-- VM Options: (DEFAULT is shared with the PRESET options below)
DEFAULT = __attribute
NONE = __attribute
FAST = __attribute
SWIFT = __attribute
-- PRESET Options: (FAST is shared with the VM options above)
BALANCED = __attribute
SECURE = __attribute
SECURE_MAX = __attribute
-- TRANSFORM Options:
EXTRACT = __attribute
CONTROL_FLOW = __attribute
REWRITE_NAMECALLS = __attribute
-- EXTRACT Options:
GLOBALS = __attribute
CONSTANTS = __attribute
LPH_ENCSTR, MV_ENC_STR, WYNF_ENC_STRING = SOTR_ENC_STR, SOTR_ENC_STR, SOTR_ENC_STR
LPH_ENCNUM, WYNF_ENC_NUM = SOTR_ENC_NUM, SOTR_ENC_NUM
LPH_ENCFUNC, MV_ENC_FUNC, WYNF_ENC_FUNC = SOTR_ENC_FUNC, SOTR_ENC_FUNC, SOTR_ENC_FUNC
LPH_ENCBUF = SOTR_ENC_BUF
SOTR_NO_VIRTUALIZE, LPH_NO_VIRTUALIZE, MV_OMIT_VM, WYNF_NO_VIRTUALIZE = SOTR_EXPOSE, SOTR_EXPOSE, SOTR_EXPOSE, SOTR_EXPOSE
LPH_JIT = SOTR_JIT
LPH_JIT_MAX = SOTR_JIT_MAX
LPH_NO_UPVALUES = SOTR_NO_UPVALUES
WYNF_SECURE_CALL = SOTR_SECURE_CALL
LPH_INLINE = SOTR_INLINE
LPH_PRECHECK = SOTR_PRECHECK
LPH_REWRITE = SOTR_REWRITE
LPH_ATTRIBUTES = SOTR_ATTRIBUTES
SOTR_CRASH, LPH_CRASH, MV_CRASH, WYNF_CRASH = SOTR_KILL, SOTR_KILL, SOTR_KILL, SOTR_KILL
WYNF_LINE = SOTR_LINE -- call-form alias of SOTR_LINE (LPH_LINE / MV_LINE are markers, not defined)
endSOTR_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)
