Browse Source

Extract multiple package entries

pull/3508/head
CreateAndInject 3 weeks ago
parent
commit
a26a6efd3e
  1. 197
      ILSpy/Commands/ExtractPackageEntryContextMenuEntry.cs
  2. 19
      ILSpy/Properties/Resources.Designer.cs
  3. 3
      ILSpy/Properties/Resources.resx
  4. 3
      ILSpy/Properties/Resources.zh-Hans.resx

197
ILSpy/Commands/ExtractPackageEntryContextMenuEntry.cs

@ -22,6 +22,7 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows;
using ICSharpCode.Decompiler; using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.Decompiler.CSharp.ProjectDecompiler;
@ -30,9 +31,12 @@ using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TextView;
using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX;
using ICSharpCode.ILSpyX.TreeView;
using Microsoft.Win32; using Microsoft.Win32;
using static ICSharpCode.ILSpyX.LoadedPackage;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
[ExportContextMenuEntry(Header = nameof(Resources.ExtractPackageEntry), Category = nameof(Resources.Save), Icon = "Images/Save")] [ExportContextMenuEntry(Header = nameof(Resources.ExtractPackageEntry), Category = nameof(Resources.Save), Icon = "Images/Save")]
@ -41,52 +45,184 @@ namespace ICSharpCode.ILSpy
{ {
public void Execute(TextViewContext context) 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, x => x is AssemblyTreeNode { PackageEntry: { } } or PackageFolderTreeNode);
// Get root assembly to infer the initial directory for the save dialog. // Get root assembly to infer the initial directory for the save dialog.
var bundleNode = selectedNodes.FirstOrDefault()?.Ancestors().OfType<AssemblyTreeNode>() var bundleNode = selectedNodes.FirstOrDefault()?.Ancestors().OfType<AssemblyTreeNode>()
.FirstOrDefault(asm => asm.PackageEntry == null); .FirstOrDefault(asm => asm.PackageEntry == null);
if (bundleNode == null) if (bundleNode == null)
return; return;
var assembly = selectedNodes[0].PackageEntry;
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 (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)
return;
string fileName = dlg.FileName;
dockWorkspace.RunWithCancellation(ct => Task<AvalonEditTextOutput>.Factory.StartNew(() => {
AvalonEditTextOutput output = new AvalonEditTextOutput();
Stopwatch stopwatch = Stopwatch.StartNew();
SaveEntry(output, assembly, fileName);
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.WriteLine();
return output;
}, ct)).Then(dockWorkspace.ShowText).HandleExceptions();
}
else
{
OpenFolderDialog dlg = new OpenFolderDialog();
dlg.InitialDirectory = Path.GetDirectoryName(bundleNode.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;
}
dockWorkspace.RunWithCancellation(ct => Task<AvalonEditTextOutput>.Factory.StartNew(() => {
AvalonEditTextOutput output = new AvalonEditTextOutput();
Stopwatch stopwatch = Stopwatch.StartNew();
foreach (var selectedNode in selectedNodes)
{
if (selectedNode is AssemblyTreeNode { PackageEntry: { } assembly })
{
string fileName = Path.Combine(folderName, GetFullPath(selectedNode.Parent) + WholeProjectDecompiler.SanitizeFileName(assembly.Name));
SaveEntry(output, assembly, fileName);
}
else if (selectedNode is PackageFolderTreeNode)
{
selectedNode.EnsureLazyChildren();
foreach (var node in selectedNode.DescendantsAndSelf())
{
if (node is AssemblyTreeNode { PackageEntry: { } asm })
{
string fileName = Path.Combine(folderName, GetFullPath(node.Parent) + WholeProjectDecompiler.SanitizeFileName(asm.Name));
SaveEntry(output, asm, fileName);
}
else if (node is PackageFolderTreeNode)
{
Directory.CreateDirectory(Path.Combine(folderName, GetFullPath(node)));
}
}
}
}
stopwatch.Stop();
output.WriteLine(Resources.GenerationCompleteInSeconds, stopwatch.Elapsed.TotalSeconds.ToString("F1"));
output.WriteLine();
output.AddButton(null, Resources.OpenExplorer, delegate { Process.Start("explorer", "\"" + folderName + "\""); });
output.WriteLine();
return output;
}, ct)).Then(dockWorkspace.ShowText).HandleExceptions();
}
}
static string GetFullPath(SharpTreeNode node)
{
if (node is PackageFolderTreeNode)
{
string name = node.Text + "\\";
if (GetFullPath(node.Parent) is string parent)
return parent + "\\" + name;
return name;
}
return null;
}
void SaveEntry(ITextOutput output, PackageEntry entry, string targetFileName)
{
output.Write(entry.Name + ": ");
using Stream stream = entry.TryOpenStream();
if (stream == null)
{
output.WriteLine("Could not open stream!");
return;
}
stream.Position = 0;
using FileStream fileStream = new FileStream(targetFileName, FileMode.OpenOrCreate);
stream.CopyTo(fileStream);
output.WriteLine("Written to " + targetFileName);
}
public bool IsEnabled(TextViewContext context) => true;
public bool IsVisible(TextViewContext context) =>
context.SelectedTreeNodes?.Any(x => x is AssemblyTreeNode { PackageEntry: { } } or PackageFolderTreeNode) == true;
}
[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) if (dlg.ShowDialog() != true)
return; 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;
}
dockWorkspace.RunWithCancellation(ct => Task<AvalonEditTextOutput>.Factory.StartNew(() => { dockWorkspace.RunWithCancellation(ct => Task<AvalonEditTextOutput>.Factory.StartNew(() => {
AvalonEditTextOutput output = new AvalonEditTextOutput(); AvalonEditTextOutput output = new AvalonEditTextOutput();
Stopwatch stopwatch = Stopwatch.StartNew(); Stopwatch stopwatch = Stopwatch.StartNew();
stopwatch.Stop();
if (selectedNodes.Length == 1)
asm.EnsureLazyChildren();
foreach (var node in asm.Descendants())
{ {
SaveEntry(output, selectedNodes[0].PackageEntry, outputFolderOrFileName);
}
else
{
foreach (var node in selectedNodes)
if (node is AssemblyTreeNode { PackageEntry: { } assembly })
{
string fileName = Path.Combine(folderName, GetFullPath(node.Parent) + WholeProjectDecompiler.SanitizeFileName(assembly.Name));
SaveEntry(output, assembly, fileName);
}
else if (node is PackageFolderTreeNode)
{ {
var fileName = Path.GetFileName(WholeProjectDecompiler.SanitizeFileName(node.PackageEntry.Name));
SaveEntry(output, node.PackageEntry, Path.Combine(outputFolderOrFileName, fileName));
Directory.CreateDirectory(Path.Combine(folderName, GetFullPath(node)));
} }
} }
stopwatch.Stop();
output.WriteLine(Resources.GenerationCompleteInSeconds, stopwatch.Elapsed.TotalSeconds.ToString("F1")); output.WriteLine(Resources.GenerationCompleteInSeconds, stopwatch.Elapsed.TotalSeconds.ToString("F1"));
output.WriteLine(); output.WriteLine();
output.AddButton(null, Resources.OpenExplorer, delegate { Process.Start("explorer", "/select,\"" + fileName + "\""); });
output.AddButton(null, Resources.OpenExplorer, delegate { Process.Start("explorer", "\"" + folderName + "\""); });
output.WriteLine(); output.WriteLine();
return output; return output;
}, ct)).Then(dockWorkspace.ShowText).HandleExceptions(); }, ct)).Then(dockWorkspace.ShowText).HandleExceptions();
} }
static string GetFullPath(SharpTreeNode node)
{
if (node is PackageFolderTreeNode)
{
string name = node.Text + "\\";
if (GetFullPath(node.Parent) is string parent)
return parent + "\\" + name;
return name;
}
return null;
}
void SaveEntry(ITextOutput output, PackageEntry entry, string targetFileName) void SaveEntry(ITextOutput output, PackageEntry entry, string targetFileName)
{ {
output.Write(entry.Name + ": "); output.Write(entry.Name + ": ");
@ -107,9 +243,18 @@ namespace ICSharpCode.ILSpy
public bool IsVisible(TextViewContext context) 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 { PackageEntry: null } asm])
{
try
{
if (asm.LoadedAssembly.GetLoadResultAsync().GetAwaiter().GetResult().Package is { Kind: PackageKind.Bundle })
return true;
}
catch
{
}
}
return false;
} }
} }
} }

