|
|
|
@ -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); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|