diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index a2d7b5bb48..7fe6df907a 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -147,6 +147,7 @@
+
diff --git a/ICSharpCode.Decompiler/Metadata/MetadataExtensions.cs b/ICSharpCode.Decompiler/Metadata/MetadataExtensions.cs
index 8a7df4beef..90f44096a8 100644
--- a/ICSharpCode.Decompiler/Metadata/MetadataExtensions.cs
+++ b/ICSharpCode.Decompiler/Metadata/MetadataExtensions.cs
@@ -1,6 +1,25 @@
-using System;
+// Copyright (c) 2018 Siegfried Pammer
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
using System.Buffers.Binary;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
@@ -426,5 +445,23 @@ public static BlobReader AsBlobReader(this MetadataReader metadataReader)
return new(metadataReader.MetadataPointer, metadataReader.MetadataLength);
}
}
+
+ public static uint ReadULEB128(this BinaryReader reader)
+ {
+ uint val = 0;
+ int shift = 0;
+ while (true)
+ {
+ byte b = reader.ReadByte();
+ val |= (b & 0b0111_1111u) << shift;
+ if ((b & 0b1000_0000) == 0)
+ break;
+ shift += 7;
+ if (shift >= 35)
+ throw new OverflowException();
+ }
+ return val;
+ }
+
}
}
diff --git a/ICSharpCode.Decompiler/Metadata/MetadataFile.cs b/ICSharpCode.Decompiler/Metadata/MetadataFile.cs
index ba884731b3..c92c0e061c 100644
--- a/ICSharpCode.Decompiler/Metadata/MetadataFile.cs
+++ b/ICSharpCode.Decompiler/Metadata/MetadataFile.cs
@@ -51,6 +51,7 @@ public enum MetadataFileKind
{
PortableExecutable,
ProgramDebugDatabase,
+ WebCIL,
Metadata
}
diff --git a/ICSharpCode.Decompiler/Metadata/WebCilFile.cs b/ICSharpCode.Decompiler/Metadata/WebCilFile.cs
new file mode 100644
index 0000000000..291316085c
--- /dev/null
+++ b/ICSharpCode.Decompiler/Metadata/WebCilFile.cs
@@ -0,0 +1,284 @@
+// Copyright (c) 2024 Siegfried Pammer
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.IO.MemoryMappedFiles;
+using System.Reflection.Metadata;
+using System.Text;
+
+using ICSharpCode.Decompiler.TypeSystem;
+
+#nullable enable
+
+namespace ICSharpCode.Decompiler.Metadata
+{
+ public class WebCilFile : MetadataFile, IDisposable, IModuleReference
+ {
+ readonly MemoryMappedViewAccessor view;
+ readonly long webcilOffset;
+
+ private WebCilFile(string fileName, long webcilOffset, long metadataOffset, MemoryMappedViewAccessor view, ImmutableArray sectionHeaders, ImmutableArray wasmSections, MetadataReaderProvider provider, MetadataReaderOptions metadataOptions = MetadataReaderOptions.Default)
+ : base(MetadataFileKind.WebCIL, fileName, provider, metadataOptions, 0)
+ {
+ this.webcilOffset = webcilOffset;
+ this.MetadataOffset = (int)metadataOffset;
+ this.view = view;
+ this.SectionHeaders = sectionHeaders;
+ this.WasmSections = wasmSections;
+ }
+
+ public static WebCilFile? FromStream(string fileName, MetadataReaderOptions metadataOptions = MetadataReaderOptions.Default)
+ {
+ using var memoryMappedFile = MemoryMappedFile.CreateFromFile(fileName, FileMode.Open, null, 0, MemoryMappedFileAccess.Read);
+ var view = memoryMappedFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read);
+ try
+ {
+ // read magic "\0asm"
+ if (view.ReadUInt32(0) != WASM_MAGIC)
+ return null;
+
+ // read version
+ if (view.ReadUInt32(4) != 1)
+ return null;
+
+ using var stream = view.AsStream();
+ using var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
+
+ stream.Position += 8;
+
+ long metadataOffset = -1;
+ List sections = new List();
+
+ while (stream.Position < stream.Length)
+ {
+ WasmSectionId id = (WasmSectionId)reader.ReadByte();
+ uint size = reader.ReadULEB128();
+ sections.Add(new WasmSection(id, stream.Position, size, view));
+
+ if (id == WasmSectionId.Custom && size == 0)
+ {
+ break;
+ }
+ stream.Seek(size, SeekOrigin.Current);
+ }
+
+ foreach (var section in sections)
+ {
+ if (section.Id != WasmSectionId.Data || metadataOffset > -1)
+ continue;
+
+ stream.Seek(section.Offset, SeekOrigin.Begin);
+
+ uint numSegments = reader.ReadULEB128();
+ if (numSegments != 2)
+ continue;
+
+ // skip the first segment
+ if (reader.ReadByte() != 1)
+ continue;
+
+ long segmentLength = reader.ReadULEB128();
+ long segmentStart = reader.BaseStream.Position;
+
+ reader.BaseStream.Seek(segmentLength, SeekOrigin.Current);
+
+ if (reader.ReadByte() != 1)
+ continue;
+
+ segmentLength = reader.ReadULEB128();
+ if (TryReadWebCilSegment(reader, out var header, out metadataOffset, out var webcilOffset, out var sectionHeaders))
+ {
+ stream.Seek(metadataOffset, SeekOrigin.Begin);
+ var metadata = MetadataReaderProvider.FromMetadataStream(stream, MetadataStreamOptions.LeaveOpen | MetadataStreamOptions.PrefetchMetadata);
+
+ var result = new WebCilFile(fileName, webcilOffset, metadataOffset, view, ImmutableArray.Create(sectionHeaders), sections.ToImmutableArray(), metadata, metadataOptions);
+
+ view = null; // don't dispose the view, we're still using it in the sections
+ return result;
+ }
+ }
+
+ return null;
+ }
+ finally
+ {
+ view?.Dispose();
+ }
+ }
+
+ static unsafe bool TryReadWebCilSegment(BinaryReader reader, out WebcilHeader webcilHeader, out long metadataOffset, out long webcilOffset, [NotNullWhen(true)] out SectionHeader[]? sectionHeaders)
+ {
+ webcilHeader = default;
+ metadataOffset = -1;
+ sectionHeaders = null;
+
+ webcilOffset = reader.BaseStream.Position;
+
+ if (reader.ReadUInt32() != WEBCIL_MAGIC)
+ return false;
+
+ webcilHeader.VersionMajor = reader.ReadUInt16();
+ webcilHeader.VersionMinor = reader.ReadUInt16();
+ webcilHeader.CoffSections = reader.ReadUInt16();
+ _ = reader.ReadUInt16(); // reserved0
+ webcilHeader.PECliHeaderRVA = reader.ReadUInt32();
+ webcilHeader.PECliHeaderSize = reader.ReadUInt32();
+ webcilHeader.PEDebugRVA = reader.ReadUInt32();
+ webcilHeader.PEDebugSize = reader.ReadUInt32();
+
+ sectionHeaders = new SectionHeader[webcilHeader.CoffSections];
+ for (int i = 0; i < webcilHeader.CoffSections; i++)
+ {
+ sectionHeaders[i].VirtualSize = reader.ReadUInt32();
+ sectionHeaders[i].VirtualAddress = reader.ReadUInt32();
+ sectionHeaders[i].RawDataSize = reader.ReadUInt32();
+ sectionHeaders[i].RawDataPtr = reader.ReadUInt32();
+ }
+
+ long corHeaderStart = TranslateRVA(sectionHeaders, webcilOffset, webcilHeader.PECliHeaderRVA);
+ if (reader.BaseStream.Seek(corHeaderStart, SeekOrigin.Begin) != corHeaderStart)
+ return false;
+ int byteCount = reader.ReadInt32();
+ int majorVersion = reader.ReadUInt16();
+ int minorVersion = reader.ReadUInt16();
+ metadataOffset = TranslateRVA(sectionHeaders, webcilOffset, (uint)reader.ReadInt32());
+ return reader.BaseStream.Seek(metadataOffset, SeekOrigin.Begin) == metadataOffset;
+ }
+
+ public override int MetadataOffset { get; }
+
+ private static int GetContainingSectionIndex(IEnumerable sections, int rva)
+ {
+ int i = 0;
+ foreach (var section in sections)
+ {
+ if (rva >= section.VirtualAddress && rva < section.VirtualAddress + section.VirtualSize)
+ {
+ return i;
+ }
+ i++;
+ }
+ return -1;
+ }
+
+ private static long TranslateRVA(IEnumerable sections, long webcilOffset, uint rva)
+ {
+ foreach (var section in sections)
+ {
+ if (rva >= section.VirtualAddress && rva < section.VirtualAddress + section.VirtualSize)
+ {
+ return section.RawDataPtr + (rva - section.VirtualAddress) + webcilOffset;
+ }
+ }
+ throw new BadImageFormatException("RVA not found in any section");
+ }
+
+ public override MethodBodyBlock GetMethodBody(int rva)
+ {
+ var reader = GetSectionData(rva).GetReader();
+ return MethodBodyBlock.Create(reader);
+ }
+
+ public override int GetContainingSectionIndex(int rva)
+ {
+ return GetContainingSectionIndex(SectionHeaders, rva);
+ }
+
+ public override unsafe SectionData GetSectionData(int rva)
+ {
+ foreach (var section in SectionHeaders)
+ {
+ if (rva >= section.VirtualAddress && rva < section.VirtualAddress + section.VirtualSize)
+ {
+ byte* ptr = (byte*)0;
+ view.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
+ return new SectionData(ptr + section.RawDataPtr + webcilOffset + (rva - section.VirtualAddress), (int)section.RawDataSize);
+ }
+ }
+ throw new BadImageFormatException("RVA not found in any section");
+ }
+
+ public override ImmutableArray SectionHeaders { get; }
+
+ public ImmutableArray WasmSections { get; }
+
+ IModule? IModuleReference.Resolve(ITypeResolveContext context)
+ {
+ return new MetadataModule(context.Compilation, this, TypeSystemOptions.Default);
+ }
+
+ public void Dispose()
+ {
+ view.Dispose();
+ }
+
+ public struct WebcilHeader
+ {
+ public ushort VersionMajor;
+ public ushort VersionMinor;
+ public ushort CoffSections;
+ public uint PECliHeaderRVA;
+ public uint PECliHeaderSize;
+ public uint PEDebugRVA;
+ public uint PEDebugSize;
+ }
+
+ const uint WASM_MAGIC = 0x6d736100u; // "\0asm"
+ const uint WEBCIL_MAGIC = 0x4c496257u; // "WbIL"
+
+ [DebuggerDisplay("WasmSection {Id}: {Offset} {Size}")]
+ public class WasmSection
+ {
+ public WasmSectionId Id;
+ public long Offset;
+ public uint Size;
+ private MemoryMappedViewAccessor view;
+
+ public WasmSection(WasmSectionId id, long offset, uint size, MemoryMappedViewAccessor view)
+ {
+ this.Id = id;
+ this.Size = size;
+ this.Offset = offset;
+ this.view = view;
+ }
+ }
+
+ public enum WasmSectionId : byte
+ {
+ // order matters: enum values must match the WebAssembly spec
+ Custom = 0,
+ Type = 1,
+ Import = 2,
+ Function = 3,
+ Table = 4,
+ Memory = 5,
+ Global = 6,
+ Export = 7,
+ Start = 8,
+ Element = 9,
+ Code = 10,
+ Data = 11,
+ DataCount = 12,
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/SRMExtensions.cs b/ICSharpCode.Decompiler/SRMExtensions.cs
index a86e455253..e461272d0f 100644
--- a/ICSharpCode.Decompiler/SRMExtensions.cs
+++ b/ICSharpCode.Decompiler/SRMExtensions.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Immutable;
+using System.IO;
+using System.IO.MemoryMappedFiles;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
@@ -748,5 +750,11 @@ public static string ToILSyntax(this SignatureCallingConvention callConv)
_ => callConv.ToString().ToLowerInvariant()
};
}
+
+ public static UnmanagedMemoryStream AsStream(this MemoryMappedViewAccessor view)
+ {
+ long size = checked((long)view.SafeMemoryMappedViewHandle.ByteLength);
+ return new UnmanagedMemoryStream(view.SafeMemoryMappedViewHandle, 0, size);
+ }
}
}
diff --git a/ICSharpCode.Decompiler/SingleFileBundle.cs b/ICSharpCode.Decompiler/SingleFileBundle.cs
index c92af8b352..b898639edd 100644
--- a/ICSharpCode.Decompiler/SingleFileBundle.cs
+++ b/ICSharpCode.Decompiler/SingleFileBundle.cs
@@ -104,18 +104,12 @@ public struct Entry
public string RelativePath; // Path of an embedded file, relative to the Bundle source-directory.
}
- static UnmanagedMemoryStream AsStream(MemoryMappedViewAccessor view)
- {
- long size = checked((long)view.SafeMemoryMappedViewHandle.ByteLength);
- return new UnmanagedMemoryStream(view.SafeMemoryMappedViewHandle, 0, size);
- }
-
///
/// Reads the manifest header from the memory mapping.
///
public static Header ReadManifest(MemoryMappedViewAccessor view, long bundleHeaderOffset)
{
- using var stream = AsStream(view);
+ using var stream = view.AsStream();
stream.Seek(bundleHeaderOffset, SeekOrigin.Begin);
return ReadManifest(stream);
}
diff --git a/ICSharpCode.ILSpyX/LoadedAssembly.cs b/ICSharpCode.ILSpyX/LoadedAssembly.cs
index 6ae72866f6..19509df007 100644
--- a/ICSharpCode.ILSpyX/LoadedAssembly.cs
+++ b/ICSharpCode.ILSpyX/LoadedAssembly.cs
@@ -383,6 +383,16 @@ async Task LoadAsync(Task? streamTask)
bundle.LoadedAssembly = this;
return new LoadResult(loadAssemblyException, bundle);
}
+ // If it's not a .NET module, maybe it's a WASM module
+ var wasm = WebCilFile.FromStream(fileName);
+ if (wasm != null)
+ {
+ lock (loadedAssemblies)
+ {
+ loadedAssemblies.Add(wasm, this);
+ }
+ return new LoadResult(loadAssemblyException, wasm);
+ }
// If it's not a .NET module, maybe it's a zip archive (e.g. .nupkg)
try
{
diff --git a/ILSpy/Images/Images.cs b/ILSpy/Images/Images.cs
index 5b48f10bc8..49dfde0fd6 100644
--- a/ILSpy/Images/Images.cs
+++ b/ILSpy/Images/Images.cs
@@ -59,6 +59,7 @@ static ImageSource Load(string icon)
public static readonly ImageSource ReferenceFolder = Load("ReferenceFolder");
public static readonly ImageSource NuGet = Load(null, "Images/NuGet.png");
public static readonly ImageSource MetadataFile = Load("MetadataFile");
+ public static readonly ImageSource WebAssemblyFile = Load("WebAssembly");
public static readonly ImageSource ProgramDebugDatabase = Load("ProgramDebugDatabase");
public static readonly ImageSource Metadata = Load("Metadata");
diff --git a/ILSpy/Images/WebAssembly.svg b/ILSpy/Images/WebAssembly.svg
new file mode 100644
index 0000000000..f2d67d77a3
--- /dev/null
+++ b/ILSpy/Images/WebAssembly.svg
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/ILSpy/Images/WebAssembly.xaml b/ILSpy/Images/WebAssembly.xaml
new file mode 100644
index 0000000000..302c838256
--- /dev/null
+++ b/ILSpy/Images/WebAssembly.xaml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs
index cd8dc1c441..c42facb8f2 100644
--- a/ILSpy/MainWindow.xaml.cs
+++ b/ILSpy/MainWindow.xaml.cs
@@ -1385,7 +1385,7 @@ void OpenCommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
e.Handled = true;
OpenFileDialog dlg = new OpenFileDialog();
- dlg.Filter = ".NET assemblies|*.dll;*.exe;*.winmd|Nuget Packages (*.nupkg)|*.nupkg|Portable Program Database (*.pdb)|*.pdb|All files|*.*";
+ dlg.Filter = ".NET assemblies|*.dll;*.exe;*.winmd;*.wasm|Nuget Packages (*.nupkg)|*.nupkg|Portable Program Database (*.pdb)|*.pdb|All files|*.*";
dlg.Multiselect = true;
dlg.RestoreDirectory = true;
if (dlg.ShowDialog() == true)
diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs
index 7d4c2dd5f1..112b01cc42 100644
--- a/ILSpy/TreeNodes/AssemblyTreeNode.cs
+++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs
@@ -103,6 +103,7 @@ public override object Icon {
return loadResult.MetadataFile.Kind switch {
MetadataFile.MetadataFileKind.PortableExecutable => Images.Assembly,
MetadataFile.MetadataFileKind.ProgramDebugDatabase => Images.ProgramDebugDatabase,
+ MetadataFile.MetadataFileKind.WebCIL => Images.WebAssemblyFile,
_ => Images.MetadataFile,
};
}
@@ -203,6 +204,9 @@ protected override void LoadChildren()
case MetadataFile.MetadataFileKind.PortableExecutable:
LoadChildrenForPEFile(loadResult.PEFile);
break;
+ case MetadataFile.MetadataFileKind.WebCIL:
+ LoadChildrenForWebCilFile((WebCilFile)loadResult.MetadataFile);
+ break;
default:
var metadata = loadResult.MetadataFile;
this.Children.Add(new MetadataTablesTreeNode(metadata));
@@ -291,6 +295,72 @@ NamespaceTreeNode GetOrCreateNamespaceTreeNode(string @namespace)
}
}
+ void LoadChildrenForWebCilFile(WebCilFile module)
+ {
+ typeSystem = LoadedAssembly.GetTypeSystemOrNull();
+ var assembly = (MetadataModule)typeSystem.MainModule;
+ this.Children.Add(new MetadataTreeNode(module, Resources.Metadata));
+ Decompiler.DebugInfo.IDebugInfoProvider debugInfo = LoadedAssembly.GetDebugInfoOrNull();
+ if (debugInfo is PortableDebugInfoProvider ppdb
+ && ppdb.GetMetadataReader() is System.Reflection.Metadata.MetadataReader reader)
+ {
+ this.Children.Add(new MetadataTreeNode(ppdb.ToMetadataFile(), $"Debug Metadata ({(ppdb.IsEmbedded ? "Embedded" : "From portable PDB")})"));
+ }
+ this.Children.Add(new ReferenceFolderTreeNode(module, this));
+ if (module.Resources.Any())
+ this.Children.Add(new ResourceListTreeNode(module));
+ foreach (NamespaceTreeNode ns in namespaces.Values)
+ {
+ ns.Children.Clear();
+ }
+ namespaces.Clear();
+ bool useNestedStructure = MainWindow.Instance.CurrentDisplaySettings.UseNestedNamespaceNodes;
+ foreach (var type in assembly.TopLevelTypeDefinitions.OrderBy(t => t.ReflectionName, NaturalStringComparer.Instance))
+ {
+ var ns = GetOrCreateNamespaceTreeNode(type.Namespace);
+ TypeTreeNode node = new TypeTreeNode(type, this);
+ typeDict[(TypeDefinitionHandle)type.MetadataToken] = node;
+ ns.Children.Add(node);
+ }
+ foreach (NamespaceTreeNode ns in namespaces.Values
+ .Where(ns => ns.Children.Count > 0 && ns.Parent == null)
+ .OrderBy(n => n.Name, NaturalStringComparer.Instance))
+ {
+ this.Children.Add(ns);
+ SetPublicAPI(ns);
+ }
+
+ NamespaceTreeNode GetOrCreateNamespaceTreeNode(string @namespace)
+ {
+ if (!namespaces.TryGetValue(@namespace, out NamespaceTreeNode ns))
+ {
+ if (useNestedStructure)
+ {
+ int decimalIndex = @namespace.LastIndexOf('.');
+ if (decimalIndex < 0)
+ {
+ var escapedNamespace = Language.EscapeName(@namespace);
+ ns = new NamespaceTreeNode(escapedNamespace);
+ }
+ else
+ {
+ var parentNamespaceTreeNode = GetOrCreateNamespaceTreeNode(@namespace.Substring(0, decimalIndex));
+ var escapedInnerNamespace = Language.EscapeName(@namespace.Substring(decimalIndex + 1));
+ ns = new NamespaceTreeNode(escapedInnerNamespace);
+ parentNamespaceTreeNode.Children.Add(ns);
+ }
+ }
+ else
+ {
+ var escapedNamespace = Language.EscapeName(@namespace);
+ ns = new NamespaceTreeNode(escapedNamespace);
+ }
+ namespaces.Add(@namespace, ns);
+ }
+ return ns;
+ }
+ }
+
private static void SetPublicAPI(NamespaceTreeNode ns)
{
foreach (NamespaceTreeNode innerNamespace in ns.Children.OfType())