← All docs

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 the LMO_ 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.

Warning. This exposes the function — minified and stripped of local/upvalue names and comments. Raw strings and structure remain visible. Only use it on code that doesn't need obfuscation.

Measured

EngineVirtualizedNativeSpeed-up
block0.86 s0.035 s~24×
interp26.2 s0.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
Limitation. A native function uses globals and its own parameters & locals, but cannot capture locals from the surrounding virtualized scope — pass that state in as parameters. The same goes for varargs: the wrapped function is not vararg unless you write 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.

Note. LimeObf does not require you to pass an encryption/decryption key — the VM provides runtime decryption automatically, so the signature is just the function. An 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
Note. A condition body still creates its own scope, so locals declared inside an 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.