Browse Source

Merge pull request #3191 from icsharpcode/file-loaders

Add IFileLoader API
pull/3195/head
Siegfried Pammer 1 year ago committed by GitHub
parent
commit
67eade3e05
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs
  2. 2
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectFileWriter.cs
  3. 2
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectInfoProvider.cs
  4. 8
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
  5. 4
      ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs
  6. 7
      ICSharpCode.ILSpyX/AssemblyList.cs
  7. 3
      ICSharpCode.ILSpyX/AssemblyListManager.cs
  8. 3
      ICSharpCode.ILSpyX/AssemblyListSnapshot.cs
  9. 40
      ICSharpCode.ILSpyX/FileLoaders/ArchiveFileLoader.cs
  10. 33
      ICSharpCode.ILSpyX/FileLoaders/BundleFileLoader.cs
  11. 49
      ICSharpCode.ILSpyX/FileLoaders/FileLoaderRegistry.cs
  12. 40
      ICSharpCode.ILSpyX/FileLoaders/LoadResult.cs
  13. 48
      ICSharpCode.ILSpyX/FileLoaders/MetadataFileLoader.cs
  14. 40
      ICSharpCode.ILSpyX/FileLoaders/WebCilFileLoader.cs
  15. 73
      ICSharpCode.ILSpyX/FileLoaders/XamarinCompressedFileLoader.cs
  16. 196
      ICSharpCode.ILSpyX/LoadedAssembly.cs
  17. 4
      ICSharpCode.ILSpyX/LoadedPackage.cs
  18. 2
      ILSpy/Languages/CSharpLanguage.cs
  19. 15
      ILSpy/MainWindow.xaml.cs
  20. 15
      ILSpy/TreeNodes/AssemblyTreeNode.cs

4
ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs

@ -289,8 +289,8 @@ namespace ICSharpCode.Decompiler.Tests @@ -289,8 +289,8 @@ namespace ICSharpCode.Decompiler.Tests
class TestProjectDecompiler : WholeProjectDecompiler
{
public TestProjectDecompiler(Guid projecGuid, IAssemblyResolver resolver, AssemblyReferenceClassifier assemblyReferenceClassifier, DecompilerSettings settings)
: base(settings, projecGuid, resolver, assemblyReferenceClassifier, debugInfoProvider: null)
public TestProjectDecompiler(Guid projectGuid, IAssemblyResolver resolver, AssemblyReferenceClassifier assemblyReferenceClassifier, DecompilerSettings settings)
: base(settings, projectGuid, resolver, null, assemblyReferenceClassifier, debugInfoProvider: null)
{
}
}

2
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectFileWriter.cs

