Macros
Control protection per-function, right in your source. Mark a hot loop native, force full encryption on a sensitive function, or guard constants — no config, just a function call.
Overview
Macros are special markers recognized by the compiler at build time. They read like ordinary global calls (LMO_NO_VIRTUALIZE(function() return work() end)) but are transformed during obfuscation — there's nothing to import. They let you selectively adjust the balance between performance and security, per-function. Macros work in both engines (block and interp).
Dev shims
So your script still runs unobfuscated during development, define passthrough shims for every macro you use. They must be declared as globals (locals won't be replaced correctly) and wrapped in an if not LMO_OBFUSCATED then block. When the build is obfuscated, LMO_OBFUSCATED becomes a compile-time true, the whole block is skipped, and the real macros take over.
if not LMO_OBFUSCATED then
LMO_OBFUSCATED = false
LMO_LINE = 0
function LMO_NO_VIRTUALIZE(...) return ... end
function LMO_JIT(...) return ... end
function LMO_JIT_MAX(...) return ... end
function LMO_ENCFUNC(...) return ... end
function LMO_NO_UPVALUES(...) return ... end
function LMO_ENCSTR(...) return ... end
function LMO_ENCNUM(...) return ... end
function LMO_CRASH() error("LMO_CRASH") end
end
Reserved prefix
All identifiers starting with LMO_ are reserved for macro use and will error if used incorrectly:
LMO_DO_SOMETHING()— errors, because it is not a real macro.local LMO_NAME = 1— errors, because theLMO_prefix is reserved (this includes locals and parameters; globals used as shims are fine).
LMO_NO_VIRTUALIZENative speed
function LMO_NO_VIRTUALIZE(const function toDevirtualize)
Aliases LMO_JIT, LMO_JIT_MAX
Compiles the function as real native Lua instead of routing it through the VM. The rest of your script stays virtualized; only this function runs at full speed. This is the fix for per-frame CPU cost — wrap hot loops, render steps, or math-heavy functions.
Measured
| Engine | Virtualized | Native | Speed-up |
|---|---|---|---|
block | 0.86 s | 0.035 s | ~24× |
interp | 26.2 s | 0.035 s | ~748× |
Correct usage
local myFunction = LMO_NO_VIRTUALIZE(function() end)
LMO_NO_VIRTUALIZE(function() end)()
someFunction(LMO_NO_VIRTUALIZE(function() end))
-- on a platform callback (Roblox)
game:GetService("RunService").RenderStepped:Connect(LMO_NO_VIRTUALIZE(function(dt) end))
task.spawn(LMO_NO_VIRTUALIZE(function() end))
Incorrect usage
LMO_NO_VIRTUALIZE(function() end) -- does NOT auto-call the function
LMO_NO_VIRTUALIZE(myFunction) -- the argument must be a function LITERAL
local outer = 10
LMO_NO_VIRTUALIZE(function() return outer end) -- captures an outer (virtualized) local -> reads nil
function(...) yourself, so it can't borrow the enclosing function's ... (doing so is a Lua error, not specific to LimeObf). Native code is also subject to plain Lua's VM limits (register/syntax depth), so make sure it runs unobfuscated first.LMO_JITNative speed
function LMO_JIT(const function toEnhance)
Aliases LMO_JIT_MAX
Optimizes a performance-critical function by compiling it to native speed — use it on rendering loops or heavy math. In LimeObf this maps to the same native path as LMO_NO_VIRTUALIZE, so LMO_JIT and LMO_JIT_MAX are aliases of it and share the same restriction.
Correct usage
local myFunction = LMO_JIT(function() end)
LMO_JIT(function() end)()
someFunction(LMO_JIT_MAX(function() end))
Incorrect usage
LMO_JIT(function() end) -- does NOT auto-call the passed function
LMO_ENCFUNCFull encryption
function LMO_ENCFUNC(const function toEncrypt)
Aliases LMO_FUNCENC
The opposite of LMO_NO_VIRTUALIZE: forces the function through full VM virtualization. In LimeObf a virtualized function is encrypted at rest — its bytecode carries per-function keys and its constants are decoded lazily, bound to the runtime environment, so it is decrypted only as it runs. Use it to guarantee maximum protection on the code that matters most: license checks, key validation, anti-tamper logic.
LMO_ENCFUNC function is virtualized, so it may capture upvalues normally; it runs at VM speed (slower than native) — that's the trade for maximum opacity.Correct usage
local verifyKey = LMO_ENCFUNC(function(key)
return checksum(key) == EXPECTED -- may use upvalues / globals
end)
verifyKey(userKey)
someFunction(LMO_ENCFUNC(function() end))
Incorrect usage
LMO_ENCFUNC(myFunc) -- the argument must be a function LITERAL
LMO_ENCFUNC(function() end, "deadbeef") -- LimeObf takes ONE argument (no key) — too many args
LMO_NO_UPVALUESVirtualized
function LMO_NO_UPVALUES(const function toFix)
Virtualizes the function normally, but guarantees it has no upvalues — useful for hooks and APIs that misbehave with high upvalue counts. LimeObf enforces this at compile time: if the function captures a local from the enclosing scope, the build fails with a clear error, so you get a genuinely self-contained closure.
Correct usage
local myFunction = LMO_NO_UPVALUES(function(x) return x * 2 end)
LMO_NO_UPVALUES(function() end)()
hookfunction(target, LMO_NO_UPVALUES(function() end))
-- a function nested inside may still capture the marked function's OWN locals
local outer = LMO_NO_UPVALUES(function(x)
local inner = function() return x end
return inner()
end)
Incorrect usage
LMO_NO_UPVALUES(function() end) -- does NOT auto-call the passed function
LMO_NO_UPVALUES(myFunction) -- the argument must be a function LITERAL
local secret = 42
LMO_NO_UPVALUES(function() return secret end) -- compile error: captures the outer local `secret`
LMO_ENCSTRConstant
string LMO_ENCSTR(const string toEncrypt)
Aliases LMO_STRENC
Marks a string constant for encryption. LimeObf already encrypts every constant by default, so this is an explicit marker for the values you most want protected — and it validates that the argument is a literal string.
Correct usage
local myString = LMO_ENCSTR("Important String")
print(LMO_ENCSTR("Hello, World!"))
return LMO_ENCSTR("Goodbye!")
Incorrect usage
local myString = LMO_ENCSTR(variable) -- argument is not a constant
print(LMO_ENCSTR("A", "B")) -- too many arguments passed
print(LMO_ENCSTR()) -- not enough arguments passed
LMO_ENCNUMConstant
number LMO_ENCNUM(const number toEncrypt)
Aliases LMO_NUMENC
Marks a number constant for encryption. Works on integers and doubles and preserves the value's type.
Correct usage
local myNumber = LMO_ENCNUM(1000)
print(LMO_ENCNUM(1), LMO_ENCNUM(1.5))
return LMO_ENCNUM(0xDEADBEEF)
Incorrect usage
local myNumber = LMO_ENCNUM(variable) -- argument is not a constant
print(LMO_ENCNUM(0, 0)) -- too many arguments passed
print(LMO_ENCNUM()) -- not enough arguments passed
LMO_OBFUSCATEDCompile-time
const boolean LMO_OBFUSCATED
A constant value, replaced with true in an obfuscated build (and left to your shim, false, when unobfuscated). Use it to gate code paths that should only run in one context, or to power the dev-shim pattern.
Correct usage
if LMO_OBFUSCATED then
validateWhitelist() -- only runs in the obfuscated build
else
skipWhitelist() -- only runs unobfuscated
end
return LMO_OBFUSCATED and runProtected() or runWithDebugging()
Incorrect usage
LMO_OBFUSCATED() -- LMO_OBFUSCATED is a boolean value, not a function
if LMO_OBFUSCATED then branch aren't visible outside it. Declare the variable in the parent scope first and just assign inside the branch.LMO_LINECompile-time
const number LMO_LINE
A constant value replaced with the current source line number at compile time. Handy for lightweight tracing without leaving real line-info or a debug call in the output.
Correct usage
local firstLine = LMO_LINE
print("Current line", LMO_LINE)
error("Error on line: " .. LMO_LINE)
Incorrect usage
LMO_LINE() -- LMO_LINE is a number value, not a function
LMO_CRASHUtility
void LMO_CRASH()
Securely crashes the VM at the call site. Drop it into a branch that should never be reached — after an integrity or environment check fails — to halt a tampered build hard, with no recovery.
Correct usage
if hookDetected() then
LMO_CRASH()
end
return LMO_CRASH()
Incorrect usage
There is no incorrect usage — the VM crashes regardless of any arguments or surrounding context.