Decompiler
Exporting an existing UMaterial or UMaterialFunction back to .dsm / .dsf — what round-trips, what falls back to UE.Expression, and what is simply lost.
The decompiler walks an existing UMaterial or UMaterialFunction node graph and writes an
equivalent DreamShaderLang source file. It is how you move a material that already exists into the
language without rebuilding it by hand.
| Aspect | Value |
|---|---|
| Accepts | UMaterial · UMaterialFunction · UMaterialFunctionMaterialLayer · UMaterialFunctionMaterialLayerBlend |
| Produces | one .dsm or .dsf file, UTF-8 without BOM |
| Writes to | <SourceDirectory>/Decompiled/… unless an explicit output path is given |
| Since | since 1.3.5 the Content Browser actions |
This is a migration starting point, not a round-trip guarantee. The exporter reproduces the
graph's structure and the parts of the node state it can express, then leaves a // Warning: comment
for everything it could not. Read Known gaps before you delete the original asset —
several classes of node state are dropped with no per-property warning at all.
Invoking it
| Route | Where | Produces |
|---|---|---|
| Content Browser | right-click a UMaterial ▸ DreamShader ▸ Export DSM | .dsm |
| Content Browser | right-click a UMaterialFunction, UMaterialFunctionMaterialLayer or UMaterialFunctionMaterialLayerBlend ▸ DreamShader ▸ Export DSF | .dsf |
| Material Editor | the DreamShader toolbar combo ▸ Export DSM / Export DSF | as above |
| Commandlet | -run=DreamShader decompile -Asset=<object path> [-Out=<file>] | as above |
Both editor routes require exactly one selected asset, write the file, and then open it in your preferred editor — see Editor Tools. The headless route is on Commandlet.
A progress dialog appears after a 0.25 s delay, titled Decompiling Material '{Asset}'... or
Decompiling Material Function '{Asset}'..., with one frame per visited node. It is suppressed in
commandlet runs.
Where the file goes
| Asset class | Directory | Extension |
|---|---|---|
UMaterial | <SourceDirectory>/Decompiled/Materials/ | .dsm |
UMaterialFunction | <SourceDirectory>/Decompiled/Functions/ | .dsf |
UMaterialFunctionMaterialLayer | <SourceDirectory>/Decompiled/Layers/ | .dsf |
UMaterialFunctionMaterialLayerBlend | <SourceDirectory>/Decompiled/LayerBlends/ | .dsf |
<SourceDirectory> is the Source Directory project setting, DShader by default. Layers and layer
blends get their own directories, and the class test is ordered blend-first, so a layer blend never
lands in Layers.
Inside the category directory the asset's package path becomes the relative file path, one directory per package segment:
/Game/Materials/Metal/M_Steel
→ <Project>/DShader/Decompiled/Materials/Game/Materials/Metal/M_Steel.dsm| Rule | Detail |
|---|---|
Leading and trailing / | stripped from the package name |
| Illegal characters | control characters and < > : " / \ | ? * become _ |
| Empty segment | becomes Folder<N> for a folder, Asset<N> for the last segment, <N> being the 1-based index |
| No package | the asset's own name is used as the only segment |
Passing -Out= to the commandlet overrides the whole computation and writes exactly where told
(after path normalization). The editor routes never take an override.
The name inside the file
The Name= written inside the file is not the file path. It is
Decompiled/<Category>/<package segments>, with \ / . : replaced by _ in each segment:
/Game/Materials/Metal/M_Steel
→ Shader(Name="Decompiled/Materials/Game/Materials/Metal/M_Steel")Recompiling the exported file therefore creates a new asset under /Game/Decompiled/Materials/…
and leaves the original untouched. That is deliberate: you can compare the two before committing.
Edit Name= and Root= when you are ready to take over the original path — see
Asset Paths.
File layout
| Notation | Meaning | Example |
|---|---|---|
<x> | Placeholder — substitute a real value; the angle brackets are not typed. | Name = <string> |
[ x ] | Optional — the whole group may be left out. | [, Root = <string>] |
{ a | b } | Choice — take exactly one of the alternatives separated by |. | { Node( … ) | Comment( … ) } |
… | Repetition — the preceding item may appear any number of times. | <property-declaration> … |
// Decompiled from <full object path>
[// Warning: <text>]…
[<VirtualFunction declaration>]…
{ Shader | ShaderFunction | ShaderLayer | ShaderLayerBlend }(Name="<generated name>")
{
[Properties = { … }]
[Inputs = { … }] // function kinds only
[Settings = { … }] // Shader only
[Outputs = { … }]
[Graph = { … }]
[Layout = { … }]
}| Block kind emitted | Source asset |
|---|---|
Shader | UMaterial |
ShaderFunction | UMaterialFunction |
ShaderLayer | UMaterialFunctionMaterialLayer |
ShaderLayerBlend | UMaterialFunctionMaterialLayerBlend |
The Settings block of a decompiled Shader always begins with Domain, ShadingModel and
BlendMode, emitted unconditionally. Every other setting is emitted only when it differs from the
UMaterial class default; the round-trip set is on Material Settings.
When a Base.FrontMaterial binding was decompiled the shading model is forced to Substrate,
because a Substrate material's own shading-model enum does not describe its surface.
Outputs declares one variable per connected material property and binds it, in this fixed order:
EmissiveColor, BaseColor, Metallic, Specular, Roughness, Anisotropy, Opacity, OpacityMask,
Normal, Tangent, WorldPositionOffset, SubsurfaceColor, CustomData0, CustomData1,
AmbientOcclusion, Refraction, PixelDepthOffset, MaterialAttributes,
FrontMaterial (UE 5.4 and newer only)Unconnected properties are skipped entirely.
What round-trips faithfully
The node walker has a curated case for each class below. Everything else falls through to the generic fallback.
Emitted as DreamShaderLang syntax
UMaterialExpression class | Emitted as |
|---|---|
Constant | a float literal |
Constant2Vector | float2(x, y) |
Constant3Vector | float3(r, g, b) |
Constant4Vector | float4(r, g, b, a) |
Add | a + b |
Subtract | a - b |
Multiply | a * b |
Divide | a / b |
OneMinus | 1.0 - x |
LinearInterpolate | lerp(a, b, alpha) |
Clamp | clamp(x, min, max) — only when the clamp mode is the default two-sided clamp |
Power | pow(base, exponent) |
DotProduct | dot(a, b) |
Normalize | normalize(v) |
Min | min(a, b) |
Max | max(a, b) |
Abs | abs(x) |
Saturate | saturate(x) |
Floor | floor(x) |
Ceil | ceil(x) |
Frac | frac(x) |
SquareRoot | sqrt(x) |
Sine | sin(x) — only when Period is 1 |
Cosine | cos(x) — only when Period is 1 |
ComponentMask | a swizzle on the input, built from the R/G/B/A flags in that order |
AppendVector | floatN(a, b), or a single merged swizzle when both operands swizzle the same base value |
Time | UE.Time() — only when it neither ignores pause nor overrides the period |
Reroute | nothing — plain reroutes are traced through, with a cycle guard |
NamedRerouteDeclaration, NamedRerouteUsage | a named Graph temporary, reused by every usage |
FunctionInput | the name declared in the Inputs section |
MaterialFunctionCall | a generated VirtualFunction declaration placed above the block, plus a call to it |
Unconnected operand pins fall back to the node's own constant property when it has one — Min,
Max, LinearInterpolate, Power and Clamp all read their Const* values — otherwise to a
literal 0.0.
The math-builtin spellings above are the same 19 names documented on
Math Builtins. Note the gaps: there is no decompiler branch for
UMaterialExpressionFmod, so an existing Fmod node comes back as a generic
UE.Expression(Class="Fmod", …) call rather than fmod(…). The source is equivalent; it just is not
the builtin spelling.
Emitted as a property declaration
These become entries in the Properties section and are referenced by name in the Graph. Names are
uniquified, and a ParameterName= metadata entry is added whenever the DreamShaderLang identifier
had to differ from the asset's parameter name.
UMaterialExpression class | Declaration |
|---|---|
ScalarParameter | ScalarParameter <Name> = <default>; |
VectorParameter | VectorParameter <Name> = float4(r, g, b, a); |
TextureObjectParameter | TextureObjectParameter <Name>[ = <asset path>]; |
TextureSampleParameter2D | TextureSampleParameter2D <Name>[ = <asset path>]; — only when no input pin is connected |
A TextureSampleParameter2D with any connected input pin is emitted as a curated UE.Expression
instead, carrying its parameter arguments and its sampler arguments together since 1.3.7. Its
RGBA output is emitted once into a named temporary; every other pin becomes a swizzle of it.
Emitted as a curated UE.Expression
These keep a hand-written argument list rather than a reflection dump, so only the properties that actually differ from the node default appear.
UMaterialExpression class | Arguments emitted |
|---|---|
CurveAtlasRowParameter | ParameterName, Group, SortPriority and Desc when non-default, then DefaultValue, Curve, Atlas, UseCustomPrimitiveData with PrimitiveDataIndex, and CurveTime when the time pin is connected |
StaticComponentMaskParameter | Input, DefaultR, DefaultG, DefaultB, DefaultA, and ParameterName when set. OutputType follows the number of enabled channels |
StaticSwitchParameter | True, False, and ParameterName, DefaultValue, DynamicBranch when non-default |
TextureCoordinate | CoordinateIndex, UTiling, VTiling — each only when non-default |
Time | bIgnorePause, bOverride_Period, Period — used when the node is not at its defaults |
Sine / Cosine | Input, Period — used when Period is not 1 |
Clamp | Input, Min, Max, ClampMode — used when the clamp mode is min-only or max-only |
Panner | Coordinate or ConstCoordinate, Time, and either Speed or the non-zero SpeedX / SpeedY, plus bFractionalPart |
Rotator | Coordinate or ConstCoordinate, Time, and CenterX, CenterY, Speed when non-default |
WorldPosition | WorldPositionShaderOffset when it is not the default |
CameraVectorWS | (none) |
ObjectPositionWS | (none) |
ScreenPosition | (none) |
VertexColor | (none) — always typed float4, with the pin's mask emitted as a swizzle |
TextureSample | the texture, sampler and mip arguments; the RGBA output is emitted once and every other pin becomes a swizzle of it |
Custom | Code, Description, Output for a secondary output, the full AdditionalOutputs list, and one argument per connected input. OutputType is the node's own declared return type, never the selected output's type |
The UE.Expression fallback
Any class without a case above is exported as a generic
UE.Expression call, and the decompiler records a warning for it.
The argument list is built in two passes:
- Every connected input pin, named after the pin, in pin order.
- Every reflected literal property whose value differs from the class default since 1.3.7.
A property is exported in pass 2 only when all of these hold:
| Requirement | Detail |
|---|---|
| Not deprecated, transient or duplicate-transient | |
| Not a material-expression input | those are pass 1 |
| Marked editable | CPF_Edit |
| Not a control name | Class, OutputType, ResultType, Output, OutputName, OutputIndex |
| Not an editor-only name | MaterialExpressionEditorX, MaterialExpressionEditorY, Desc, bCommentBubbleVisible, bShowOutputNameOnPin, bHidePreviewWindow, bCollapsed, bShaderInputData, SortPriority |
| A supported property type | bool, numeric, enum, byte, name, string, text, or object reference |
| Different from the class default | identical values are omitted |
| Not already an argument | the first writer of a name wins |
Names are compared after normalization, so bTwoSided and Two Sided collide.
Struct, array, map, set and delegate properties are dropped silently. They are not one of the
supported property types, so a node whose state lives in a struct exports with that state at its class
default, and no warning names the specific property. Re-set those by hand after the first compile, or
keep the node as UE.Expression and add the missing arguments yourself.
OutputType is always emitted, resolved from the real output index. When a named output selector
(Output= / OutputName=) is present, OutputIndex is suppressed, because the generator rejects a
call carrying both. Calls with more than three arguments, or longer than 120 characters, are emitted
across multiple lines.
Layout export
Controlled by the Export Decompiled Layout project setting, default on. When on, the file gets
a Layout section:
| Emitted line | From |
|---|---|
Comment(Name="<text>", X=<x>, Y=<y>, W=<w>, H=<h>, Color=float4(r, g, b, a)); | every editor comment box |
Node(Var="<name>", X=<x>, Y=<y>); | every named expression, sorted by X, then Y, then name |
Comment boxes whose text begins with DreamShader: are skipped — those are generated markers, not
authored comments.
Independently of the setting, each expression is also assigned to the smallest comment box that
encloses it, and those assignments become #Region / #EndRegion directives around the
corresponding Graph statements. Turning layout export off removes the Layout block but not the
regions. See Layout and #Region.
Diagnostics
Runtime substitutions are written {Placeholder}.
Warnings written into the file
Each is emitted once, as a // Warning: … comment under the // Decompiled from … header line.
None of them fails the export.
| Message | Cause |
|---|---|
Exported '{Class}' as UE.Expression; review reflected literal properties if the node has editor-only state. | a node with no curated case |
MaterialFunctionCall '{Path}' is not a plain MaterialFunction; it was exported through UE.Expression. | the call targets a layer or layer blend rather than a plain material function |
A MaterialFunctionCall had no function asset and was exported as a zero literal. | the call node has no function assigned |
Failed to emit VirtualFunction for '{Path}': {Error} | the called function's declaration could not be built |
Named reroute usage '{Node}' has no valid declaration; emitted a default literal. | a dangling named-reroute usage reached as a node |
Named reroute usage '{Node}' has no valid declaration; emitted its default value. | the same, reached through an input pin |
Detected a recursive graph dependency while decompiling node '{Node}'; emitted a default literal to avoid stack overflow. | a cycle in the expression graph |
Detected a recursive reroute dependency while decompiling node '{Node}'; emitted a default literal to avoid stack overflow. | a cycle through plain reroutes |
Detected a recursive named reroute dependency for '{Node}'; emitted a default literal to avoid stack overflow. | a cycle through named reroutes |
Append node '{Node}' resolved to {A} + {B} components, which cannot fit a float4; masked its inputs down to {A2} + {B2}. Review the emitted swizzle. | an append whose operands exceed four components |
Export failures
| Message | Cause | Fix |
|---|---|---|
| No Material asset was provided. | A null material reached the decompiler. | |
| No MaterialFunction asset was provided. | A null function reached the decompiler. | |
| No asset was provided. | A null asset reached the service. | |
| MaterialFunction '{Name}' does not expose any outputs. | The function declares no outputs. | Add an output to the asset, or export the material that calls it instead. |
| DreamShader decompile supports Material and MaterialFunction assets only: {Path} | Any other asset class — including UMaterialInstanceConstant. | Export the parent UMaterial and re-create the instance. |
| Decompile did not produce source text. | The decompile reported failure with no message. | |
| DreamShader failed to resolve an output file path. | The computed output path was empty. | |
| DreamShader failed to create output directory '{Directory}'. | The directory could not be created. | |
| DreamShader failed to write decompiled source '{File}'. | The file could not be written. |
Editor toasts
| Toast | Cause |
|---|---|
DreamShader could not find the selected Material. / …Material Function. | the asset was unloaded between right-click and click |
DreamShader failed to export DSM: {Error} / DreamShader failed to export DSF: {Error} | the decompile failed |
| (the raw write error) | the file could not be saved |
Exported DSM but could not open it: {File} | written, but the editor could not be launched |
Exported DSM: {File} / Exported DSF: {File} | success |
Logs: Exported Material '{Asset}' to DSM '{File}'. at Display, and
Failed to export Material '{Asset}' to DSM: {Error} at Warning.
Known gaps
Verified behaviour of 1.5.0. Every row is something the exported file will not reproduce. Check the ones that apply to your asset before treating the source file as the truth.
| Gap | Effect | Work-around |
|---|---|---|
| Material-function settings are never emitted | Description, ExposeToLibrary, LibraryCategories and UserExposedCaption are lost when exporting a UMaterialFunction | add a Settings block by hand |
Only the blessed UMaterial property set is emitted | properties outside it — OpacityMaskClipValue, NumCustomizedUVs, translucency lighting mode, displacement scaling, Nanite override — keep the class default | add the keys to Settings; they resolve by reflection — see Material Settings |
| Struct-, array-, map- and set-valued node properties are not reflected | a fallback UE.Expression node loses that state, with no per-property warning | set the property on the material after generation, or extend the emitted call |
Node comment text (Desc) and node SortPriority are dropped | comment bubbles and pin ordering are not reproduced | re-apply by hand |
| Node positions depend on a setting | with Export Decompiled Layout off, the regenerated graph is auto-laid-out instead | leave the setting on, or write Layout by hand |
Comment boxes prefixed DreamShader: are dropped | generated markers are not re-emitted, by design | none needed |
A MaterialFunctionCall on a layer or layer blend falls back to UE.Expression | the call is not expressed as a VirtualFunction | export the layer separately and call it |
A MaterialFunctionCall with no assigned function becomes 0.0 | the branch is silently constant-folded | re-assign the function in the original asset and re-export |
| Cycles emit a default literal | the cyclic branch evaluates to a constant | break the cycle in the original graph |
| An append wider than four components is masked down | components are dropped | check the emitted swizzle |
| Material instances are not supported | UMaterialInstanceConstant is rejected outright | export the parent UMaterial, then re-create the instance |
Texture-sample GatherMode round-trips only on UE 5.6 and newer | on older engines the property is omitted | none |
bHasPixelAnimation is in the emitted flag set only on UE 5.4 and newer | on older engines the flag is omitted | none |
Base.FrontMaterial and the Substrate shading-model spelling exist only on since UE 5.4 | a Substrate material cannot be exported meaningfully below 5.4 | none |
The generated Name= points into Decompiled/… | recompiling creates a second asset rather than replacing the original | edit Name= / Root= once the source is trusted |
| Large graphs skip automatic layout at generation time | a big regenerated graph can come back visually unordered when no Layout block is present | keep layout export on |
Example
Export /Game/Materials/M_Steel headlessly, then inspect the result:
& "$Engine\Binaries\Win64\UnrealEditor-Cmd.exe" "I:\Project\Project.uproject" `
-run=DreamShader decompile -Asset="/Game/Materials/M_Steel" `
-unattended -nopause -nosplash -stdout -logDreamShader decompiled '/Game/Materials/M_Steel.M_Steel' to
'I:/Project/DShader/Decompiled/Materials/Game/Materials/M_Steel.dsm'.The written file:
// Decompiled from /Game/Materials/M_Steel.M_Steel
Shader(Name="Decompiled/Materials/Game/Materials/M_Steel")
{
Properties = {
ScalarParameter Roughness_0 = 0.35 [ParameterName="Roughness"];
VectorParameter Tint = float4(0.8, 0.8, 0.82, 1.0);
}
Settings = {
Domain = "Surface";
ShadingModel = "DefaultLit";
BlendMode = "Opaque";
}
Outputs = {
float3 BaseColor;
float Metallic;
float Roughness;
Base.BaseColor = BaseColor;
Base.Metallic = Metallic;
Base.Roughness = Roughness;
}
Graph = {
BaseColor = Tint.rgb;
Metallic = 1.0;
Roughness = saturate(Roughness_0);
}
Layout = {
Node(Var="Tint", X=-640, Y=-208);
Node(Var="Roughness_0", X=-640, Y=48);
}
}Note ParameterName="Roughness" on the first property. The asset's parameter is called Roughness,
but that identifier was already taken in this file, so the declaration was uniquified to
Roughness_0 and the real parameter name preserved as metadata — otherwise the regenerated material
would expose a parameter under the wrong name. See
Metadata and Groups.
Where next
Editor Tools
The Export DSM / Export DSF menu entries.
Commandlet
decompile -Asset, -Out, and the exit codes.
UE.Expression
The generic call the fallback emits.
Material Settings
What the exporter can and cannot emit into Settings.
Layout and #Region
Node and Comment directives, and the regions.
Asset Paths
What the emitted Name= resolves to.