unluac-rs
简体中文 | English
This repository is still in a testing phase, and its behavior, APIs, and output details may continue to evolve. Bug reports, problematic test cases, incompatibility findings, usage feedback, and release-related suggestions are all very welcome. If you are using this decompiler and run into bytecode files that decompile poorly, please make sure to share a reproducible sample. Real-world samples are essential for improving the current logic, covering edge cases, and steadily raising output quality.
A multi-dialect Lua decompiler written in Rust.
Published entry points:
- Rust crate
unluac - Standalone CLI binaries on GitHub Releases
- npm package
unluac-js - API documentation on docs.rs
Introduction
The project currently supports the following Lua versions and dialects:
It uses control-flow analysis, dominator-tree analysis, and later pipeline normalization passes to eliminate most intermediate variables. For the cases currently tracked in this repository, it can usually reconstruct source code with a close-to-original shape.
The repository is organized roughly like this:
- Root package
unluac: core decompiler library packages/unluac-cli: command-line entry pointpackages/unluac-wasm: wasm bindingspackages/unluac-js: npm wrapper packagextask: test orchestration and Lua toolchain helpers
Usage
The project is currently distributed through these entry points:
- CLI: use the standalone binary from GitHub Releases, or run/build
unluac-clifrom this repository. - Rust library: add the published crate
unluacto a Rust project and call the decompilation pipeline directly. - npm package: install
unluac-jsfor Node.js or bundler-based browser environments. - WebAssembly: use
packages/unluac-wasmdirectly when you want to build your own runtime wrapper on top of the wasm layer.
CLI
The published CLI package in this repository is unluac-cli.
Recommended installation paths:
- Download a standalone binary from GitHub Releases and place it on your PATH under a stable name such as
unluac-cli - Build and install it from a local checkout:
- Run it directly from this repository during development:
If you are working inside this repository, .cargo/config.toml still provides cargo unluac -- ... as a local alias, but the documented CLI name is unluac-cli because that matches the published package.
Typical usage:
Notes:
- The CLI requires either
-i/--inputor-s/--source - When
-s/--sourceis provided, the CLI first invokes an external compiler to produce a chunk, then decompiles that generated chunk - Standalone GitHub Release binaries do not bundle a Lua compiler;
-s/--sourceonly works when you pass-l/--luacexplicitly, or when a compatible compiler is available underlua/build/<dialect>/or on PATH - When
-o/--outputis provided, the CLI writes the final generated source to the target file instead of stdout -o/--outputonly works for pure final-source runs and cannot be combined with debug / timing flags or--stop-afterearlier thangenerate- The CLI prints plain generated source by default and does not emit debug dumps unless you explicitly request them
unluac-cli --helpandunluac-cli --versionboth include the repository link- CLI defaults come from the core library's
DecompileOptions::default(), with CLI debug output disabled unless you explicitly enable it
Input options:
| Argument | Description | Default |
|---|---|---|
-D, --dialect |
Dialect used for compilation / decompilation | lua5.1 |
-i, --input |
Path to a compiled chunk | None |
-s, --source |
Path to Lua source; the CLI invokes an external compiler before decompiling | None |
-l, --luac |
Explicit compiler path used by --source |
First tries lua/build/<dialect>/, otherwise falls back to a compatible compiler on PATH |
-e, --encoding |
String decoding encoding | utf-8 |
-m, --decode-mode |
String decode failure strategy | strict |
-p, --parse-mode |
Strict vs permissive parser mode | permissive |
Debug options:
| Argument | Description | Default |
|---|---|---|
-d, --debug |
Enable debug output using the current target stage as the default dump stage | false |
--dump |
Dump one or more pipeline stages; repeat to request multiple stages | None |
--detail |
Debug output detail level | normal when debug is enabled |
-c, --color |
Debug color mode | auto |
--proto |
Restrict debug dumps to a specific proto id | None |
-t, --timing |
Print timing report | false |
Readability and naming options:
| Argument | Description | Default |
|---|---|---|
--return-inline-max-complexity |
Max inline complexity for returned expressions | 10 |
--index-inline-max-complexity |
Max inline complexity for table index expressions | 10 |
--args-inline-max-complexity |
Max inline complexity for call arguments | 6 |
--access-base-inline-max-complexity |
Max inline complexity for table access bases | 5 |
-n, --naming-mode |
Naming strategy | debug-like |
--debug-like-include-function |
Whether debug-like names should include function-shaped names | true |
Generate and output options:
| Argument | Description | Default |
|---|---|---|
--indent-width |
Generated source indentation width | 4 |
--max-line-length |
Preferred maximum line length | 100 |
--quote-style |
String quote style | min-escape |
--table-style |
Table constructor layout style | balanced |
--conservative-output |
Whether to prefer conservative source generation | true |
--comment |
Whether to emit generate-stage comments and metadata | true |
-g, --generate-mode |
How to handle syntax not supported by the target dialect | strict |
--stop-after |
Last pipeline stage to run | generate |
-o, --output |
Write the final generated source to a file instead of stdout | stdout |
Stage-valued options such as --dump and --stop-after accept:
parse, transform, cfg, graph-facts, dataflow, structure-facts, hir, ast, readability, naming, generate.
For more debugging examples and CLI workflow details, see docs/debug.md.
Rust Library
The published crate name is unluac.
For released builds, the recommended setup is the crates.io package:
[]
= "1"
If you need the latest unreleased changes from main, use a git dependency instead:
[]
= { = "https://github.com/x3zvawq/unluac-rs" }
Minimal example:
use fs;
use ;
Things to keep in mind:
- The library API accepts bytes of an already compiled chunk and does not compile Lua source for you
- If all you have is Lua source, the CLI is usually the more convenient entry point
- The main decompiler entry points are re-exported from src/decompile/mod.rs
npm Package
The published npm package is unluac-js.
Install it with:
unluac-js is a thin TypeScript wrapper around the wasm bindings produced by packages/unluac-wasm, with publishable contents narrowed to the built package output.
The published npm wasm build trims out debug / timing support to keep the package smaller. The CLI and Rust APIs still keep the full debugging surface. The npm-facing decompile() API returns the final source string directly instead of exposing intermediate pipeline metadata.
The main public APIs are:
init(input?)decompile(bytes, options?)supportedOptionValues()
Minimal Node.js example:
import from "unluac-js";
import from "node:fs/promises";
const chunkBytes = await ;
const source = await ;
console.log;
For browser usage and more complete package-level examples, see packages/unluac-js/README.md.
WebAssembly
The wasm binding layer lives at packages/unluac-wasm.
It uses wasm-bindgen and serde-wasm-bindgen to expose a JS-friendly object protocol instead of leaking Rust internal layouts across the boundary.
If you only want to use this project from JavaScript or TypeScript, the npm wrapper above is the recommended entry point. If you need to integrate the wasm layer into another language or runtime, you can:
- Take the built
unluac_wasm.jsandunluac_wasm_bg.wasmfiles from the published npm package - Or build
packages/unluac-wasmdirectly in this repository and prepare language-specific bindings yourself - Or consume the standalone
unluac_wasm_bg.wasmasset published alongside GitHub Releases
If you plan to extend the wasm support to a specific language or runtime, PRs are welcome.
Contributing and Feedback
Contributions of all kinds are welcome, including code, documentation, test cases, and other improvements. If you run into issues while using the project, or have ideas and suggestions, feel free to open an issue. If the project performs poorly on a specific case, attaching the corresponding binary file is also very helpful for diagnosis.
License
This project is released under the MIT License. See LICENSE.txt for details.
Acknowledgements
- metaworms's lua decompiler - This project's design and implementation were inspired by it, and the author's tutorial was also very helpful. The website is no longer accessible today.
- This project contains code generated by GPT-5.4 and Claude Opus 4.6.