Skip to content

csproj export emits embedded resources flat instead of restoring folder structure from dotted LogicalName #3717

@SBvn-dev

Description

@SBvn-dev

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions