Skip to content

Commit

Permalink
Merge pull request #3191 from icsharpcode/file-loaders
Browse files Browse the repository at this point in the history
Add IFileLoader API
  • Loading branch information
siegfriedpammer committed Apr 1, 2024
2 parents 1fca3da + 963ff7c commit 67eade3
Show file tree
Hide file tree
Showing 20 changed files with 433 additions and 167 deletions.
4 changes: 2 additions & 2 deletions ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,8 @@ static async Task RunTest(string outputDir, string fileToTest)

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)
{
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,23 +99,25 @@ public class WholeProjectDecompiler : IProjectInfoProvider
#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)
{
}

protected WholeProjectDecompiler(
DecompilerSettings settings,
Guid projectGuid,
IAssemblyResolver assemblyResolver,
IProjectFileWriter projectWriter,
AssemblyReferenceClassifier assemblyReferenceClassifier,
IDebugInfoProvider debugInfoProvider)
{
Expand All @@ -124,7 +126,7 @@ public WholeProjectDecompiler(IAssemblyResolver assemblyResolver)
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
Expand Down
4 changes: 1 addition & 3 deletions ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@

using Microsoft.Extensions.Hosting;

using NuGet.Versioning;

namespace ICSharpCode.ILSpyCmd
{
[Command(Name = "ilspycmd", Description = "dotnet tool for decompiling .NET assemblies and generating portable PDBs",
Expand Down Expand Up @@ -314,7 +312,7 @@ ProjectId DecompileAsProject(string assemblyFileName, string projectFileName)
{
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);
}
Expand Down
7 changes: 6 additions & 1 deletion ICSharpCode.ILSpyX/AssemblyList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
using System.Xml.Linq;

using ICSharpCode.ILSpyX.Extensions;
using ICSharpCode.ILSpyX.FileLoaders;

namespace ICSharpCode.ILSpyX
{
Expand Down Expand Up @@ -128,6 +129,7 @@ public AssemblyList(AssemblyList list, string newName)

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.
Expand Down Expand Up @@ -279,7 +281,7 @@ public LoadedAssembly OpenAssembly(string file, bool isAutoLoaded = false)
{
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;
});
Expand All @@ -293,6 +295,7 @@ public LoadedAssembly OpenAssembly(string file, Stream? stream, bool isAutoLoade
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;
Expand Down Expand Up @@ -345,6 +348,7 @@ LoadedAssembly OpenAssembly(string file, Func<LoadedAssembly> load)
return null;

var newAsm = new LoadedAssembly(this, file, stream: Task.FromResult<Stream?>(stream),
fileLoaders: manager?.LoaderRegistry,
applyWinRTProjections: ApplyWinRTProjections, useDebugSymbols: UseDebugSymbols);
newAsm.IsAutoLoaded = target.IsAutoLoaded;

Expand Down Expand Up @@ -374,6 +378,7 @@ LoadedAssembly OpenAssembly(string file, Func<LoadedAssembly> load)
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)
Expand Down
3 changes: 3 additions & 0 deletions ICSharpCode.ILSpyX/AssemblyListManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using System.Xml.Linq;

using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.ILSpyX.FileLoaders;
using ICSharpCode.ILSpyX.Settings;

namespace ICSharpCode.ILSpyX
Expand Down Expand Up @@ -59,6 +60,8 @@ public AssemblyListManager(ISettingsProvider settingsProvider)

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.
Expand Down
3 changes: 2 additions & 1 deletion ICSharpCode.ILSpyX/AssemblyListSnapshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Util;
using ICSharpCode.ILSpyX.Extensions;
using ICSharpCode.ILSpyX.FileLoaders;

namespace ICSharpCode.ILSpyX
{
Expand Down Expand Up @@ -156,7 +157,7 @@ public async Task<IList<LoadedAssembly>> GetAllAssembliesAsync()

foreach (var asm in assemblies)
{
LoadedAssembly.LoadResult result;
LoadResult result;
try
{
result = await asm.GetLoadResultAsync().ConfigureAwait(false);
Expand Down
40 changes: 40 additions & 0 deletions ICSharpCode.ILSpyX/FileLoaders/ArchiveFileLoader.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 33 additions & 0 deletions ICSharpCode.ILSpyX/FileLoaders/BundleFileLoader.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 49 additions & 0 deletions ICSharpCode.ILSpyX/FileLoaders/FileLoaderRegistry.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 40 additions & 0 deletions ICSharpCode.ILSpyX/FileLoaders/LoadResult.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 48 additions & 0 deletions ICSharpCode.ILSpyX/FileLoaders/MetadataFileLoader.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}

0 comments on commit 67eade3

Please sign in to comment.