|
|
@ -17,11 +17,13 @@ |
|
|
|
// DEALINGS IN THE SOFTWARE.
|
|
|
|
|
|
|
|
using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.Composition; |
|
|
|
using System.Diagnostics; |
|
|
|
using System.IO; |
|
|
|
using System.Linq; |
|
|
|
using System.Threading.Tasks; |
|
|
|
using System.Windows; |
|
|
|
|
|
|
|
using ICSharpCode.Decompiler; |
|
|
|
using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; |
|
|
@ -30,9 +32,12 @@ using ICSharpCode.ILSpy.Properties; |
|
|
|
using ICSharpCode.ILSpy.TextView; |
|
|
|
using ICSharpCode.ILSpy.TreeNodes; |
|
|
|
using ICSharpCode.ILSpyX; |
|
|
|
using ICSharpCode.ILSpyX.TreeView; |
|
|
|
|
|
|
|
using Microsoft.Win32; |
|
|
|
|
|
|
|
using static ICSharpCode.ILSpyX.LoadedPackage; |
|
|
|
|
|
|
|
namespace ICSharpCode.ILSpy |
|
|
|
{ |
|
|
|
[ExportContextMenuEntry(Header = nameof(Resources.ExtractPackageEntry), Category = nameof(Resources.Save), Icon = "Images/Save")] |
|
|
@ -41,55 +46,115 @@ namespace ICSharpCode.ILSpy |
|
|
|
{ |
|
|
|
public void Execute(TextViewContext context) |
|
|
|
{ |
|
|
|
// Get all assemblies in the selection that are stored inside a package.
|
|
|
|
var selectedNodes = context.SelectedTreeNodes.OfType<AssemblyTreeNode>() |
|
|
|
.Where(asm => asm.PackageEntry != null).ToArray(); |
|
|
|
var selectedNodes = Array.FindAll(context.SelectedTreeNodes, IsBundleItem); |
|
|
|
// Get root assembly to infer the initial directory for the save dialog.
|
|
|
|
var bundleNode = selectedNodes.FirstOrDefault()?.Ancestors().OfType<AssemblyTreeNode>() |
|
|
|
.FirstOrDefault(asm => asm.PackageEntry == null); |
|
|
|
if (bundleNode == null) |
|
|
|
return; |
|
|
|
var assembly = selectedNodes[0].PackageEntry; |
|
|
|
if (selectedNodes is [AssemblyTreeNode { PackageEntry: { } assembly }]) |
|
|
|
{ |
|
|
|
SaveFileDialog dlg = new SaveFileDialog(); |
|
|
|
dlg.FileName = Path.GetFileName(WholeProjectDecompiler.SanitizeFileName(assembly.Name)); |
|
|
|
dlg.Filter = ".NET assemblies|*.dll;*.exe;*.winmd" + Resources.AllFiles; |
|
|
|
dlg.InitialDirectory = Path.GetDirectoryName(bundleNode.LoadedAssembly.FileName); |
|
|
|
if (dlg.ShowDialog() == true) |
|
|
|
Save(dockWorkspace, selectedNodes, dlg.FileName, true); |
|
|
|
} |
|
|
|
else if (selectedNodes is [ResourceTreeNode { Resource: { } resource }]) |
|
|
|
{ |
|
|
|
SaveFileDialog dlg = new SaveFileDialog(); |
|
|
|
dlg.FileName = Path.GetFileName(WholeProjectDecompiler.SanitizeFileName(resource.Name)); |
|
|
|
dlg.Filter = Resources.AllFiles[1..]; |
|
|
|
dlg.InitialDirectory = Path.GetDirectoryName(bundleNode.LoadedAssembly.FileName); |
|
|
|
if (dlg.ShowDialog() == true) |
|
|
|
Save(dockWorkspace, selectedNodes, dlg.FileName, true); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
OpenFolderDialog dlg = new OpenFolderDialog(); |
|
|
|
dlg.InitialDirectory = Path.GetDirectoryName(bundleNode.LoadedAssembly.FileName); |
|
|
|
if (dlg.ShowDialog() != true) |
|
|
|
return; |
|
|
|
|
|
|
|
string fileName = dlg.FileName; |
|
|
|
string outputFolderOrFileName = fileName; |
|
|
|
if (selectedNodes.Length > 1) |
|
|
|
outputFolderOrFileName = Path.GetDirectoryName(outputFolderOrFileName); |
|
|
|
string folderName = dlg.FolderName; |
|
|
|
if (Directory.EnumerateFileSystemEntries(folderName).Any()) |
|
|
|
{ |
|
|
|
var result = MessageBox.Show( |
|
|
|
Resources.AssemblySaveCodeDirectoryNotEmpty, |
|
|
|
Resources.AssemblySaveCodeDirectoryNotEmptyTitle, |
|
|
|
MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No); |
|
|
|
if (result == MessageBoxResult.No) |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
Save(dockWorkspace, selectedNodes, folderName, false); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static string GetPackageFolderPath(SharpTreeNode node) |
|
|
|
{ |
|
|
|
string name = ""; |
|
|
|
while (node is PackageFolderTreeNode) |
|
|
|
{ |
|
|
|
name = Path.Combine(node.Text.ToString(), name); |
|
|
|
node = node.Parent; |
|
|
|
} |
|
|
|
return name; |
|
|
|
} |
|
|
|
|
|
|
|
internal static void Save(DockWorkspace dockWorkspace, IEnumerable<SharpTreeNode> nodes, string path, bool isFile) |
|
|
|
{ |
|
|
|
dockWorkspace.RunWithCancellation(ct => Task<AvalonEditTextOutput>.Factory.StartNew(() => { |
|
|
|
AvalonEditTextOutput output = new AvalonEditTextOutput(); |
|
|
|
Stopwatch stopwatch = Stopwatch.StartNew(); |
|
|
|
stopwatch.Stop(); |
|
|
|
|
|
|
|
if (selectedNodes.Length == 1) |
|
|
|
foreach (var node in nodes) |
|
|
|
{ |
|
|
|
if (node is AssemblyTreeNode { PackageEntry: { } assembly }) |
|
|
|
{ |
|
|
|
SaveEntry(output, selectedNodes[0].PackageEntry, outputFolderOrFileName); |
|
|
|
string fileName = isFile ? path : Path.Combine(path, GetPackageFolderPath(node.Parent), assembly.Name); |
|
|
|
SaveEntry(output, assembly, fileName); |
|
|
|
} |
|
|
|
else |
|
|
|
else if (node is ResourceTreeNode { Resource: PackageEntry { } resource }) |
|
|
|
{ |
|
|
|
foreach (var node in selectedNodes) |
|
|
|
string fileName = isFile ? path : Path.Combine(path, GetPackageFolderPath(node.Parent), resource.Name); |
|
|
|
SaveEntry(output, resource, fileName); |
|
|
|
} |
|
|
|
else if (node is PackageFolderTreeNode) |
|
|
|
{ |
|
|
|
var fileName = Path.GetFileName(WholeProjectDecompiler.SanitizeFileName(node.PackageEntry.Name)); |
|
|
|
SaveEntry(output, node.PackageEntry, Path.Combine(outputFolderOrFileName, fileName)); |
|
|
|
node.EnsureLazyChildren(); |
|
|
|
foreach (var item in node.DescendantsAndSelf()) |
|
|
|
{ |
|
|
|
if (item is AssemblyTreeNode { PackageEntry: { } asm }) |
|
|
|
{ |
|
|
|
string fileName = Path.Combine(path, GetPackageFolderPath(item.Parent), asm.Name); |
|
|
|
SaveEntry(output, asm, fileName); |
|
|
|
} |
|
|
|
else if (item is ResourceTreeNode { Resource: PackageEntry { } entry }) |
|
|
|
{ |
|
|
|
string fileName = Path.Combine(path, GetPackageFolderPath(item.Parent), entry.Name); |
|
|
|
SaveEntry(output, entry, fileName); |
|
|
|
} |
|
|
|
else if (item is PackageFolderTreeNode) |
|
|
|
{ |
|
|
|
Directory.CreateDirectory(Path.Combine(path, GetPackageFolderPath(item))); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
stopwatch.Stop(); |
|
|
|
output.WriteLine(Resources.GenerationCompleteInSeconds, stopwatch.Elapsed.TotalSeconds.ToString("F1")); |
|
|
|
output.WriteLine(); |
|
|
|
output.AddButton(null, Resources.OpenExplorer, delegate { Process.Start("explorer", "/select,\"" + fileName + "\""); }); |
|
|
|
output.AddButton(null, Resources.OpenExplorer, delegate { Process.Start("explorer", isFile ? $"/select,\"{path}\"" : $"\"{path}\""); }); |
|
|
|
output.WriteLine(); |
|
|
|
return output; |
|
|
|
}, ct)).Then(dockWorkspace.ShowText).HandleExceptions(); |
|
|
|
} |
|
|
|
|
|
|
|
void SaveEntry(ITextOutput output, PackageEntry entry, string targetFileName) |
|
|
|
static void SaveEntry(ITextOutput output, PackageEntry entry, string targetFileName) |
|
|
|
{ |
|
|
|
output.Write(entry.Name + ": "); |
|
|
|
targetFileName = WholeProjectDecompiler.SanitizeFileName(targetFileName); |
|
|
|
using Stream stream = entry.TryOpenStream(); |
|
|
|
if (stream == null) |
|
|
|
{ |
|
|
@ -105,11 +170,58 @@ namespace ICSharpCode.ILSpy |
|
|
|
|
|
|
|
public bool IsEnabled(TextViewContext context) => true; |
|
|
|
|
|
|
|
public bool IsVisible(TextViewContext context) => context.SelectedTreeNodes?.Any(IsBundleItem) == true; |
|
|
|
|
|
|
|
static bool IsBundleItem(SharpTreeNode node) |
|
|
|
{ |
|
|
|
if (node is AssemblyTreeNode { PackageEntry: { } } or PackageFolderTreeNode) |
|
|
|
return true; |
|
|
|
if (node is ResourceTreeNode { Resource: PackageEntry { } resource } && resource.FullName.StartsWith("bundle://")) |
|
|
|
return true; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
[ExportContextMenuEntry(Header = nameof(Resources.ExtractAllPackageEntries), Category = nameof(Resources.Save), Icon = "Images/Save")] |
|
|
|
[Shared] |
|
|
|
sealed class ExtractAllPackageEntriesContextMenuEntry(DockWorkspace dockWorkspace) : IContextMenuEntry |
|
|
|
{ |
|
|
|
public void Execute(TextViewContext context) |
|
|
|
{ |
|
|
|
if (context.SelectedTreeNodes is not [AssemblyTreeNode { PackageEntry: null } asm]) |
|
|
|
return; |
|
|
|
OpenFolderDialog dlg = new OpenFolderDialog(); |
|
|
|
dlg.InitialDirectory = Path.GetDirectoryName(asm.LoadedAssembly.FileName); |
|
|
|
if (dlg.ShowDialog() != true) |
|
|
|
return; |
|
|
|
|
|
|
|
string folderName = dlg.FolderName; |
|
|
|
if (Directory.EnumerateFileSystemEntries(folderName).Any()) |
|
|
|
{ |
|
|
|
var result = MessageBox.Show( |
|
|
|
Resources.AssemblySaveCodeDirectoryNotEmpty, |
|
|
|
Resources.AssemblySaveCodeDirectoryNotEmptyTitle, |
|
|
|
MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No); |
|
|
|
if (result == MessageBoxResult.No) |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
asm.EnsureLazyChildren(); |
|
|
|
ExtractPackageEntryContextMenuEntry.Save(dockWorkspace, asm.Descendants(), folderName, false); |
|
|
|
} |
|
|
|
|
|
|
|
public bool IsEnabled(TextViewContext context) => true; |
|
|
|
|
|
|
|
public bool IsVisible(TextViewContext context) |
|
|
|
{ |
|
|
|
var selectedNodes = context.SelectedTreeNodes?.OfType<AssemblyTreeNode>() |
|
|
|
.Where(asm => asm.PackageEntry != null) ?? Enumerable.Empty<AssemblyTreeNode>(); |
|
|
|
return selectedNodes.Any(); |
|
|
|
if (context.SelectedTreeNodes is [AssemblyTreeNode { LoadedAssembly.IsLoaded: true, LoadedAssembly.HasLoadError: false, PackageEntry: null } asm]) |
|
|
|
{ |
|
|
|
// Using .GetAwaiter().GetResult() is no problem here, since we already checked IsLoaded and HasLoadError.
|
|
|
|
var loadResult = asm.LoadedAssembly.GetLoadResultAsync().GetAwaiter().GetResult(); |
|
|
|
if (loadResult.Package is { Kind: PackageKind.Bundle }) |
|
|
|
return true; |
|
|
|
} |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
} |