Obfuscator

@60.mirage
Every string is chained through three seeded substitution rounds and dropped into a shared opcode-dispatched constant pool alongside decoy instructions of the same shape, so only an integer key is left at each call site β€” no static byte table anywhere in the output. Standard-library globals are routed the same way, through a shuffled indirect dispatch table mixed with decoy entries, instead of plain named aliases. Layer on number/boolean rewriting, density-tunable opaque junk and decoys (up to Extreme), statement anti-beautify, a six-check anti-tamper guard, local renaming, and up to four nested encrypted loader layers, and a short script grows into something genuinely large and structurally noisy, the way heavier VM-flavored obfuscators do. No base64, no bitwise XOR β€” just string.char and table.concat for the core passes, so everything except the optional loader layers runs wherever standard Lua does.
Code hygiene
Rename & alias
Encrypt & disguise
Junk density
Off
Low
Medium
High
Insane
Extreme
Runtime protection
Loader layers
0 (off)
1
2
3
4
each layer needs load
Obfuscation strength πŸŒ‘πŸŒ‘πŸŒ‘πŸŒ‘πŸŒ‘ Weak
Input
0 chars
Output0 chars
Loaded:
Execution log
No runs yet β€” click Obfuscate to generate a real, timestamped log of every transformation applied.
How this works & honest limits +
Two honest trade-offs, your choice: everything except "Wrap in loader" compiles as ordinary static Lua β€” no dynamic compilation, so it runs in sandboxed executors that block load. The cost is that control flow (every if/for/function) and calls to standard globals like print or game stay visible in plain text β€” only strings, numbers, and local variable names get hidden. "Alias globals" closes part of that gap: calls to common globals (print, pairs, string, game, workspace, task, etc.) get routed through generated local variables, so the recognizable names disappear from the body of the code too. If you need everything β€” logic, structure, and all β€” hidden behind one opaque blob, turn on "Wrap in loader": it encrypts the entire finished script with the same cipher below and decodes it with (loadstring or load) at runtime. That's real opacity, but it only runs where load/loadstring isn't disabled β€” check your target executor before relying on it.
Two-round chained string cipher, with seed-derived keys: each character goes through round A β€” a 256-entry substitution table, multiplied by an odd constant mod 256 (always invertible, since 256=2⁸), then shifted by a rotating 8–16-byte additive key β€” and the result of that is immediately pushed through an independently-seeded round B the same way (combined with % throughout β€” no bitwise XOR). Neither round's substitution table, key or multiplier is ever written into the output as a literal array β€” only two 16-bit seeds are. The header runs a small deterministic generator (a 16-bit LCG) twice at load time to reconstruct the exact tables/keys/multipliers the browser used to encode, and the key length itself is derived from each seed (8–16 bytes) rather than fixed. "Wrap in loader" layers use the same two-round seeded derivation for their own cipher, plus a checksum of the encoded byte table verified before each layer's payload runs. Long strings (24+ chars) split into 2–3 independently-encoded chunks joined with ... "Rename locals" also renames local function names themselves, not just their parameters.
VM-style constant pool & opcode dispatch: encrypted string chunks aren't decoded inline anymore β€” each becomes one {op,{bytes}} instruction in a single shared pool table, addressed from the code by an integer key: _VMxxxx(37) instead of a decoder call carrying its own byte array. A dispatcher function reads inst[1] as an opcode and only takes the real two-round decode path when it matches this run's randomized real-op value (0–9, different every run); every other opcode wired into its if/elseif chain is genuine dead code β€” nothing in the output ever calls the dispatcher with those values, they're just structurally there. The pool itself is padded with decoy instructions (roughly one per real string) carrying those other opcodes and random byte payloads, and every entry β€” real or decoy β€” sits under an explicit [n]= key in a shuffled emission order, so position tells you nothing about which entries are real. This is what actually makes the output resemble a small bytecode VM rather than a string-substitution pass: an opaque instruction stream plus a dispatcher, not one decoder-per-literal.
Boolean disguise & opaque predicates: "Obfuscate true/false" rewrites every bare true/false literal into a small arithmetic comparison that always evaluates the same way ((a+b)==(a+b) for true, (a+b)==(a+c) for false) β€” a keyword search for the literal finds nothing. "Anti-beautify" alternates between wrapping bare call statements in a plain do ... end block and wrapping them in an if (opaque expression) then ... end branch that is always true at runtime β€” scope-safe either way, since it never wraps local declarations, but the branch form can't be stripped by a deobfuscator that just pattern-matches and deletes block delimiters; it has to evaluate the arithmetic first.
Anti-tamper & junk density: "Anti-tamper integrity check" inserts a block at the top of the output with six independent checks, any one of which can trip it: print, pcall, string, table, pairs, and ipairs still have their expected types; type() and tostring() themselves still behave correctly on known primitives (catches a hooked type/tostring); string.format and math.floor still return correct results on known inputs (catches a patched stdlib function used to slip past the other checks); where debug is present, debug.gethook() shows no installed hook; where getfenv is present, the running function's environment is still the real global table and not a swapped-in proxy; and where os.clock() is present, two separate busy loops with different workloads each run against their own randomized threshold. Any failed check raises an error. "Junk density" trades off size against noise: Low/Medium/High insert single-line junk (opaque conditionals, dead loops, decoy byte tables, decoy cipher headers, decoy pool-shaped instructions) at increasing probability; Insane also unlocks a nested do...end variant that wraps two more junk statements a level deeper; Extreme raises the insertion rate further and adds a bounded opaque-loop variant (a fixed single-iteration loop guarding an always-true predicate with a break) that reads like control-flow noise without any actual branch to trace. These are heuristics, not guarantees: an attacker who runs the script and logs what it does, or who patches every check, isn't stopped by them.
Loader layers: each layer encrypts the entire script produced so far with its own independent two-round seeded cipher and wraps it behind (loadstring or load), checksum-verified before it runs β€” 0 (off) leaves the script as plain static Lua, 1–4 nest that encryption that many times, so unwrapping layer 1 only reveals another opaque encrypted blob feeding into layer 2's loader, and so on. Each additional layer roughly doubles the output size and requires load/loadstring to be available at runtime, so it won't run in sandboxes that disable dynamic compilation β€” check your target environment before relying on more than one layer.
Real execution log: every click of "Obfuscate" records genuine, timestamped entries for each stage that ran β€” real counts of strings encrypted, pool/decoy instruction totals, numbers rewritten, identifiers renamed, junk statements inserted, loader layers applied, and total elapsed time β€” in the log panel above. This runs entirely in your browser (this is a static page with no server), so "real logs" means an accurate record of what that run actually did, not telemetry sent anywhere. Copy it, download it as a .log file, or clear it at any time.
Honest limits: the constant pool virtualizes literal storage/retrieval, not general control flow β€” every if/for/function in your source still compiles to ordinary, readable Lua statements unless you add loader layers, which hide structure by encrypting the whole script rather than by compiling it to custom instructions. That's a real difference from a full instruction-set VM (Luraph, Prometheus), which lowers arbitrary logic β€” not just constants β€” into its own bytecode; those are stronger and heavier for it. Anyone willing to actually run the script and log what it does, or attach a debugger that doesn't trip the anti-tamper checks, can still recover its behavior; no static or load-time obfuscator prevents that. Always test the output before shipping it.