diff --git a/IL.View/IL.View.csproj b/IL.View/IL.View.csproj index 76ddbcb..275d5ed 100644 --- a/IL.View/IL.View.csproj +++ b/IL.View/IL.View.csproj @@ -130,6 +130,7 @@ + diff --git a/IL.View/Model/FileService.cs b/IL.View/Model/FileService.cs new file mode 100644 index 0000000..80697c8 --- /dev/null +++ b/IL.View/Model/FileService.cs @@ -0,0 +1,169 @@ +/* + * The MIT License + * + * Copyright © 2011, Denys Vuika + * + * 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.Diagnostics; +using System.IO; +using System.IO.IsolatedStorage; +using System.Linq; +using System.Runtime.InteropServices.Automation; +using System.Windows; +using System.Windows.Threading; +using Mono.Cecil; + +namespace IL.View.Model +{ + internal static class FileService + { + private const string ReferenceCachePrefix = "REF_"; + + // TODO: add possibility to clear cache from UI + public static AssemblyDefinition FindExternalAssembly(AssemblyNameReference reference, Dispatcher dispatcher) + { + // TODO: add support User folders + /* + * User folders can be accessed without Automation Factory so it should be possible to + * define a reference path based on User folders hierarchy. In this case this feature will work + * for MacOs platforms. + * + * Possible ways to implement: + * + * 1. Allow "Reference Paths" tab to be visible when running on Macs + * 2. When adding reference path a check should be performed (whether path belongs to User folder hierarchy) + * 3. The paths that do not belong to User folder hierachy should not be added (Macs only, provide some warning/notification) + * + * Reference caching should also work fine for User folders. + */ + if (!Application.Current.HasElevatedPermissions) return null; + if (!AutomationFactory.IsAvailable) return null; + if (reference == null) return null; + + var referencePaths = ReferencePathsSettings.Current.Folders.ToArray(); + var targetName = string.Format("{0}.dll", reference.Name); + + try + { + var savedPath = GetCachedPath(reference); + // TODO: optimize code and remove duplication + if (!string.IsNullOrEmpty(savedPath)) + { + using (var stream = LoadFile(savedPath)) + { + var definition = AssemblyDefinition.ReadAssembly(stream); + + if (definition != null && definition.FullName == reference.FullName) + { + Debug.WriteLine("Successfully resolved external assembly from cache: {0}", savedPath); + var assemblyStream = new AssemblyMemoryStream(targetName, stream); + dispatcher.BeginInvoke(() => ApplicationModel.Current.AssemblyCache.LoadAssembly(assemblyStream, definition)); + return definition; + } + } + } + + var fso = AutomationFactory.CreateObject("Scripting.FileSystemObject"); + + foreach (var referenceFolder in referencePaths) + { + var file = FindFile(fso, referenceFolder.Path, targetName, referenceFolder.RecursiveSearch); + if (file == null) continue; + + string path = file.Path; + using (var stream = LoadFile(path)) + { + var definition = AssemblyDefinition.ReadAssembly(stream); + + if (definition == null) continue; + if (definition.FullName != reference.FullName) continue; + + Debug.WriteLine("Successfully resolved external assembly: {0}", path); + + AddCachedPath(reference, path); + + var assemblyStream = new AssemblyMemoryStream(targetName, stream); + dispatcher.BeginInvoke(() => ApplicationModel.Current.AssemblyCache.LoadAssembly(assemblyStream, definition)); + return definition; + } + } + } + catch + { + if (Debugger.IsAttached) Debugger.Break(); + return null; + } + + return null; + } + + private static dynamic FindFile(dynamic fso, string rootPath, string fileName, bool recursive) + { + if (!fso.FolderExists(rootPath)) return null; + var folder = fso.GetFolder(rootPath); + + var targetPath = Path.Combine(rootPath, fileName); + if (fso.FileExists(targetPath)) return fso.GetFile(targetPath); + + if (!recursive) return null; + + foreach (var subFolder in folder.SubFolders) + { + var assembly = FindFile(fso, subFolder.Path, fileName, true); + if (assembly != null) return assembly; + } + + return null; + } + + private static string GetCachedPath(AssemblyNameReference reference) + { + if (reference == null) return null; + var cachedName = ReferenceCachePrefix + reference.FullName; + string cachedPath; + return IsolatedStorageSettings.ApplicationSettings.TryGetValue(cachedName, out cachedPath) ? cachedPath : null; + } + + private static void AddCachedPath(AssemblyNameReference reference, string path) + { + if (reference == null) return; + if (string.IsNullOrWhiteSpace(path)) return; + IsolatedStorageSettings.ApplicationSettings[ReferenceCachePrefix + reference.FullName] = path; + } + + // http://stackoverflow.com/questions/3462039/scripting-filesystemobject-write-method-fails + private static Stream LoadFile(string path) + { + byte[] data; + const int adTypeBinary = 1; + + using (var adoCom = AutomationFactory.CreateObject("ADODB.Stream")) + { + adoCom.Type = adTypeBinary; + adoCom.Open(); + adoCom.LoadFromFile(path); + data = adoCom.Read(); + } + + return data == null ? Stream.Null : new MemoryStream(data); + } + } +} diff --git a/IL.View/Model/ReferencePathsSettings.cs b/IL.View/Model/ReferencePathsSettings.cs index 75a8079..fcbf218 100644 --- a/IL.View/Model/ReferencePathsSettings.cs +++ b/IL.View/Model/ReferencePathsSettings.cs @@ -35,15 +35,42 @@ namespace IL.View.Model { [DebuggerDisplay("{Path}")] - public sealed class ReferenceFolder + public sealed class ReferenceFolder : INotifyPropertyChanged { + private readonly ReferencePathsSettings _settings; + private bool _recursiveSearch; + public string Path { get; set; } - public ReferenceFolder(string path) + public bool RecursiveSearch + { + get { return _recursiveSearch; } + set + { + if (_recursiveSearch == value) return; + _recursiveSearch = value; + OnPropertyChanged("RecursiveSearch"); + if (_settings.AutoSave) _settings.Save(); + } + } + + internal ReferenceFolder(ReferencePathsSettings settings, string path, bool recursiveSearch = false) { + if (settings == null) throw new ArgumentNullException("settings"); if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullException("path"); + + _settings = settings; + _recursiveSearch = recursiveSearch; Path = path; } + + public event PropertyChangedEventHandler PropertyChanged; + + private void OnPropertyChanged(string propertyName) + { + var handler = PropertyChanged; + if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); + } } public sealed class ReferencePathsSettings : INotifyPropertyChanged @@ -54,7 +81,7 @@ public static ReferencePathsSettings Current } public bool AutoSave { get; set; } - + private ObservableCollection _folders = new ObservableCollection(); public ObservableCollection Folders @@ -86,7 +113,7 @@ public bool AddFolder(string path) if (!Folders.Any(f => f.Path.Equals(path, StringComparison.OrdinalIgnoreCase))) { - Folders.Add(new ReferenceFolder(path)); + Folders.Add(new ReferenceFolder(this, path)); return true; } @@ -102,9 +129,10 @@ public void Save() { var data = new XElement("ReferencePaths"); - foreach (var address in Folders.Select(d => d.Path).Distinct()) + foreach (var folder in Folders) data.Add(new XElement("ReferenceFolder", - new XAttribute("Path", address))); + new XAttribute("Path", folder.Path), + new XAttribute("RecursiveSearch", folder.RecursiveSearch))); IsolatedStorageSettings.ApplicationSettings["ReferencePaths"] = data.ToString(); IsolatedStorageSettings.ApplicationSettings.Save(); @@ -116,7 +144,7 @@ public void Load() OnPropertyChanged("Folders"); } - private static IEnumerable LoadSettings() + private IEnumerable LoadSettings() { string settings; @@ -128,9 +156,9 @@ private static IEnumerable LoadSettings() { foreach (var element in data.Elements()) { - var path = element.Attribute("Path"); - if (path != null) - yield return new ReferenceFolder(path.Value); + var path = (string)element.Attribute("Path"); + var recursiveSearch = (bool)element.Attribute("RecursiveSearch"); + yield return new ReferenceFolder(this, path, recursiveSearch); } } } diff --git a/IL.View/Views/Home.xaml.cs b/IL.View/Views/Home.xaml.cs index 42b9f71..2d80d8a 100644 --- a/IL.View/Views/Home.xaml.cs +++ b/IL.View/Views/Home.xaml.cs @@ -373,6 +373,10 @@ private AssemblyDefinition TryResolveAssembly(object sender, AssemblyNameReferen if (definition == null) definition = TryResolveHigherVersionAssembly(_decompileTask.CallingAssembly, reference); + // try to resolve assembly from user-defined reference paths + if (definition == null) + definition = FileService.FindExternalAssembly(reference, Dispatcher); + // ask user to resolve assembly manually if (definition == null) { diff --git a/IL.View/Views/Settings.xaml b/IL.View/Views/Settings.xaml index 3daedca..d3f600c 100644 --- a/IL.View/Views/Settings.xaml +++ b/IL.View/Views/Settings.xaml @@ -40,6 +40,7 @@ + @@ -51,9 +52,11 @@