Summary
When exporting an assembly as a .csproj (ilspycmd <asm> -p), embedded
resources are written to disk as flat files in the project root with their
full dotted manifest names as the filenames, rather than being restored to a
folder hierarchy that matches what the original source tree probably looked
like. The dotted segments of the resource name encode exactly that hierarchy
(modulo SDK name-mangling rules).
Steps to reproduce
Take any assembly built from a project whose source tree organizes assets
into folders (e.g. images/CopyHS.png, payloads/9.01/payloadName.bin), then:
ilspycmd MyApp.exe -p -o ./out
Actual output
out/
├── MyApp.images.CopyHS.png
├── MyApp.images.CutHS.png
├── MyApp.images.donate_full.png
├── MyApp.payloads._9._01.payloadName.bin
└── MyApp.csproj
with corresponding csproj entries:
<None Remove="MyApp.images.CopyHS.png" />
<EmbeddedResource Include="MyApp.images.CopyHS.png"
LogicalName="MyApp.images.CopyHS.png" />
Expected output
out/
├── images/
│ ├── CopyHS.png
│ ├── CutHS.png
│ └── donate_full.png
├── payloads/
│ └── 9.01/
│ └── payloadName.bin
└── MyApp.csproj
with csproj entries that preserve the original logical name even though the
file now lives in a folder, so existing
Assembly.GetManifestResourceStream("MyApp.images.CopyHS.png") calls in the
decompiled code keep working unchanged:
<EmbeddedResource Include="images\CopyHS.png"
LogicalName="MyApp.images.CopyHS.png" />
Why this matters
Right now, opening the decompiled csproj in Visual Studio shows a project
root with dozens of dotted-name files all jumbled together at the top level.
Reorganizing them into the original folder layout is a purely mechanical
transform (the dot-separated logical name is the encoding of the original
path) but currently has to be done by hand for every decompiled project.
Proposed approach
For each EmbeddedResource whose logical name begins with the project's
root namespace (typically the assembly name), strip that prefix, split the
remainder on ., treat the last segment as the file extension, the
second-to-last as the filename stem, and the rest as folder segments.
Apply the inverse of the SDK's leading-_ mangling for segments that begin
with _<digit>. Preserve the original LogicalName= attribute on the
emitted item so the resource keys stay byte-identical and existing
reflection-based resource lookups in the decompiled code keep working.
There is one inherent ambiguity (a folder name like 9.01 round-trips to
_9._01 which is indistinguishable from a 9/01/ two-folder hierarchy
without external knowledge), but a heuristic that merges consecutive
underscore-digit segments into a single dotted folder name handles the
common case and is no worse than the current behavior for the others.
Details
- Product in use: ilspycmd
- Version in use: 10.0.0.8330 (latest stable as of 2026-04-13)
Summary
When exporting an assembly as a
.csproj(ilspycmd <asm> -p), embeddedresources are written to disk as flat files in the project root with their
full dotted manifest names as the filenames, rather than being restored to a
folder hierarchy that matches what the original source tree probably looked
like. The dotted segments of the resource name encode exactly that hierarchy
(modulo SDK name-mangling rules).
Steps to reproduce
Take any assembly built from a project whose source tree organizes assets
into folders (e.g.
images/CopyHS.png,payloads/9.01/payloadName.bin), then:Actual output
with corresponding csproj entries:
Expected output
with csproj entries that preserve the original logical name even though the
file now lives in a folder, so existing
Assembly.GetManifestResourceStream("MyApp.images.CopyHS.png")calls in thedecompiled code keep working unchanged:
Why this matters
Right now, opening the decompiled csproj in Visual Studio shows a project
root with dozens of dotted-name files all jumbled together at the top level.
Reorganizing them into the original folder layout is a purely mechanical
transform (the dot-separated logical name is the encoding of the original
path) but currently has to be done by hand for every decompiled project.
Proposed approach
For each
EmbeddedResourcewhose logical name begins with the project'sroot namespace (typically the assembly name), strip that prefix, split the
remainder on
., treat the last segment as the file extension, thesecond-to-last as the filename stem, and the rest as folder segments.
Apply the inverse of the SDK's leading-
_mangling for segments that beginwith
_<digit>. Preserve the originalLogicalName=attribute on theemitted item so the resource keys stay byte-identical and existing
reflection-based resource lookups in the decompiled code keep working.
There is one inherent ambiguity (a folder name like
9.01round-trips to_9._01which is indistinguishable from a9/01/two-folder hierarchywithout external knowledge), but a heuristic that merges consecutive
underscore-digit segments into a single dotted folder name handles the
common case and is no worse than the current behavior for the others.
Details