From 113e00505d1d438f3ca104f1b937373ccc8519b2 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 26 Aug 2021 20:09:48 +0200 Subject: [PATCH] Fix #2337: Expose simple public XamlDecompiler API. --- ILSpy.BamlDecompiler.Tests/BamlTestRunner.cs | 6 +- .../BamlDecompilationResult.cs | 36 +++++ .../BamlDecompilerSettings.cs | 50 ++++++ .../BamlDecompilerTypeSystem.cs | 144 ++++++++++++++++++ ILSpy.BamlDecompiler/BamlResourceEntryNode.cs | 140 +---------------- .../BamlResourceNodeFactory.cs | 9 +- ILSpy.BamlDecompiler/XamlContext.cs | 6 +- ILSpy.BamlDecompiler/XamlDecompiler.cs | 81 ++++++++-- 8 files changed, 318 insertions(+), 154 deletions(-) create mode 100644 ILSpy.BamlDecompiler/BamlDecompilationResult.cs create mode 100644 ILSpy.BamlDecompiler/BamlDecompilerSettings.cs create mode 100644 ILSpy.BamlDecompiler/BamlDecompilerTypeSystem.cs diff --git a/ILSpy.BamlDecompiler.Tests/BamlTestRunner.cs b/ILSpy.BamlDecompiler.Tests/BamlTestRunner.cs index 7728ede9d..f57910e9f 100644 --- a/ILSpy.BamlDecompiler.Tests/BamlTestRunner.cs +++ b/ILSpy.BamlDecompiler.Tests/BamlTestRunner.cs @@ -168,7 +168,11 @@ namespace ILSpy.BamlDecompiler.Tests var res = module.Resources.First(); Stream bamlStream = LoadBaml(res, name + ".baml"); Assert.IsNotNull(bamlStream); - XDocument document = BamlResourceEntryNode.LoadIntoDocument(module, resolver, bamlStream, CancellationToken.None); + + BamlDecompilerTypeSystem typeSystem = new BamlDecompilerTypeSystem(module, resolver); + var decompiler = new XamlDecompiler(typeSystem, new BamlDecompilerSettings()); + + XDocument document = decompiler.Decompile(bamlStream).Xaml; XamlIsEqual(File.ReadAllText(sourcePath), document.ToString()); } diff --git a/ILSpy.BamlDecompiler/BamlDecompilationResult.cs b/ILSpy.BamlDecompiler/BamlDecompilationResult.cs new file mode 100644 index 000000000..f76d86d9e --- /dev/null +++ b/ILSpy.BamlDecompiler/BamlDecompilationResult.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2021 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.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace ILSpy.BamlDecompiler +{ + public class BamlDecompilationResult + { + public XDocument Xaml { get; } + public List AssemblyReferences { get; } + + public BamlDecompilationResult(XDocument xaml, IEnumerable assemblyReferences) + { + this.Xaml = xaml; + this.AssemblyReferences = assemblyReferences.ToList(); + } + } +} \ No newline at end of file diff --git a/ILSpy.BamlDecompiler/BamlDecompilerSettings.cs b/ILSpy.BamlDecompiler/BamlDecompilerSettings.cs new file mode 100644 index 000000000..464298ad0 --- /dev/null +++ b/ILSpy.BamlDecompiler/BamlDecompilerSettings.cs @@ -0,0 +1,50 @@ +// Copyright (c) 2021 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.ComponentModel; +using System.Runtime.CompilerServices; + +namespace ILSpy.BamlDecompiler +{ + public class BamlDecompilerSettings : INotifyPropertyChanged + { + bool throwOnAssemblyResolveErrors = true; + + [Browsable(false)] + public bool ThrowOnAssemblyResolveErrors { + get { return throwOnAssemblyResolveErrors; } + set { + if (throwOnAssemblyResolveErrors != value) + { + throwOnAssemblyResolveErrors = value; + OnPropertyChanged(); + } + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + } +} \ No newline at end of file diff --git a/ILSpy.BamlDecompiler/BamlDecompilerTypeSystem.cs b/ILSpy.BamlDecompiler/BamlDecompilerTypeSystem.cs new file mode 100644 index 000000000..7276bd047 --- /dev/null +++ b/ILSpy.BamlDecompiler/BamlDecompilerTypeSystem.cs @@ -0,0 +1,144 @@ +// Copyright (c) 2021 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.Linq; +using System.Reflection.Metadata; + +using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.TypeSystem.Implementation; +using ICSharpCode.Decompiler.Util; + +namespace ILSpy.BamlDecompiler +{ + class BamlDecompilerTypeSystem : SimpleCompilation, IDecompilerTypeSystem + { + string[] defaultBamlReferences = new[] { + "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", + "System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", + "WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", + "PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", + "PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", + "PresentationUI, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", + "System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" + }; + + public BamlDecompilerTypeSystem(PEFile mainModule, IAssemblyResolver assemblyResolver) + { + if (mainModule == null) + throw new ArgumentNullException(nameof(mainModule)); + if (assemblyResolver == null) + throw new ArgumentNullException(nameof(assemblyResolver)); + // Load referenced assemblies and type-forwarder references. + // This is necessary to make .NET Core/PCL binaries work better. + var referencedAssemblies = new List(); + var assemblyReferenceQueue = new Queue<(bool IsAssembly, PEFile MainModule, object Reference)>(); + var mainMetadata = mainModule.Metadata; + foreach (var h in mainMetadata.GetModuleReferences()) + { + var moduleRef = mainMetadata.GetModuleReference(h); + var moduleName = mainMetadata.GetString(moduleRef.Name); + foreach (var fileHandle in mainMetadata.AssemblyFiles) + { + var file = mainMetadata.GetAssemblyFile(fileHandle); + if (mainMetadata.StringComparer.Equals(file.Name, moduleName) && file.ContainsMetadata) + { + assemblyReferenceQueue.Enqueue((false, mainModule, moduleName)); + break; + } + } + } + foreach (var refs in mainModule.AssemblyReferences) + { + assemblyReferenceQueue.Enqueue((true, mainModule, refs)); + } + foreach (var bamlReference in defaultBamlReferences) + { + assemblyReferenceQueue.Enqueue((true, mainModule, AssemblyNameReference.Parse(bamlReference))); + } + var comparer = KeyComparer.Create(((bool IsAssembly, PEFile MainModule, object Reference) reference) => + reference.IsAssembly ? "A:" + ((IAssemblyReference)reference.Reference).FullName : + "M:" + reference.Reference); + var processedAssemblyReferences = new HashSet<(bool IsAssembly, PEFile Parent, object Reference)>(comparer); + while (assemblyReferenceQueue.Count > 0) + { + var asmRef = assemblyReferenceQueue.Dequeue(); + if (!processedAssemblyReferences.Add(asmRef)) + continue; + PEFile asm; + if (asmRef.IsAssembly) + { + asm = assemblyResolver.Resolve((IAssemblyReference)asmRef.Reference); + } + else + { + asm = assemblyResolver.ResolveModule(asmRef.MainModule, (string)asmRef.Reference); + } + if (asm != null) + { + referencedAssemblies.Add(asm); + var metadata = asm.Metadata; + foreach (var h in metadata.ExportedTypes) + { + var exportedType = metadata.GetExportedType(h); + switch (exportedType.Implementation.Kind) + { + case HandleKind.AssemblyReference: + assemblyReferenceQueue.Enqueue((true, asm, new ICSharpCode.Decompiler.Metadata.AssemblyReference(asm, (AssemblyReferenceHandle)exportedType.Implementation))); + break; + case HandleKind.AssemblyFile: + var file = metadata.GetAssemblyFile((AssemblyFileHandle)exportedType.Implementation); + assemblyReferenceQueue.Enqueue((false, asm, metadata.GetString(file.Name))); + break; + } + } + } + } + var mainModuleWithOptions = mainModule.WithOptions(TypeSystemOptions.Default); + var referencedAssembliesWithOptions = referencedAssemblies.Select(file => file.WithOptions(TypeSystemOptions.Default)); + // Primitive types are necessary to avoid assertions in ILReader. + // Fallback to MinimalCorlib to provide the primitive types. + if (!HasType(KnownTypeCode.Void) || !HasType(KnownTypeCode.Int32)) + { + Init(mainModule.WithOptions(TypeSystemOptions.Default), referencedAssembliesWithOptions.Concat(new[] { MinimalCorlib.Instance })); + } + else + { + Init(mainModuleWithOptions, referencedAssembliesWithOptions); + } + this.MainModule = (MetadataModule)base.MainModule; + + bool HasType(KnownTypeCode code) + { + TopLevelTypeName name = KnownTypeReference.Get(code).TypeName; + if (mainModule.GetTypeDefinition(name) != null) + return true; + foreach (var file in referencedAssemblies) + { + if (file.GetTypeDefinition(name) != null) + return true; + } + return false; + } + } + + public new MetadataModule MainModule { get; } + } +} \ No newline at end of file diff --git a/ILSpy.BamlDecompiler/BamlResourceEntryNode.cs b/ILSpy.BamlDecompiler/BamlResourceEntryNode.cs index bbb4ddbc8..4c9a93cf8 100644 --- a/ILSpy.BamlDecompiler/BamlResourceEntryNode.cs +++ b/ILSpy.BamlDecompiler/BamlResourceEntryNode.cs @@ -17,27 +17,17 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using System.Xml.Linq; using ICSharpCode.AvalonEdit.Highlighting; -using ICSharpCode.Decompiler.Metadata; -using ICSharpCode.Decompiler.TypeSystem; -using ICSharpCode.Decompiler.TypeSystem.Implementation; -using ICSharpCode.Decompiler.Util; using ICSharpCode.ILSpy; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.ViewModels; -using ILSpy.BamlDecompiler.Baml; - -using SRM = System.Reflection.Metadata; - namespace ILSpy.BamlDecompiler { public sealed class BamlResourceEntryNode : ResourceEntryNode @@ -75,131 +65,11 @@ namespace ILSpy.BamlDecompiler { var asm = this.Ancestors().OfType().First().LoadedAssembly; using var data = OpenStream(); - XDocument xamlDocument = LoadIntoDocument(asm.GetPEFileOrNull(), asm.GetAssemblyResolver(), data, cancellationToken); - output.Write(xamlDocument.ToString()); - } - - internal static XDocument LoadIntoDocument(PEFile module, IAssemblyResolver assemblyResolver, - Stream stream, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - var document = BamlReader.ReadDocument(stream, cancellationToken); - var xaml = new XamlDecompiler().Decompile(new BamlDecompilerTypeSystem(module, assemblyResolver), document, cancellationToken, new BamlDecompilerOptions(), null); - return xaml; - } - - class BamlDecompilerTypeSystem : SimpleCompilation, IDecompilerTypeSystem - { - string[] defaultBamlReferences = new[] { - "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", - "System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", - "WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", - "PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", - "PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", - "PresentationUI, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", - "System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" - }; - - public BamlDecompilerTypeSystem(PEFile mainModule, IAssemblyResolver assemblyResolver) - { - if (mainModule == null) - throw new ArgumentNullException(nameof(mainModule)); - if (assemblyResolver == null) - throw new ArgumentNullException(nameof(assemblyResolver)); - // Load referenced assemblies and type-forwarder references. - // This is necessary to make .NET Core/PCL binaries work better. - var referencedAssemblies = new List(); - var assemblyReferenceQueue = new Queue<(bool IsAssembly, PEFile MainModule, object Reference)>(); - var mainMetadata = mainModule.Metadata; - foreach (var h in mainMetadata.GetModuleReferences()) - { - var moduleRef = mainMetadata.GetModuleReference(h); - var moduleName = mainMetadata.GetString(moduleRef.Name); - foreach (var fileHandle in mainMetadata.AssemblyFiles) - { - var file = mainMetadata.GetAssemblyFile(fileHandle); - if (mainMetadata.StringComparer.Equals(file.Name, moduleName) && file.ContainsMetadata) - { - assemblyReferenceQueue.Enqueue((false, mainModule, moduleName)); - break; - } - } - } - foreach (var refs in mainModule.AssemblyReferences) - { - assemblyReferenceQueue.Enqueue((true, mainModule, refs)); - } - foreach (var bamlReference in defaultBamlReferences) - { - assemblyReferenceQueue.Enqueue((true, mainModule, AssemblyNameReference.Parse(bamlReference))); - } - var comparer = KeyComparer.Create(((bool IsAssembly, PEFile MainModule, object Reference) reference) => - reference.IsAssembly ? "A:" + ((IAssemblyReference)reference.Reference).FullName : - "M:" + reference.Reference); - var processedAssemblyReferences = new HashSet<(bool IsAssembly, PEFile Parent, object Reference)>(comparer); - while (assemblyReferenceQueue.Count > 0) - { - var asmRef = assemblyReferenceQueue.Dequeue(); - if (!processedAssemblyReferences.Add(asmRef)) - continue; - PEFile asm; - if (asmRef.IsAssembly) - { - asm = assemblyResolver.Resolve((IAssemblyReference)asmRef.Reference); - } - else - { - asm = assemblyResolver.ResolveModule(asmRef.MainModule, (string)asmRef.Reference); - } - if (asm != null) - { - referencedAssemblies.Add(asm); - var metadata = asm.Metadata; - foreach (var h in metadata.ExportedTypes) - { - var exportedType = metadata.GetExportedType(h); - switch (exportedType.Implementation.Kind) - { - case SRM.HandleKind.AssemblyReference: - assemblyReferenceQueue.Enqueue((true, asm, new AssemblyReference(asm, (SRM.AssemblyReferenceHandle)exportedType.Implementation))); - break; - case SRM.HandleKind.AssemblyFile: - var file = metadata.GetAssemblyFile((SRM.AssemblyFileHandle)exportedType.Implementation); - assemblyReferenceQueue.Enqueue((false, asm, metadata.GetString(file.Name))); - break; - } - } - } - } - var mainModuleWithOptions = mainModule.WithOptions(TypeSystemOptions.Default); - var referencedAssembliesWithOptions = referencedAssemblies.Select(file => file.WithOptions(TypeSystemOptions.Default)); - // Primitive types are necessary to avoid assertions in ILReader. - // Fallback to MinimalCorlib to provide the primitive types. - if (!HasType(KnownTypeCode.Void) || !HasType(KnownTypeCode.Int32)) - { - Init(mainModule.WithOptions(TypeSystemOptions.Default), referencedAssembliesWithOptions.Concat(new[] { MinimalCorlib.Instance })); - } - else - { - Init(mainModuleWithOptions, referencedAssembliesWithOptions); - } - this.MainModule = (MetadataModule)base.MainModule; - - bool HasType(KnownTypeCode code) - { - TopLevelTypeName name = KnownTypeReference.Get(code).TypeName; - if (mainModule.GetTypeDefinition(name) != null) - return true; - foreach (var file in referencedAssemblies) - { - if (file.GetTypeDefinition(name) != null) - return true; - } - return false; - } - } - - public new MetadataModule MainModule { get; } + BamlDecompilerTypeSystem typeSystem = new BamlDecompilerTypeSystem(asm.GetPEFileOrNull(), asm.GetAssemblyResolver()); + var decompiler = new XamlDecompiler(typeSystem, new BamlDecompilerSettings()); + decompiler.CancellationToken = cancellationToken; + var result = decompiler.Decompile(data); + output.Write(result.Xaml.ToString()); } } } diff --git a/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs b/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs index 107949a3a..f2c0b375c 100644 --- a/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs +++ b/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs @@ -46,9 +46,14 @@ namespace ILSpy.BamlDecompiler public string WriteResourceToFile(LoadedAssembly assembly, string fileName, Stream stream, DecompilationOptions options) { - var document = BamlResourceEntryNode.LoadIntoDocument(assembly.GetPEFileOrNull(), assembly.GetAssemblyResolver(), stream, options.CancellationToken); + BamlDecompilerTypeSystem typeSystem = new BamlDecompilerTypeSystem(assembly.GetPEFileOrNull(), assembly.GetAssemblyResolver()); + var decompiler = new XamlDecompiler(typeSystem, new BamlDecompilerSettings() { + ThrowOnAssemblyResolveErrors = options.DecompilerSettings.ThrowOnAssemblyResolveErrors + }); + decompiler.CancellationToken = options.CancellationToken; fileName = Path.ChangeExtension(fileName, ".xaml"); - document.Save(Path.Combine(options.SaveAsProjectDirectory, fileName)); + var result = decompiler.Decompile(stream); + result.Xaml.Save(Path.Combine(options.SaveAsProjectDirectory, fileName)); return fileName; } } diff --git a/ILSpy.BamlDecompiler/XamlContext.cs b/ILSpy.BamlDecompiler/XamlContext.cs index 4b281bac3..31c010986 100644 --- a/ILSpy.BamlDecompiler/XamlContext.cs +++ b/ILSpy.BamlDecompiler/XamlContext.cs @@ -48,7 +48,7 @@ namespace ILSpy.BamlDecompiler public IDecompilerTypeSystem TypeSystem { get; } public CancellationToken CancellationToken { get; private set; } - public BamlDecompilerOptions BamlDecompilerOptions { get; private set; } + public BamlDecompilerSettings Settings { get; private set; } public BamlContext Baml { get; private set; } public BamlNode RootNode { get; private set; } @@ -56,11 +56,11 @@ namespace ILSpy.BamlDecompiler public XmlnsDictionary XmlNs { get; } - public static XamlContext Construct(IDecompilerTypeSystem typeSystem, BamlDocument document, CancellationToken token, BamlDecompilerOptions bamlDecompilerOptions) + public static XamlContext Construct(IDecompilerTypeSystem typeSystem, BamlDocument document, CancellationToken token, BamlDecompilerSettings bamlDecompilerOptions) { var ctx = new XamlContext(typeSystem); ctx.CancellationToken = token; - ctx.BamlDecompilerOptions = bamlDecompilerOptions ?? new BamlDecompilerOptions(); + ctx.Settings = bamlDecompilerOptions ?? new BamlDecompilerSettings(); ctx.Baml = BamlContext.ConstructContext(typeSystem, document, token); ctx.RootNode = BamlNode.Parse(document, token); diff --git a/ILSpy.BamlDecompiler/XamlDecompiler.cs b/ILSpy.BamlDecompiler/XamlDecompiler.cs index dafe597f6..84dc908ef 100644 --- a/ILSpy.BamlDecompiler/XamlDecompiler.cs +++ b/ILSpy.BamlDecompiler/XamlDecompiler.cs @@ -20,11 +20,15 @@ THE SOFTWARE. */ -using System.Collections.Generic; +using System; +using System.IO; using System.Linq; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; using System.Threading; using System.Xml.Linq; +using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; using ILSpy.BamlDecompiler.Baml; @@ -32,7 +36,7 @@ using ILSpy.BamlDecompiler.Rewrite; namespace ILSpy.BamlDecompiler { - internal class XamlDecompiler + public class XamlDecompiler { static readonly IRewritePass[] rewritePasses = new IRewritePass[] { new XClassRewritePass(), @@ -42,9 +46,66 @@ namespace ILSpy.BamlDecompiler new DocumentRewritePass(), }; - public XDocument Decompile(IDecompilerTypeSystem typeSystem, BamlDocument document, CancellationToken token, BamlDecompilerOptions bamlDecompilerOptions, List assemblyReferences) + private BamlDecompilerTypeSystem typeSystem; + private BamlDecompilerSettings settings; + private MetadataModule module; + + public BamlDecompilerSettings Settings { + get { return settings; } + set { settings = value; } + } + + public CancellationToken CancellationToken { get; set; } + + public XamlDecompiler(string fileName, BamlDecompilerSettings settings) + : this(CreateTypeSystemFromFile(fileName, settings), settings) + { + } + + public XamlDecompiler(string fileName, IAssemblyResolver assemblyResolver, BamlDecompilerSettings settings) + : this(LoadPEFile(fileName, settings), assemblyResolver, settings) + { + } + + public XamlDecompiler(PEFile module, IAssemblyResolver assemblyResolver, BamlDecompilerSettings settings) + : this(new BamlDecompilerTypeSystem(module, assemblyResolver), settings) + { + } + + internal XamlDecompiler(BamlDecompilerTypeSystem typeSystem, BamlDecompilerSettings settings) { - var ctx = XamlContext.Construct(typeSystem, document, token, bamlDecompilerOptions); + this.typeSystem = typeSystem ?? throw new ArgumentNullException(nameof(typeSystem)); + this.settings = settings; + this.module = typeSystem.MainModule; + if (module.TypeSystemOptions.HasFlag(TypeSystemOptions.Uncached)) + throw new ArgumentException("Cannot use an uncached type system in the decompiler."); + } + + static PEFile LoadPEFile(string fileName, BamlDecompilerSettings settings) + { + return new PEFile( + fileName, + new FileStream(fileName, FileMode.Open, FileAccess.Read), + streamOptions: PEStreamOptions.PrefetchEntireImage, + metadataOptions: MetadataReaderOptions.None + ); + } + + static BamlDecompilerTypeSystem CreateTypeSystemFromFile(string fileName, BamlDecompilerSettings settings) + { + var file = LoadPEFile(fileName, settings); + var resolver = new UniversalAssemblyResolver(fileName, settings.ThrowOnAssemblyResolveErrors, + file.DetectTargetFrameworkId(), file.DetectRuntimePack(), + PEStreamOptions.PrefetchMetadata, + MetadataReaderOptions.None); + return new BamlDecompilerTypeSystem(file, resolver); + } + + public BamlDecompilationResult Decompile(Stream stream) + { + var ct = CancellationToken; + var document = BamlReader.ReadDocument(stream, ct); + var ctx = XamlContext.Construct(typeSystem, document, ct, settings); var handler = HandlerMap.LookupHandler(ctx.RootNode.Type); var elem = handler.Translate(ctx, ctx.RootNode, null); @@ -54,18 +115,12 @@ namespace ILSpy.BamlDecompiler foreach (var pass in rewritePasses) { - token.ThrowIfCancellationRequested(); + ct.ThrowIfCancellationRequested(); pass.Run(ctx, xaml); } - if (assemblyReferences != null) - assemblyReferences.AddRange(ctx.Baml.AssemblyIdMap.Select(a => a.Value.AssemblyFullName)); - - return xaml; + var assemblyReferences = ctx.Baml.AssemblyIdMap.Select(a => a.Value.AssemblyFullName); + return new BamlDecompilationResult(xaml, assemblyReferences); } } - - public class BamlDecompilerOptions - { - } } \ No newline at end of file