19
ILSpy/Properties/Resources.Designer.cs

@ -1820,11 +1820,20 @@ namespace ICSharpCode.ILSpy.Properties {
return ResourceManager.GetString("ExpandUsingDeclarationsAfterDecompilation", resourceCulture); return ResourceManager.GetString("ExpandUsingDeclarationsAfterDecompilation", resourceCulture);
} }
} }
/// <summary>
/// Looks up a localized string similar to Extract package entry.
/// </summary>
public static string ExtractPackageEntry {
/// <summary>
/// Looks up a localized string similar to Extract all package entries.
/// </summary>
public static string ExtractAllPackageEntries {
get {
return ResourceManager.GetString("ExtractAllPackageEntries", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Extract package entry.
/// </summary>
public static string ExtractPackageEntry {
get { get {
return ResourceManager.GetString("ExtractPackageEntry", resourceCulture); return ResourceManager.GetString("ExtractPackageEntry", resourceCulture);
} }

3
ILSpy/Properties/Resources.resx

@ -624,6 +624,9 @@ Are you sure you want to continue?</value>
<data name="ExpandUsingDeclarationsAfterDecompilation" xml:space="preserve"> <data name="ExpandUsingDeclarationsAfterDecompilation" xml:space="preserve">
<value>Expand using declarations after decompilation</value> <value>Expand using declarations after decompilation</value>
</data> </data>
<data name="ExtractAllPackageEntries" xml:space="preserve">
<value>Extract all package entries</value>
</data>
<data name="ExtractPackageEntry" xml:space="preserve"> <data name="ExtractPackageEntry" xml:space="preserve">
<value>Extract package entry</value> <value>Extract package entry</value>
</data> </data>

3
ILSpy/Properties/Resources.zh-Hans.resx

@ -582,6 +582,9 @@
<data name="ExpandUsingDeclarationsAfterDecompilation" xml:space="preserve"> <data name="ExpandUsingDeclarationsAfterDecompilation" xml:space="preserve">
<value>反编译后展开引用和声明</value> <value>反编译后展开引用和声明</value>
</data> </data>
<data name="ExtractAllPackageEntries" xml:space="preserve">
<value>提取所有包条目</value>
</data>
<data name="ExtractPackageEntry" xml:space="preserve"> <data name="ExtractPackageEntry" xml:space="preserve">
<value>提取包条目</value> <value>提取包条目</value>
</data> </data>

Loading…
Cancel
Save