@ -27,7 +27,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -27,7 +27,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
/// An interface for a service that creates and writes a project file structure
/// for a specific module being decompiled.
/// </summary>
interface IProjectFileWriter
public interface IProjectFileWriter
{
/// <summary>
/// Writes the content of a new project file for the specified <paramref name="module"/> being decompiled.

2
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectInfoProvider.cs

@ -25,7 +25,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -25,7 +25,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
/// <summary>
/// An interface that provides common information for a project being decompiled to.
/// </summary>
interface IProjectInfoProvider
public interface IProjectInfoProvider
{
/// <summary>
/// Gets the assembly resolver active for the project.

8
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs

@ -99,16 +99,17 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -99,16 +99,17 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
#endregion
public WholeProjectDecompiler(IAssemblyResolver assemblyResolver)
: this(new DecompilerSettings(), assemblyResolver, assemblyReferenceClassifier: null, debugInfoProvider: null)
: this(new DecompilerSettings(), assemblyResolver, projectWriter: null, assemblyReferenceClassifier: null, debugInfoProvider: null)
{
}
public WholeProjectDecompiler(
DecompilerSettings settings,
IAssemblyResolver assemblyResolver,
IProjectFileWriter projectWriter,
AssemblyReferenceClassifier assemblyReferenceClassifier,
IDebugInfoProvider debugInfoProvider)
: this(settings, Guid.NewGuid(), assemblyResolver, assemblyReferenceClassifier, debugInfoProvider)
: this(settings, Guid.NewGuid(), assemblyResolver, projectWriter, assemblyReferenceClassifier, debugInfoProvider)
{
}
@ -116,6 +117,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -116,6 +117,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
DecompilerSettings settings,
Guid projectGuid,
IAssemblyResolver assemblyResolver,
IProjectFileWriter projectWriter,
AssemblyReferenceClassifier assemblyReferenceClassifier,
IDebugInfoProvider debugInfoProvider)
{
@ -124,7 +126,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -124,7 +126,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
AssemblyResolver = assemblyResolver ?? throw new ArgumentNullException(nameof(assemblyResolver));
AssemblyReferenceClassifier = assemblyReferenceClassifier ?? new AssemblyReferenceClassifier();
DebugInfoProvider = debugInfoProvider;
projectWriter = Settings.UseSdkStyleProjectFormat ? ProjectFileWriterSdkStyle.Create() : ProjectFileWriterDefault.Create();
this.projectWriter = projectWriter ?? (Settings.UseSdkStyleProjectFormat ? ProjectFileWriterSdkStyle.Create() : ProjectFileWriterDefault.Create());
}
// per-run members

4
ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs

@ -24,8 +24,6 @@ using McMaster.Extensions.CommandLineUtils; @@ -24,8 +24,6 @@ using McMaster.Extensions.CommandLineUtils;
using Microsoft.Extensions.Hosting;
using NuGet.Versioning;
namespace ICSharpCode.ILSpyCmd
{
[Command(Name = "ilspycmd", Description = "dotnet tool for decompiling .NET assemblies and generating portable PDBs",
@ -314,7 +312,7 @@ Examples: @@ -314,7 +312,7 @@ Examples:
{
resolver.AddSearchDirectory(path);
}
var decompiler = new WholeProjectDecompiler(GetSettings(module), resolver, resolver, TryLoadPDB(module));
var decompiler = new WholeProjectDecompiler(GetSettings(module), resolver, null, resolver, TryLoadPDB(module));
using (var projectFileWriter = new StreamWriter(File.OpenWrite(projectFileName)))
return decompiler.DecompileProject(module, Path.GetDirectoryName(projectFileName), projectFileWriter);
}

7
ICSharpCode.ILSpyX/AssemblyList.cs

@ -30,6 +30,7 @@ using System.Threading.Tasks; @@ -30,6 +30,7 @@ using System.Threading.Tasks;
using System.Xml.Linq;
using ICSharpCode.ILSpyX.Extensions;
using ICSharpCode.ILSpyX.FileLoaders;
namespace ICSharpCode.ILSpyX
{
@ -128,6 +129,7 @@ namespace ICSharpCode.ILSpyX @@ -128,6 +129,7 @@ namespace ICSharpCode.ILSpyX
public bool ApplyWinRTProjections { get; set; }
public bool UseDebugSymbols { get; set; }
public FileLoaderRegistry LoaderRegistry => this.manager.LoaderRegistry;
/// <summary>
/// Gets the loaded assemblies. This method is thread-safe.
@ -279,7 +281,7 @@ namespace ICSharpCode.ILSpyX @@ -279,7 +281,7 @@ namespace ICSharpCode.ILSpyX
{
file = Path.GetFullPath(file);
return OpenAssembly(file, () => {
var newAsm = new LoadedAssembly(this, file, applyWinRTProjections: ApplyWinRTProjections, useDebugSymbols: UseDebugSymbols);
var newAsm = new LoadedAssembly(this, file, fileLoaders: manager?.LoaderRegistry, applyWinRTProjections: ApplyWinRTProjections, useDebugSymbols: UseDebugSymbols);
newAsm.IsAutoLoaded = isAutoLoaded;
return newAsm;
});
@ -293,6 +295,7 @@ namespace ICSharpCode.ILSpyX @@ -293,6 +295,7 @@ namespace ICSharpCode.ILSpyX
file = Path.GetFullPath(file);
return OpenAssembly(file, () => {
var newAsm = new LoadedAssembly(this, file, stream: Task.FromResult(stream),
fileLoaders: manager?.LoaderRegistry,
applyWinRTProjections: ApplyWinRTProjections, useDebugSymbols: UseDebugSymbols);
newAsm.IsAutoLoaded = isAutoLoaded;
return newAsm;
@ -345,6 +348,7 @@ namespace ICSharpCode.ILSpyX @@ -345,6 +348,7 @@ namespace ICSharpCode.ILSpyX
return null;
var newAsm = new LoadedAssembly(this, file, stream: Task.FromResult<Stream?>(stream),
fileLoaders: manager?.LoaderRegistry,
applyWinRTProjections: ApplyWinRTProjections, useDebugSymbols: UseDebugSymbols);
newAsm.IsAutoLoaded = target.IsAutoLoaded;
@ -374,6 +378,7 @@ namespace ICSharpCode.ILSpyX @@ -374,6 +378,7 @@ namespace ICSharpCode.ILSpyX
if (index < 0)
return null;
var newAsm = new LoadedAssembly(this, target.FileName, pdbFileName: target.PdbFileName,
fileLoaders: manager?.LoaderRegistry,
applyWinRTProjections: ApplyWinRTProjections, useDebugSymbols: UseDebugSymbols);
newAsm.IsAutoLoaded = target.IsAutoLoaded;
lock (lockObj)

3
ICSharpCode.ILSpyX/AssemblyListManager.cs

@ -23,6 +23,7 @@ using System.Linq; @@ -23,6 +23,7 @@ using System.Linq;
using System.Xml.Linq;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.ILSpyX.FileLoaders;
using ICSharpCode.ILSpyX.Settings;
namespace ICSharpCode.ILSpyX
@ -59,6 +60,8 @@ namespace ICSharpCode.ILSpyX @@ -59,6 +60,8 @@ namespace ICSharpCode.ILSpyX
public ObservableCollection<string> AssemblyLists { get; } = new ObservableCollection<string>();
public FileLoaderRegistry LoaderRegistry { get; } = new FileLoaderRegistry();
/// <summary>
/// Loads an assembly list from the ILSpySettings.
/// If no list with the specified name is found, the default list is loaded instead.

3
ICSharpCode.ILSpyX/AssemblyListSnapshot.cs

@ -27,6 +27,7 @@ using System.Threading.Tasks; @@ -27,6 +27,7 @@ using System.Threading.Tasks;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Util;
using ICSharpCode.ILSpyX.Extensions;
using ICSharpCode.ILSpyX.FileLoaders;
namespace ICSharpCode.ILSpyX
{
@ -156,7 +157,7 @@ namespace ICSharpCode.ILSpyX @@ -156,7 +157,7 @@ namespace ICSharpCode.ILSpyX
foreach (var asm in assemblies)
{
LoadedAssembly.LoadResult result;
LoadResult result;
try
{
result = await asm.GetLoadResultAsync().ConfigureAwait(false);

40
ICSharpCode.ILSpyX/FileLoaders/ArchiveFileLoader.cs

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
// 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.IO;
using System.Threading.Tasks;
namespace ICSharpCode.ILSpyX.FileLoaders
{
public sealed class ArchiveFileLoader : IFileLoader
{
public Task<LoadResult?> Load(string fileName, Stream stream, FileLoadSettings settings)
{
try
{
var zip = LoadedPackage.FromZipFile(fileName);
var result = zip != null ? new LoadResult { Package = zip } : null;
return Task.FromResult(result);
}
catch (InvalidDataException)
{
return Task.FromResult<LoadResult?>(null);
}
}
}
}

33
ICSharpCode.ILSpyX/FileLoaders/BundleFileLoader.cs

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
// 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.IO;
using System.Threading.Tasks;
namespace ICSharpCode.ILSpyX.FileLoaders
{
public sealed class BundleFileLoader : IFileLoader
{
public Task<LoadResult?> Load(string fileName, Stream stream, FileLoadSettings settings)
{
var bundle = LoadedPackage.FromBundle(fileName);
var result = bundle != null ? new LoadResult { Package = bundle } : null;
return Task.FromResult(result);
}
}
}

49
ICSharpCode.ILSpyX/FileLoaders/FileLoaderRegistry.cs

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
// 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;
namespace ICSharpCode.ILSpyX.FileLoaders
{
public sealed class FileLoaderRegistry
{
readonly List<IFileLoader> registeredLoaders = new List<IFileLoader>();
public IReadOnlyList<IFileLoader> RegisteredLoaders => registeredLoaders;
public void Register(IFileLoader loader)
{
if (loader is null)
{
throw new ArgumentNullException(nameof(loader));
}
registeredLoaders.Add(loader);
}
public FileLoaderRegistry()
{
Register(new XamarinCompressedFileLoader());
Register(new WebCilFileLoader());
Register(new MetadataFileLoader());
Register(new BundleFileLoader());
Register(new ArchiveFileLoader());
}
}
}

40
ICSharpCode.ILSpyX/FileLoaders/LoadResult.cs

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
// 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.IO;
using System.Threading.Tasks;
using ICSharpCode.Decompiler.Metadata;
namespace ICSharpCode.ILSpyX.FileLoaders
{
public sealed class LoadResult
{
public MetadataFile? MetadataFile { get; init; }
public Exception? FileLoadException { get; init; }
public LoadedPackage? Package { get; init; }
}
public record FileLoadSettings(bool ApplyWinRTProjections);
public interface IFileLoader
{
Task<LoadResult?> Load(string fileName, Stream stream, FileLoadSettings settings);
}
}

48
ICSharpCode.ILSpyX/FileLoaders/MetadataFileLoader.cs

@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
// 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.IO;
using System.Reflection.Metadata;
using System.Threading.Tasks;
using ICSharpCode.Decompiler.Metadata;
using static ICSharpCode.Decompiler.Metadata.MetadataFile;
namespace ICSharpCode.ILSpyX.FileLoaders
{
public sealed class MetadataFileLoader : IFileLoader
{
public Task<LoadResult?> Load(string fileName, Stream stream, FileLoadSettings settings)
{
try
{
var kind = Path.GetExtension(fileName).Equals(".pdb", StringComparison.OrdinalIgnoreCase)
? MetadataFileKind.ProgramDebugDatabase : MetadataFileKind.Metadata;
var metadata = MetadataReaderProvider.FromMetadataStream(stream, MetadataStreamOptions.PrefetchMetadata | MetadataStreamOptions.LeaveOpen);
var metadataFile = new MetadataFile(kind, fileName, metadata);
return Task.FromResult<LoadResult?>(new LoadResult { MetadataFile = metadataFile });
}
catch (BadImageFormatException)
{
return Task.FromResult<LoadResult?>(null);
}
}
}
}

40
ICSharpCode.ILSpyX/FileLoaders/WebCilFileLoader.cs

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
// 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.IO;
using System.Reflection.Metadata;
using System.Threading.Tasks;
using ICSharpCode.Decompiler.Metadata;
namespace ICSharpCode.ILSpyX.FileLoaders
{
public sealed class WebCilFileLoader : IFileLoader
{
public Task<LoadResult?> Load(string fileName, Stream stream, FileLoadSettings settings)
{
MetadataReaderOptions options = settings.ApplyWinRTProjections
? MetadataReaderOptions.ApplyWindowsRuntimeProjections
: MetadataReaderOptions.None;
var wasm = WebCilFile.FromStream(fileName, options);
var result = wasm != null ? new LoadResult { MetadataFile = wasm } : null;
return Task.FromResult(result);
}
}
}

73
ICSharpCode.ILSpyX/FileLoaders/XamarinCompressedFileLoader.cs

@ -0,0 +1,73 @@ @@ -0,0 +1,73 @@
// 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.Buffers;
using System.IO;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Text;
using System.Threading.Tasks;
using ICSharpCode.Decompiler.Metadata;
using K4os.Compression.LZ4;
namespace ICSharpCode.ILSpyX.FileLoaders
{
public sealed class XamarinCompressedFileLoader : IFileLoader
{
public async Task<LoadResult?> Load(string fileName, Stream stream, FileLoadSettings settings)
{
const uint CompressedDataMagic = 0x5A4C4158; // Magic used for Xamarin compressed module header ('XALZ', little-endian)
using var fileReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
// Read compressed file header
var magic = fileReader.ReadUInt32();
if (magic != CompressedDataMagic)
return null;
_ = fileReader.ReadUInt32(); // skip index into descriptor table, unused
int uncompressedLength = (int)fileReader.ReadUInt32();
int compressedLength = (int)stream.Length; // Ensure we read all of compressed data
ArrayPool<byte> pool = ArrayPool<byte>.Shared;
var src = pool.Rent(compressedLength);
var dst = pool.Rent(uncompressedLength);
try
{
// fileReader stream position is now at compressed module data
await stream.ReadAsync(src, 0, compressedLength).ConfigureAwait(false);
// Decompress
LZ4Codec.Decode(src, 0, compressedLength, dst, 0, uncompressedLength);
// Load module from decompressed data buffer
using (var uncompressedStream = new MemoryStream(dst, writable: false))
{
MetadataReaderOptions options = settings.ApplyWinRTProjections
? MetadataReaderOptions.ApplyWindowsRuntimeProjections
: MetadataReaderOptions.None;
return new LoadResult {
MetadataFile = new PEFile(fileName, stream, PEStreamOptions.PrefetchEntireImage, metadataOptions: options)
};
}
}
finally
{
pool.Return(dst);
pool.Return(src);
}
}
}
}

196
ICSharpCode.ILSpyX/LoadedAssembly.cs

@ -17,7 +17,6 @@ @@ -17,7 +17,6 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.Buffers;
using System.Diagnostics;
using System.IO;
using System.Reflection.Metadata;
@ -31,12 +30,9 @@ using ICSharpCode.Decompiler.Metadata; @@ -31,12 +30,9 @@ using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
using ICSharpCode.Decompiler.Util;
using ICSharpCode.ILSpyX.FileLoaders;
using ICSharpCode.ILSpyX.PdbProvider;
using K4os.Compression.LZ4;
using static ICSharpCode.Decompiler.Metadata.MetadataFile;
#nullable enable
@ -65,47 +61,29 @@ namespace ICSharpCode.ILSpyX @@ -65,47 +61,29 @@ namespace ICSharpCode.ILSpyX
/// </summary>
internal static readonly ConditionalWeakTable<MetadataFile, LoadedAssembly> loadedAssemblies = new ConditionalWeakTable<MetadataFile, LoadedAssembly>();
public sealed class LoadResult
{
public MetadataFile? MetadataFile { get; }
public PEFile? PEFile => MetadataFile as PEFile;
public Exception? FileLoadException { get; }
public LoadedPackage? Package { get; }
public LoadResult(PEFile peFile)
{
this.MetadataFile = peFile ?? throw new ArgumentNullException(nameof(peFile));
}
public LoadResult(Exception fileLoadException, LoadedPackage package)
{
this.FileLoadException = fileLoadException ?? throw new ArgumentNullException(nameof(fileLoadException));
this.Package = package ?? throw new ArgumentNullException(nameof(package));
}
public LoadResult(Exception fileLoadException, MetadataFile metadataFile)
{
this.FileLoadException = fileLoadException ?? throw new ArgumentNullException(nameof(fileLoadException));
this.MetadataFile = metadataFile ?? throw new ArgumentNullException(nameof(metadataFile));
}
}
readonly Task<LoadResult> loadingTask;
readonly AssemblyList assemblyList;
readonly string fileName;
readonly string shortName;
readonly IAssemblyResolver? providedAssemblyResolver;
readonly FileLoaderRegistry? fileLoaders;
readonly bool applyWinRTProjections;
readonly bool useDebugSymbols;
public LoadedAssembly? ParentBundle { get; }
public LoadedAssembly(AssemblyList assemblyList, string fileName,
Task<Stream?>? stream = null, IAssemblyResolver? assemblyResolver = null, string? pdbFileName = null,
Task<Stream?>? stream = null,
FileLoaderRegistry? fileLoaders = null,
IAssemblyResolver? assemblyResolver = null,
string? pdbFileName = null,
bool applyWinRTProjections = false, bool useDebugSymbols = false)
{
this.assemblyList = assemblyList ?? throw new ArgumentNullException(nameof(assemblyList));
this.fileName = fileName ?? throw new ArgumentNullException(nameof(fileName));
this.PdbFileName = pdbFileName;
this.providedAssemblyResolver = assemblyResolver;
this.fileLoaders = fileLoaders;
this.applyWinRTProjections = applyWinRTProjections;
this.useDebugSymbols = useDebugSymbols;
@ -114,9 +92,10 @@ namespace ICSharpCode.ILSpyX @@ -114,9 +92,10 @@ namespace ICSharpCode.ILSpyX
}
public LoadedAssembly(LoadedAssembly bundle, string fileName, Task<Stream?>? stream,
FileLoaderRegistry? fileLoaders = null,
IAssemblyResolver? assemblyResolver = null,
bool applyWinRTProjections = false, bool useDebugSymbols = false)
: this(bundle.assemblyList, fileName, stream, assemblyResolver, null,
: this(bundle.assemblyList, fileName, stream, fileLoaders, assemblyResolver, null,
applyWinRTProjections, useDebugSymbols)
{
this.ParentBundle = bundle;
@ -180,7 +159,7 @@ namespace ICSharpCode.ILSpyX @@ -180,7 +159,7 @@ namespace ICSharpCode.ILSpyX
if (loadResult.MetadataFile != null)
return loadResult.MetadataFile;
else
throw loadResult.FileLoadException!;
throw loadResult.FileLoadException ?? new MetadataFileNotSupportedException();
}
/// <summary>
@ -334,145 +313,84 @@ namespace ICSharpCode.ILSpyX @@ -334,145 +313,84 @@ namespace ICSharpCode.ILSpyX
async Task<LoadResult> LoadAsync(Task<Stream?>? streamTask)
{
// runs on background thread
var stream = streamTask != null ? await streamTask.ConfigureAwait(false) : null;
if (stream != null)
{
// Read the module from a precrafted stream
if (!stream.CanSeek)
using var stream = await PrepareStream();
FileLoadSettings settings = new FileLoadSettings(applyWinRTProjections);
LoadResult? result = null;
if (fileLoaders != null)
{
var memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
stream.Close();
memoryStream.Position = 0;
stream = memoryStream;
}
var streamOptions = stream is MemoryStream ? PEStreamOptions.PrefetchEntireImage : PEStreamOptions.Default;
return LoadAssembly(stream, streamOptions, applyWinRTProjections);
}
// Read the module from disk
Exception loadAssemblyException;
try
foreach (var loader in fileLoaders.RegisteredLoaders)
{
using (var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
stream.Position = 0;
result = await loader.Load(fileName, stream, settings).ConfigureAwait(false);
if (result != null)
{
return LoadAssembly(fileStream, PEStreamOptions.PrefetchEntireImage, applyWinRTProjections);
break;
}
}
catch (MetadataFileNotSupportedException ex)
{
loadAssemblyException = ex;
}
catch (BadImageFormatException ex)
if (result == null)
{
loadAssemblyException = ex;
}
// Maybe its a compressed Xamarin/Mono assembly, see https://github.com/xamarin/xamarin-android/pull/4686
stream.Position = 0;
try
{
return LoadCompressedAssembly(fileName);
MetadataReaderOptions options = applyWinRTProjections
? MetadataReaderOptions.ApplyWindowsRuntimeProjections
: MetadataReaderOptions.None;
PEFile module = new PEFile(fileName, stream, PEStreamOptions.PrefetchEntireImage, metadataOptions: options);
result = new LoadResult { MetadataFile = module };
}
catch (InvalidDataException)
catch (Exception ex)
{
// Not a compressed module, try other options below
result = new LoadResult { FileLoadException = ex };
}
// If it's not a .NET module, maybe it's a single-file bundle
var bundle = LoadedPackage.FromBundle(fileName);
if (bundle != null)
{
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)
if (result.MetadataFile != 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
{
var zip = LoadedPackage.FromZipFile(fileName);
zip.LoadedAssembly = this;
return new LoadResult(loadAssemblyException, zip);
loadedAssemblies.Add(result.MetadataFile, this);
}
catch (InvalidDataException)
{
// Not a compressed module, try other options below
}
// or it could be a standalone portable PDB
try
{
using (var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
var kind = Path.GetExtension(fileName).Equals(".pdb", StringComparison.OrdinalIgnoreCase) ? MetadataFileKind.ProgramDebugDatabase : MetadataFileKind.Metadata;
var metadata = MetadataReaderProvider.FromMetadataStream(fileStream, MetadataStreamOptions.PrefetchMetadata);
var metadataFile = new MetadataFile(kind, fileName, metadata);
lock (loadedAssemblies)
if (result.MetadataFile is PEFile module)
{
loadedAssemblies.Add(metadataFile, this);
}
return new LoadResult(loadAssemblyException, metadataFile);
debugInfoProvider = LoadDebugInfo(module);
}
}
catch (Exception)
else if (result.Package != null)
{
throw loadAssemblyException;
}
result.Package.LoadedAssembly = this;
}
LoadResult LoadAssembly(Stream stream, PEStreamOptions streamOptions, bool applyWinRTProjections)
{
MetadataReaderOptions options = applyWinRTProjections
? MetadataReaderOptions.ApplyWindowsRuntimeProjections
: MetadataReaderOptions.None;
PEFile module = new PEFile(fileName, stream, streamOptions, metadataOptions: options);
debugInfoProvider = LoadDebugInfo(module);
lock (loadedAssemblies)
else if (result.FileLoadException != null)
{
loadedAssemblies.Add(module, this);
}
return new LoadResult(module);
throw result.FileLoadException;
}
return result;
LoadResult LoadCompressedAssembly(string fileName)
{
const uint CompressedDataMagic = 0x5A4C4158; // Magic used for Xamarin compressed module header ('XALZ', little-endian)
using (var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
using (var fileReader = new BinaryReader(fileStream))
async Task<Stream> PrepareStream()
{
// Read compressed file header
var magic = fileReader.ReadUInt32();
if (magic != CompressedDataMagic)
throw new InvalidDataException($"Xamarin compressed module header magic {magic} does not match expected {CompressedDataMagic}");
_ = fileReader.ReadUInt32(); // skip index into descriptor table, unused
int uncompressedLength = (int)fileReader.ReadUInt32();
int compressedLength = (int)fileStream.Length; // Ensure we read all of compressed data
ArrayPool<byte> pool = ArrayPool<byte>.Shared;
var src = pool.Rent(compressedLength);
var dst = pool.Rent(uncompressedLength);
try
// runs on background thread
var stream = streamTask != null ? await streamTask.ConfigureAwait(false) : null;
if (stream != null)
{
// fileReader stream position is now at compressed module data
fileStream.Read(src, 0, compressedLength);
// Decompress
LZ4Codec.Decode(src, 0, compressedLength, dst, 0, uncompressedLength);
// Load module from decompressed data buffer
using (var uncompressedStream = new MemoryStream(dst, writable: false))
// Read the module from a precrafted stream
if (!stream.CanSeek)
{
return LoadAssembly(uncompressedStream, PEStreamOptions.PrefetchEntireImage, applyWinRTProjections);
var memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
stream.Close();
memoryStream.Position = 0;
stream = memoryStream;
}
return stream;
}
finally
else
{
pool.Return(dst);
pool.Return(src);
return new FileStream(fileName, FileMode.Open, FileAccess.Read);
}
}
}

4
ICSharpCode.ILSpyX/LoadedPackage.cs

@ -71,8 +71,11 @@ namespace ICSharpCode.ILSpyX @@ -71,8 +71,11 @@ namespace ICSharpCode.ILSpyX
foreach (var entry in this.Entries)
{
var (dirname, filename) = SplitName(entry.Name);
if (!string.IsNullOrEmpty(filename))
{
GetFolder(dirname).Entries.Add(new FolderEntry(filename, entry));
}
}
this.RootFolder = rootFolder;
static (string, string) SplitName(string filename)
@ -336,6 +339,7 @@ namespace ICSharpCode.ILSpyX @@ -336,6 +339,7 @@ namespace ICSharpCode.ILSpyX
{
asm = new LoadedAssembly(
package.LoadedAssembly, entry.Name,
fileLoaders: package.LoadedAssembly.AssemblyList.LoaderRegistry,
assemblyResolver: this,
stream: Task.Run(entry.TryOpenStream),
applyWinRTProjections: package.LoadedAssembly.AssemblyList.ApplyWinRTProjections,

2
ILSpy/Languages/CSharpLanguage.cs

@ -518,7 +518,7 @@ namespace ICSharpCode.ILSpy @@ -518,7 +518,7 @@ namespace ICSharpCode.ILSpy
readonly DecompilationOptions options;
public ILSpyWholeProjectDecompiler(LoadedAssembly assembly, DecompilationOptions options)
: base(options.DecompilerSettings, assembly.GetAssemblyResolver(options.DecompilerSettings.AutoLoadAssemblyReferences, options.DecompilerSettings.ApplyWindowsRuntimeProjections), assembly.GetAssemblyReferenceClassifier(options.DecompilerSettings.ApplyWindowsRuntimeProjections), assembly.GetDebugInfoOrNull())
: base(options.DecompilerSettings, assembly.GetAssemblyResolver(options.DecompilerSettings.AutoLoadAssemblyReferences, options.DecompilerSettings.ApplyWindowsRuntimeProjections), null, assembly.GetAssemblyReferenceClassifier(options.DecompilerSettings.ApplyWindowsRuntimeProjections), assembly.GetDebugInfoOrNull())
{
this.assembly = assembly;
this.options = options;

15
ILSpy/MainWindow.xaml.cs

@ -54,6 +54,7 @@ using ICSharpCode.ILSpy.TreeNodes; @@ -54,6 +54,7 @@ using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpy.Updates;
using ICSharpCode.ILSpy.ViewModels;
using ICSharpCode.ILSpyX;
using ICSharpCode.ILSpyX.FileLoaders;
using ICSharpCode.ILSpyX.Settings;
using ICSharpCode.TreeView;
@ -152,6 +153,7 @@ namespace ICSharpCode.ILSpy @@ -152,6 +153,7 @@ namespace ICSharpCode.ILSpy
InitMainMenu();
InitWindowMenu();
InitToolbar();
InitFileLoaders();
ContextMenuProvider.Add(AssemblyTreeView);
this.Loaded += MainWindow_Loaded;
@ -579,6 +581,19 @@ namespace ICSharpCode.ILSpy @@ -579,6 +581,19 @@ namespace ICSharpCode.ILSpy
}
#endregion
#region File Loader extensibility
void InitFileLoaders()
{
// TODO
foreach (var loader in App.ExportProvider.GetExportedValues<IFileLoader>())
{
}
}
#endregion
#region Message Hook
protected override void OnSourceInitialized(EventArgs e)
{

15
ILSpy/TreeNodes/AssemblyTreeNode.cs

@ -33,6 +33,7 @@ using ICSharpCode.ILSpy.Metadata; @@ -33,6 +33,7 @@ using ICSharpCode.ILSpy.Metadata;
using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpy.ViewModels;
using ICSharpCode.ILSpyX;
using ICSharpCode.ILSpyX.FileLoaders;
using ICSharpCode.ILSpyX.PdbProvider;
using ICSharpCode.TreeView;
@ -188,7 +189,7 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -188,7 +189,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
protected override void LoadChildren()
{
LoadedAssembly.LoadResult loadResult;
LoadResult loadResult;
try
{
loadResult = LoadedAssembly.GetLoadResultAsync().GetAwaiter().GetResult();
@ -205,7 +206,7 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -205,7 +206,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
switch (loadResult.MetadataFile.Kind)
{
case MetadataFile.MetadataFileKind.PortableExecutable:
LoadChildrenForPEFile(loadResult.PEFile);
LoadChildrenForPEFile(loadResult.MetadataFile);
break;
case MetadataFile.MetadataFileKind.WebCIL:
LoadChildrenForWebCilFile((WebCilFile)loadResult.MetadataFile);
@ -462,7 +463,7 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -462,7 +463,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
try
{
var loadResult = LoadedAssembly.GetLoadResultAsync().GetAwaiter().GetResult();
if (loadResult.PEFile != null)
if (loadResult.MetadataFile != null)
{
language.DecompileAssembly(LoadedAssembly, output, options);
}
@ -471,13 +472,9 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -471,13 +472,9 @@ namespace ICSharpCode.ILSpy.TreeNodes
output.WriteLine("// " + LoadedAssembly.FileName);
DecompilePackage(loadResult.Package, output);
}
else if (loadResult.MetadataFile != null)
{
output.WriteLine("// " + LoadedAssembly.FileName);
}
else
else if (loadResult.FileLoadException != null)
{
LoadedAssembly.GetMetadataFileOrNullAsync().GetAwaiter().GetResult();
HandleException(loadResult.FileLoadException, loadResult.FileLoadException.Message);
}
}
catch (BadImageFormatException badImage)

Loading…
Cancel
Save