Turn compiled Unity shaders back into readable ShaderLab and HLSL.
Unity ships shaders as LZ4-compressed blobs of stripped D3D11 DXBC bytecode.
This project takes the JSON that AssetStudio
exports for a Shader asset, recovers the serialized ShaderLab metadata, and
decompiles every shipped GPU program through a typed SSA IR into per-pass HLSL.
Not affiliated with, sponsored by, or endorsed by Unity Technologies.
- Complete ShaderLab output — shader name, properties and defaults, fallback, custom editor, tags, LOD, render state, subshaders, and passes.
- Readable HLSL — cbuffers with
packoffset, textures and samplers, input and output structs, vertex/fragment entry points, expressions, control flow, and intrinsics. - A real compiler pipeline — typed IR, CFG, dominance, SSA, ~20 optimization passes run to a fixed point, and matrix/vector/texture/loop pattern recognition.
- Unity-aware reconstruction — restores macros such as
UnityObjectToClipPos, reconstructs matrix multiplies, and rewrites lit passes back to#pragma surfaceform when they match. - Verified against the original bytecode — emitted passes are recompiled
with
d3dcompilerand their signatures are matched against the shipped subprograms. - Debuggable — dump the IR after every phase and write per-subprogram
.bin,.dxbc, and disassembly artifacts.
Toon/Basic, decompiled from a stripped DXBC blob:
#pragma vertex vert
program2Output vert(program2Input i)
{
program2Output o = (program2Output)0;
#define UnityObjectToClipPos(v) mul(unity_MatrixVP, v)
#define UnityObjectToWorldPos(v) mul(unity_ObjectToWorld, float4(v, 1.0))
float4 worldPos_xyzw_4 = UnityObjectToWorldPos(i.position0.xyz);
o.sv_Position0.xyzw = UnityObjectToClipPos(worldPos_xyzw_4);
o.texcoord0.xy = (mad(i.texcoord0.xyxx, _MainTex_ST.xyxx, _MainTex_ST.zwzz)).xy;
...
}
#pragma fragment frag
program6Output frag(program6Input i)
{
program6Output o = (program6Output)0;
float4 r0_xyzw_1 = _ToonShade.Sample(sampler_ToonShade, i.texcoord1.xyz);
float4 r1_xyzw_1 = _MainTex.Sample(sampler_MainTex, (i.texcoord0.xyxx).xy);
float4 r1_xyzw_2 = (r1_xyzw_1 * _Color);
o.sv_Target0.xyz = ((((r0_xyzw_1.xyzx + r0_xyzw_1.xyzx)).xyzx * r1_xyzw_2.xyzx)).xyz;
o.sv_Target0.w = r1_xyzw_2.w;
return o;
}Thirty-nine complete examples are checked in under
samples/.
- CI runs on every push and pull request. It builds on Windows, Linux, and macOS, runs the fast regression tier and the offline spec tests, but does not publish binaries.
- Nightly runs at 03:00 UTC and publishes
UnityShaderDecompiler-nightly-<date>-<os>artifacts only whenmasterreceived a commit in the previous 24 hours. It can also be triggered manually with the force option.
- .NET 10 SDK — build and run the decompiler.
- Python 3.10+ with
lz4— Stage 0 only. - Windows with
d3dcompiler_47.dll— verification modes only.
The core pipeline is platform-neutral. Only --disasm,
--recompile-verify, --recompile-verify-all, --verify-signatures,
--strip-survivors, and --faithfulness-test call into the native D3D
compiler.
The only NuGet dependency is
Vortice.D3DCompiler 3.8.3.
The Unity shader-blob reader is vendored AssetStudio code in
src/UnityShaderDecompiler.Core/Unity/Blob/.
git clone https://github.com/Abby1591/Unity_Shader_Decompiler.git
cd Unity_Shader_Decompiler
pip install lz4
python Extract.py shaders_json/HOLO_Holo.jsonExtract.py writes Output/HOLO_Holo/:
| File | Contents |
|---|---|
blob.bin |
m_CompressedBlob, base64-decoded and LZ4-decompressed |
metadata.json |
flattened m_ParsedForm: properties, subshaders, render state, cbuffer layouts |
dummy.shader |
optional plain-text reference from shaders_dummy/, when a match exists |
Then build and run the decompiler:
dotnet build UnityShaderDecompiler.sln
dotnet run --project "src/UnityShaderDecompiler.Cli/UnityShaderDecompiler.Cli.csproj" -- Output/HOLO_HoloThe result is written to src/UnityShaderDecompiler.Cli/Output/HOLO_Holo.shader.
Shader Decompiler accepts a Stage 0 output folder or a blob.bin with a
sibling metadata.json:
# Stage 0 output folder
dotnet run --project "src/UnityShaderDecompiler.Cli/UnityShaderDecompiler.Cli.csproj" -- Output/HOLO_Holo
# Bare blob.bin with metadata.json next to it
dotnet run --project "src/UnityShaderDecompiler.Cli/UnityShaderDecompiler.Cli.csproj" -- path/to/blob.bin
# Write output somewhere other than the project-local Output/
dotnet run --project "src/UnityShaderDecompiler.Cli/UnityShaderDecompiler.Cli.csproj" -- Output/HOLO_Holo --out-root ./decompiledWith no arguments, the decompiler looks for Output/HOLO_Holo in the working
directory and then one level up.
| Option | Purpose |
|---|---|
--out-root <dir> |
Write the .shader and subprogram artifacts to <dir> |
--dump-stages |
Print the IR after every pipeline phase |
--save-subprograms |
Write per-subprogram .bin, .dxbc, and disassembly files |
--no-surface-shaders |
Emit raw HLSLPROGRAM passes instead of surface-shader form |
--keep-passes |
Keep original passes as comments when a surface shader is recognized |
--keep-register-bindings |
Keep explicit register(tN) / register(bN) bindings |
Full CLI reference
| Argument | Default | Effect |
|---|---|---|
json_path (positional) |
— | AssetStudio JSON export to process |
--dummy-dir |
shaders_dummy |
Directory to search for the matching .shader reference |
--out-dir |
Output |
Root output directory |
--flat |
off | Write the three files directly into --out-dir instead of a per-shader subfolder |
Folder names are sanitized for Windows (/ and \ become _, trailing dots
and spaces are stripped).
| Argument | Effect |
|---|---|
<folder> / <blob.bin> (positional) |
Stage 0 output folder, or a blob.bin with a sibling metadata.json |
--out-root <dir> |
Write .shader output and subprogram artifacts to <dir> instead of the project-local Output/ |
--dump-stages |
Print a full IR snapshot after every pipeline phase and every optimization pass |
--dump-keywords |
Print each subprogram's global and local keyword sets |
--save-subprograms |
Also write per-subprogram program{i}.bin, program{i}.dxbc, and program{i}.hlsl (disassembly) |
--no-fuse-temps |
Disable temp fusion; keep every SSA copy as a separate statement |
--no-surface-shaders |
Disable surface-shader recognition; emit raw reconstructed HLSLPROGRAM passes |
--keep-passes |
When a pass is recognized as a surface shader, keep the original compiled passes as comments |
--keep-register-bindings |
Keep explicit register(tN) / register(bN) bindings (stripped by default) |
--faithfulness-test <folder> |
Decompile one folder, then verify recompiled signatures and metadata fidelity; exit code 0 on pass, 1 on failure |
--run-spec-tests |
Run the offline parser spec vectors and exit |
--verify-signatures [dir] |
Cross-check the ISGN parser against d3dcompiler's GetInputSignatureBlob for every shipped subprogram in dir (default: the project-local Output/) |
--strip-survivors |
Compile synthetic shaders, run D3DStripShader(flags=7), and record which DXBC chunks survive; also scans shipped bytecode |
--recompile-verify [file] |
Recompile the HLSLPROGRAM blocks of one .shader and match their signatures against shipped bytecode |
--recompile-verify-all [dir] |
Aggregate recompile verification over every decompiled .shader in dir |
--disasm [dir] |
Dump DXBC disassembly for every non-compute subprogram into sibling <name>.disasm.txt files |
AssetStudio JSON export
└─ Stage 0: base64 + LZ4 → blob.bin + metadata.json
└─ Stage 1: DXBC → typed IR → SSA → optimize → HLSL AST → .shader
- Extract the compressed blob and flatten
m_ParsedFormintometadata.json. - Parse the DXBC container, its typed chunks, and the SM4/SM5 instruction stream.
- Rebuild a typed SSA IR, optimize it to a fixed point, recognize shader patterns, and bind symbolic names from the shader metadata.
- Emit the
.shader: pair variants into passes, pretty-print the AST, and recognize Unity surface-shader boilerplate.
Pipeline details
Each subprogram goes through IRPipeline (DXBC/IR/IRPipeline.cs):
- Blob classification — distinguish non-compute (
0x02header), compute (u64 sentinel), raw-variant, and wire-frame payloads, then slice out the DXBC container. - Container parse — validate the
DXBCmagic and lengths; parseISGN,OSGN,OSG5,PSGN,PCON,SFI0,RDEF,STAT, and theSHDR/SHEXshader chunk. Unknown chunks are preserved raw. - Instruction decode — decode every SM4/SM5 instruction and operand.
- IR build — build flat, typed IR statements.
- CFG — build basic blocks and edges.
- SSA — dominators (CHK algorithm), dominance frontiers, phi insertion, and renaming.
- Optimization — nine statement-level passes (constant folding, constant propagation, copy propagation, dead-code elimination, algebraic simplification, value numbering, strength reduction, loop-invariant code motion, sparse conditional constant propagation) plus a CFG inner loop (branch simplification, CFG cleanup, phi simplification), up to 50 iterations.
- Pattern recognition — fold shader idioms back into high-level IR with the matrix, vector, texture, loop, and shader-pattern recognizers.
- Leave SSA — eliminate phis and coalesce copies.
- Metadata binding — attach RDEF/ISGN/OSGN symbolic names to the IR.
- AST build — one function node per subprogram; the
Shader/SubShader/Pass shell comes from
metadata.json. - Pass and variant recovery —
BuildVariantPassesgroups subprograms into passes. Unity serializes them pass-by-pass, so a stage regression marks a pass boundary. Vertex and fragment variants are paired by interpolator hand-off signature, and byte-identical pairs are deduplicated. - Pretty print — emit properties, tags, LOD, render state,
HLSLPROGRAMblocks, structs, resources, and function bodies. - Surface-shader recognition — rewrite a pass to
#pragma surfaceform when it carries the compiled signature of a surface shader.
--dump-stages labels snapshots with these phase names:
01-cfg (pre-SSA)
02-phi-insertion (pre-rename)
03-ssa-renamed (pre-optimize)
04-optimize-<pass> (every pass, per iteration)
05-pattern-recognized
06-left-ssa
06b-copy-coalesced
07-metadata-bound
The primary regression suite is a normal dotnet test project:
dotnet test UnityShaderDecompiler.slnIt runs in a few seconds and covers:
- offline blob-classifier and DXBC-container vectors,
- in-process decompilation of checked-in fixture blobs,
- metadata fidelity (name, properties, fallback, cbuffer slots),
- SSA validity, variant pairing, and deterministic output,
- Windows-only
d3dcompilerround-trips underCategory=D3D.
Every push and pull request runs the fast tier (everything except the native D3D checks):
dotnet test UnityShaderDecompiler.sln --filter "Category!=D3D"The end-to-end Python runners exercise Stage 0 plus the CLI:
# Four PicaVoxel shaders with real source in shaders_dummy/Source/
python tests/e2e/test_picavoxel_shaders.py
# Every shader folder in Output/
python tests/e2e/test_faithfulness.py
# Narrow the faithfulness suite to one or more shaders
python tests/e2e/test_faithfulness.py --filter PicaVoxelBoth default to
src/UnityShaderDecompiler.Cli/bin/Debug/net10.0/Shader Decompiler.exe; pass
--parser to point somewhere else.
Additional verification modes live on the CLI. Run them from the repository
root, prefixing each with
dotnet run --project "src/UnityShaderDecompiler.Cli/UnityShaderDecompiler.Cli.csproj" --.
| Command | What it does |
|---|---|
--run-spec-tests |
Offline parser vectors (also run in CI as a smoke test) |
--verify-signatures |
Cross-check ISGN parsing against d3dcompiler |
--recompile-verify-all |
Recompile every emitted pass and match shipped signatures |
--strip-survivors |
Check which chunks survive D3DStripShader(flags=7) |
--faithfulness-test <folder> |
Decompile, recompile, and check metadata fidelity for one shader |
- Targets Unity 2022.3 D3D11 shader blobs.
- Vertex and fragment stages are fully reconstructed. Hull, geometry, domain, and compute programs are parsed and IR-built, but the AST/emit path mainly targets vertex/fragment; other stages attach by serialization order.
- Surface-shader rewrites are gated on recompiled signature matches. Passes that do not match are emitted as HLSLPROGRAM.
- Passes declared in metadata with no compiled DX11 variants in the blob are emitted as empty shells; the decompiler does not fabricate program bodies.
docs/shader_compiler_analysis.md— Ghidra analysis ofUnityShaderCompiler.exe, covering the IPC wire protocol, RDEF extraction, andD3DStripShader(flags=7).docs/shader_parser_spec.md— implementation-grade parser spec with every fact labeled PROVEN, STRONGLY INFERRED, POSSIBLE, or UNKNOWN.
Repository map
.
├── UnityShaderDecompiler.sln Solution (core + CLI + tests)
├── Extract.py Stage 0 orchestrator (Python)
├── libs/ blob/metadata/dummy helpers
│ ├── blob.py m_CompressedBlob → raw bytes
│ ├── metadata.py m_ParsedForm → metadata.json
│ ├── dummy.py matching .shader reference lookup
│ ├── lz4util.py LZ4 decompression wrapper
│ └── Reader.py little-endian BinaryReader
│
├── src/
│ ├── UnityShaderDecompiler.Core/ parsing, IR, optimization, HLSL emission
│ │ ├── Decompiler.cs Reusable, in-process pipeline API
│ │ ├── Core/
│ │ │ ├── Analysis/ SSA, dominators, phi insertion, def-use
│ │ │ ├── Hlsl/ AST, pretty printer, surface recognition
│ │ │ └── Optimizations/ rewrite passes and pattern recognizers
│ │ ├── DXBC/
│ │ │ ├── Chunks/ ISGN/OSGN/PSGN/RDEF/SFI0/STAT
│ │ │ ├── Compilation/ native d3dcompiler wrapper
│ │ │ ├── Container/ DxbcFile and chunk parsing
│ │ │ ├── Disassembly/ Vortice-backed disassembler
│ │ │ ├── Extraction/ blob classifier
│ │ │ ├── Instructions/ opcodes, operands, ShdrParser
│ │ │ ├── IR/ IR model, builders, IRPipeline
│ │ │ └── Metadata/ ShaderProject and metadata loading
│ │ └── Unity/Blob/ vendored AssetStudio blob reader
│ │
│ └── UnityShaderDecompiler.Cli/ entry point and verification harnesses
│ ├── Program.cs CLI dispatch
│ └── Verification/ spec vectors, faithfulness, recompile checks
│
├── tests/
│ ├── UnityShaderDecompiler.Tests/ xUnit regression suite (`dotnet test`)
│ ├── e2e/ Python end-to-end runners (Stage 0 + CLI)
│ └── Fixtures/ checked-in blob.bin + metadata.json fixtures
│
├── docs/ reverse-engineering notes and parser spec
├── samples/ 39 checked-in example decompilations
├── shaders_json/ 39 AssetStudio JSON exports
├── shaders_dummy/ reference shaders and Source/
└── Output/ generated Stage 0 output (not checked in)
Issues and pull requests are welcome.
- Add more real shader sources under
shaders_dummy/Source/to grow the golden corpus. - Fix decoder and optimizer correctness bugs.
- Improve the AST path for hull, geometry, domain, and compute stages.
If you change the pretty printer or surface-shader recognition, keep
dotnet test and --recompile-verify-all green.
- AssetStudio — the shader-blob reader
in
src/UnityShaderDecompiler.Core/Unity/Blob/is vendored from it. - Vortice.Windows — D3D compiler P/Invoke used for disassembly and recompilation.
- The reverse-engineering notes in
docs/document the UnityShaderCompiler work this project is built on.
Apache License 2.0 — see LICENSE.