Convert xLua compiled bytecode back to readable Lua 5.4 source, using an unmodified unluac as the back-end decompiler.
Tencent's xLua framework is widely used in Unity projects for hot-updating Lua scripts. These scripts ship as a custom bytecode format that standard Lua decompilers cannot read. Run unluac against a raw xLua file and you will hit:
The input file does not have the signature of a valid Lua file.
xlua-decompiler acts as a preprocessor. It reshapes xLua's file header and
root proto into standard Lua 5.4 framing so that unluac can do the heavy
lifting on the remaining bytecode.
xLua's bytecode differs from standard Lua 5.4 in four places:
| Difference | xLua | Standard Lua 5.4 |
|---|---|---|
| File header | ?@<source_path>.lua + \x80\x80\x00 |
\x1bLua\x54\x00 + integrity checks |
| Root proto start | nupval + maxstacksize |
source, linedefined, lastlinedefined, numparams, is_vararg, maxstacksize |
| Constant types | 0x03 = integer, 0x13 = float |
0x03 = float, 0x13 = integer |
| Upvalues | 3 bytes: instack, idx, kind |
2 bytes: instack, idx |
This tool handles the first two by rewriting the header and injecting the missing root proto fields. The other two are handled by unluac itself:
- Constant types:
--typemap xlua_typemap.txt(auto-generated on first run). - 3-byte upvalues: supported natively in unluac v1.2.3.569+.
pip install xlua-decompilergit clone https://github.com/GabHST/xlua-decompiler.git
cd xlua-decompiler
pip install -e .unluac is a Java JAR and is distributed separately. Download the latest unstable build (needed for 3-byte upvalues):
curl -L -o unluac.jar \
"https://sourceforge.net/projects/unluac/files/Unstable/unluac_2025_10_20.jar/download"You also need a working Java 11+ runtime on PATH.
xlua-decompiler decompile encrypted_script.lua \
--unluac unluac.jar \
-o readable.luaxlua-decompiler batch ./xlua_scripts/ \
--unluac unluac.jar \
-o ./decompiled/ \
--recursive \
-j 16A tqdm progress bar shows per-file progress through the convert and
decompile stages.
If you want to run unluac by hand or pipe the output into a different tool:
xlua-decompiler convert encrypted_script.lua -o standard.luac
java -jar unluac.jar --typemap xlua_typemap.txt standard.luac > readable.luapython -m xlua_decompiler --helpxlua-decompiler [-v|-vv] {convert, decompile, batch} ...
| Subcommand | Purpose | Needs Java? |
|---|---|---|
convert |
Rewrite a single xLua file to Lua 5.4 binary | No |
decompile |
Convert and run unluac on one file | Yes |
batch |
Parallel convert + decompile of a whole folder | Yes |
| Flag | Applies to | Description |
|---|---|---|
--overwrite |
all | Overwrite output files instead of refusing. |
--recursive |
batch |
Recurse into subdirectories. |
--extensions |
batch |
Input extensions to scan (default: .lua .luac .bytes). |
-j / --workers |
batch |
Parallel unluac workers (default: 8). |
--timeout |
decompile / batch |
Per-file unluac timeout, seconds (default: 60). |
--no-progress |
batch |
Disable the tqdm progress bar (useful in CI). |
-v / -vv |
global | Increase log verbosity (INFO / DEBUG). |
The conversion is minimal and surgical:
- Locate the bytecode by finding the
\x80\x80\x00marker after the source path header. - Extract
nupvalandmaxstacksizefrom the xLua root proto. - Build a standard Lua 5.4 31-byte header (signature, version, endianness check, float check).
- Inject the missing root proto fields (
source = NULL,linedefined = 0,lastlinedefined = 0,numparams = 0,is_vararg = 1,maxstacksize). - Copy the rest of the bytecode verbatim — constants, upvalues, nested protos, debug info.
xLua format: Standard Lua 5.4:
?@path/to/script.lua\x80\x80\x00 \x1bLua\x54\x00 ... (31 bytes)
[nupval][maxstacksize] [nupval]
[sizecode][code ...] [source_str][linedefined][lastlinedefined]
[constants ...] [numparams][is_vararg][maxstacksize]
[upvalues ...] [sizecode][code ...]
[protos ...] [constants ...]
[debug ...] [upvalues ...]
[protos ...]
[debug ...]
| Tool | xLua support | Lua 5.4 | Typemap support | Root proto fix | Upvalue fix | Batch | Output |
|---|---|---|---|---|---|---|---|
| xlua-decompiler | Yes | Yes | Yes | Yes | Yes (unluac) | Yes | Readable Lua source |
| unluac | No | Yes | No | No | Partial | No | Needs standard-format input |
| xLuaDumper | Partial | No | No | No | No | No | Bytecode dump only |
| luadec | No | No (5.x) | No | No | No | No | Lua 5.1 to 5.3 |
| LuaDecompy | No | No | No | No | No | No | Lua 5.1 only, experimental |
| luajit-decompiler-v2 | No | No | N/A | N/A | N/A | Yes | LuaJIT only |
| RustyLuaDec | No | Yes | No | No | No | No | Standard Lua 5.4 only |
Key idea: xlua-decompiler is the only open-source pipeline that handles xLua's custom framing end-to-end. Other tools either don't support the format or only perform partial steps.
unluac is an excellent Lua decompiler, but it expects standard Lua
bytecode. xLua's custom file header and missing root proto fields cause
unluac to reject the file outright. xlua-decompiler acts as a preprocessor
that transforms xLua bytecode into the standard format that unluac expects.
xLuaDumper can extract bytecode from xLua, but it focuses on opcode diffing between xLua and standard Lua. It does not handle the constant type swap, the root proto format differences, or provide a decompilation pipeline. It also requires the target DLLs to be loaded.
The xlua_typemap.txt file tells unluac how to interpret xLua's constant
types. It is auto-created next to the CLI on first run:
.type 0 nil
.type 1 false
.type 17 true
.type 3 integer
.type 19 float
.type 4 short_string
.type 20 long_string
In standard Lua 5.4 types 0x03 and 0x13 represent float and integer
respectively. xLua swaps these. The typemap corrects this at decompile time
without modifying the bytecode.
Batch decompilation with parallel workers on a typical laptop:
| Files | Workers | Time |
|---|---|---|
| 100 | 8 | ~15 s |
| 1,000 | 16 | ~90 s |
| 2,000 | 16 | ~3 min |
Each file spawns a JVM process for unluac. Increase -j on machines with
more cores.
Tested against:
- xLua bytecode produced by Tencent xLua with Lua 5.4 as the target.
- unluac v1.2.3.569 (October 2025).
- Python 3.10, 3.11, 3.12, 3.13.
- Windows 10/11, Linux.
Not compatible with:
- LuaJIT bytecode — use luajit-decompiler-v2.
- Lua 5.1, 5.2, 5.3 bytecode — different file format.
- xLua with additional custom encryption — decrypt first, then pipe through this tool.
xLua scripts are typically stored inside Unity AssetBundles. To extract them:
- Extract AssetBundles using AssetStudio or UnityPy.
- Locate TextAsset objects that contain xLua bytecode. They begin with
?@. - Decrypt if needed. Many titles XOR the blob with a fixed key; the key is usually visible as a string literal in the native runtime library.
- Run this tool on the decrypted files.
Q: I get "The input file does not have the signature of a valid Lua file".
A: You ran unluac against a raw xLua file. Use xlua-decompiler convert (or
decompile) first.
Q: I get "Unknown type name" with --typemap.
A: Make sure you are pointing at the xlua_typemap.txt that this tool
generates. Type names are case-sensitive and must match unluac's expectations.
Q: I get "The size of an integer is outside the range". A: You need unluac v1.2.3.569 or newer. Older versions do not support 3-byte upvalues.
Q: Some files are silently skipped.
A: Files whose contents begin with local data are plain-text Lua data
tables, not bytecode. They are already readable and the tool ignores them.
Q: Can this decompile any Unity + Lua project? A: Only builds that use Tencent xLua with Lua 5.4 bytecode. Plain Lua, LuaJIT, and other bindings use incompatible formats.
See CONTRIBUTING.md for development setup, test, and lint instructions.
MIT — see LICENSE.
- unluac by tehtmi, which does all the hard work of decompiling the standardized bytecode.
- Tencent/xLua for the framework.
- The reverse-engineering community for the public documentation of the Lua 5.4 bytecode format.