Overview

What superflow-rs does, what it needs to do it, and where the limits are.

superflow-rs takes an LPH-protected Luau script and reconstructs readable Lua from it. It is a Rust port of the original Python deserialize.py + unobfuscate.py pipeline.

What it actually does#

LPH compiles your script into a custom bytecode and ships an interpreter for it alongside the data. Recovering the source means:

  1. Decode the packed byte stream out of the obfuscated product
  2. Deserialize it into a constant pool and a set of protos (functions)
  3. Lift each proto's bytecode into an intermediate representation
  4. Optimise that IR — constant folding, dead code, control flow
  5. Emit Lua

Steps 3 to 5 run per proto, and they are independent, which is why the Rust port is meaningfully faster than the original: every function is lifted and optimised in parallel across all your cores.

Two modes#

ModeWhat it gives you
compatOutput that is byte-identical to the Python original. Use it when you need to diff against known-good results.
perfectReconstructs more aggressively — better variable naming and control flow at the cost of exact parity.

Compat mode exists so the port could be checked against the original. If the bytes match exactly, nothing changed behaviour on the way across.

The thing you need to know first#

It needs an opcode semantics table

The deobfuscator cannot work without OP_SEM_FINAL.json — a recovered mapping from each opcode number to what that opcode does.

That table is specific to one LPH build. LPH randomises its opcode numbering per version (and sometimes per script), so a table recovered from one product will not decode a different one. Recovering a fresh table is its own research process, not something the tool does for you.

So superflow-rs handles scripts from the build its table came from, and produces nonsense for anything else. That is how the protection works, not a fault in the port.

Running it#

Shell
superflow --from-lph obfuscated.lua --opsem OP_SEM_FINAL.json --out-dir out/
FlagWhat it does
--from-lph <file>Decode a raw LPH product
--in-bin <file>Use a pre-decoded byte stream instead
--opsem <file>The opcode semantics table. Required.
--from-jsonlSkip decoding and read a previously dumped const pool + protos
--out-dir <dir>Where to write results
--threads <n>Cap the worker count. Defaults to all cores.
--lph-skip <n>Byte offset into the packed stream

Why a Rust port#

The Python original is correct and slow. Each proto can be lifted on its own, so the work splits across cores cleanly — but Python's GIL means you only get that with multiple processes, which makes the pipeline messier than the problem warrants.

Rust just runs threads. rayon turned the per-proto loop into a parallel one and nothing else had to change.

Status#

Beta. It works, it is faster, and compat mode is verified byte-identical. What it is not is a general-purpose deobfuscator for every protector — see the warning above.