Browse Source

View/Save embedded resources.

pull/1/head
Daniel Grunwald 14 years ago
parent
commit
1787a9c478
  1. 129
      ILSpy/GuessFileType.cs
  2. 1
      ILSpy/ILSpy.csproj
  3. 33
      ILSpy/ISmartTextOutput.cs
  4. 2
      ILSpy/Images/Images.cs
  5. 7
      ILSpy/Language.cs
  6. 10
      ILSpy/MainWindow.xaml.cs
  7. 82
      ILSpy/TextView/DecompilerTextView.cs
  8. 15
      ILSpy/TreeNodes/AssemblyTreeNode.cs
  9. 20
      ILSpy/TreeNodes/ILSpyTreeNode.cs
  10. 66
      ILSpy/TreeNodes/ResourceListTreeNode.cs
  11. 2
      SharpTreeView/SharpTreeNode.cs

129
ILSpy/GuessFileType.cs

@ -0,0 +1,129 @@ @@ -0,0 +1,129 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
using System;
using System.IO;
using System.Text;
using System.Xml;
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.ILSpy
{
/// <summary>
/// Static methods for determining the type of a file.
/// </summary>
public static class GuessFileType
{
public static FileType DetectFileType(Stream stream)
{
StreamReader reader;
if (stream.Length >= 2) {
int firstByte = stream.ReadByte();
int secondByte = stream.ReadByte();
switch ((firstByte << 8) | secondByte) {
case 0xfffe: // UTF-16 LE BOM / UTF-32 LE BOM
case 0xfeff: // UTF-16 BE BOM
stream.Position -= 2;
reader = new StreamReader(stream, detectEncodingFromByteOrderMarks: true);
break;
case 0xefbb: // start of UTF-8 BOM
if (stream.ReadByte() == 0xbf) {
reader = new StreamReader(stream, Encoding.UTF8);
break;
} else {
return FileType.Binary;
}
default:
if (IsUTF8(stream, (byte)firstByte, (byte)secondByte)) {
stream.Position = 0;
reader = new StreamReader(stream, Encoding.UTF8);
break;
} else {
return FileType.Binary;
}
}
} else {
return FileType.Binary;
}
// Now we got a StreamReader with the correct encoding
// Check for XML now
try {
XmlTextReader xmlReader = new XmlTextReader(stream);
xmlReader.XmlResolver = null;
xmlReader.MoveToContent();
return FileType.Xml;
} catch (XmlException) {
return FileType.Text;
}
}
static bool IsUTF8(Stream fs, byte firstByte, byte secondByte)
{
int max = (int)Math.Min(fs.Length, 500000); // look at max. 500 KB
const int ASCII = 0;
const int Error = 1;
const int UTF8 = 2;
const int UTF8Sequence = 3;
int state = ASCII;
int sequenceLength = 0;
byte b;
for (int i = 0; i < max; i++) {
if (i == 0) {
b = firstByte;
} else if (i == 1) {
b = secondByte;
} else {
b = (byte)fs.ReadByte();
}
if (b < 0x80) {
// normal ASCII character
if (state == UTF8Sequence) {
state = Error;
break;
}
} else if (b < 0xc0) {
// 10xxxxxx : continues UTF8 byte sequence
if (state == UTF8Sequence) {
--sequenceLength;
if (sequenceLength < 0) {
state = Error;
break;
} else if (sequenceLength == 0) {
state = UTF8;
}
} else {
state = Error;
break;
}
} else if (b >= 0xc2 && b < 0xf5) {
// beginning of byte sequence
if (state == UTF8 || state == ASCII) {
state = UTF8Sequence;
if (b < 0xe0) {
sequenceLength = 1; // one more byte following
} else if (b < 0xf0) {
sequenceLength = 2; // two more bytes following
} else {
sequenceLength = 3; // three more bytes following
}
} else {
state = Error;
break;
}
} else {
// 0xc0, 0xc1, 0xf5 to 0xff are invalid in UTF-8 (see RFC 3629)
state = Error;
break;
}
}
return state != Error;
}
}
public enum FileType
{
Binary,
Text,
Xml
}
}

1
ILSpy/ILSpy.csproj

@ -89,6 +89,7 @@ @@ -89,6 +89,7 @@
<Compile Include="FilterSettings.cs" />
<Compile Include="Fusion.cs" />
<Compile Include="GacInterop.cs" />
<Compile Include="GuessFileType.cs" />
<Compile Include="ILLanguage.cs" />
<Compile Include="ILSpySettings.cs" />
<Compile Include="ISmartTextOutput.cs" />

33
ILSpy/ISmartTextOutput.cs

@ -3,6 +3,10 @@ @@ -3,6 +3,10 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using ICSharpCode.Decompiler;
namespace ICSharpCode.ILSpy
@ -17,4 +21,33 @@ namespace ICSharpCode.ILSpy @@ -17,4 +21,33 @@ namespace ICSharpCode.ILSpy
/// </summary>
void AddUIElement(Func<UIElement> element);
}
public static class SmartTextOutputExtensions
{
/// <summary>
/// Creates a button.
/// </summary>
public static void AddButton(this ISmartTextOutput output, ImageSource icon, string text, RoutedEventHandler click)
{
output.AddUIElement(
delegate {
Button button = new Button();
button.Cursor = Cursors.Arrow;
button.Margin = new Thickness(2);
if (icon != null) {
button.Content = new StackPanel {
Orientation = Orientation.Horizontal,
Children = {
new Image { Width = 16, Height = 16, Source = icon, Margin = new Thickness(0, 0, 4, 0) },
new TextBlock { Text = text }
}
};
} else {
button.Content = text;
}
button.Click += click;
return button;
});
}
}
}

2
ILSpy/Images/Images.cs

@ -20,6 +20,8 @@ namespace ICSharpCode.ILSpy @@ -20,6 +20,8 @@ namespace ICSharpCode.ILSpy
public static readonly BitmapImage Assembly = LoadBitmap("Assembly");
public static readonly BitmapImage AssemblyWarning = LoadBitmap("AssemblyWarning");
public static readonly BitmapImage AssemblyLoading = LoadBitmap("Open");
public static readonly BitmapImage Library = LoadBitmap("Library");
public static readonly BitmapImage Namespace = LoadBitmap("NameSpace");

7
ILSpy/Language.cs

@ -45,7 +45,7 @@ namespace ICSharpCode.ILSpy @@ -45,7 +45,7 @@ namespace ICSharpCode.ILSpy
/// </summary>
public virtual ICSharpCode.AvalonEdit.Highlighting.IHighlightingDefinition SyntaxHighlighting {
get {
return ICSharpCode.AvalonEdit.Highlighting.HighlightingManager.Instance.GetDefinitionByExtension(this.FileExtension);
return ICSharpCode.AvalonEdit.Highlighting.HighlightingManager.Instance.GetDefinitionByExtension(this.FileExtension);
}
}
@ -77,6 +77,11 @@ namespace ICSharpCode.ILSpy @@ -77,6 +77,11 @@ namespace ICSharpCode.ILSpy
{
}
public virtual void WriteCommentLine(ITextOutput output, string comment)
{
output.WriteLine("// " + comment);
}
/// <summary>
/// Converts a type reference into a string. This method is used by the member tree node for parameter and return types.
/// </summary>

10
ILSpy/MainWindow.xaml.cs

@ -309,6 +309,11 @@ namespace ICSharpCode.ILSpy @@ -309,6 +309,11 @@ namespace ICSharpCode.ILSpy
void TreeView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (treeView.SelectedItems.Count == 1) {
ILSpyTreeNodeBase node = treeView.SelectedItem as ILSpyTreeNodeBase;
if (node != null && node.View(decompilerTextView))
return;
}
decompilerTextView.Decompile(sessionSettings.FilterSettings.Language,
treeView.GetTopLevelSelection().OfType<ILSpyTreeNodeBase>(),
new DecompilationOptions());
@ -316,6 +321,11 @@ namespace ICSharpCode.ILSpy @@ -316,6 +321,11 @@ namespace ICSharpCode.ILSpy
void saveCode_Click(object sender, RoutedEventArgs e)
{
if (treeView.SelectedItems.Count == 1) {
ILSpyTreeNodeBase node = treeView.SelectedItem as ILSpyTreeNodeBase;
if (node != null && node.Save())
return;
}
decompilerTextView.SaveToDisk(sessionSettings.FilterSettings.Language,
treeView.GetTopLevelSelection().OfType<ILSpyTreeNodeBase>(),
new DecompilationOptions());

82
ILSpy/TextView/DecompilerTextView.cs

@ -124,7 +124,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -124,7 +124,7 @@ namespace ICSharpCode.ILSpy.TextView
{
if (currentCancellationTokenSource != null) {
currentCancellationTokenSource.Cancel();
currentCancellationTokenSource = null;
// Don't set to null: the task still needs to produce output and hide the wait adorner
}
}
#endregion
@ -132,10 +132,23 @@ namespace ICSharpCode.ILSpy.TextView @@ -132,10 +132,23 @@ namespace ICSharpCode.ILSpy.TextView
#region ShowOutput
/// <summary>
/// Shows the given output in the text view.
/// Cancels any currently running decompilation tasks.
/// </summary>
/// <param name="textOutput">The output to show.</param>
/// <param name="language">The language used for syntax highlighting.</param>
void ShowOutput(AvalonEditTextOutput textOutput, ILSpy.Language language = null)
public void Show(AvalonEditTextOutput textOutput, IHighlightingDefinition highlighting)
{
// Cancel the decompilation task:
if (currentCancellationTokenSource != null) {
currentCancellationTokenSource.Cancel();
currentCancellationTokenSource = null; // prevent canceled task from producing output
}
this.nextDecompilationRun = null; // remove scheduled decompilation run
ShowOutput(textOutput, highlighting);
}
/// <summary>
/// Shows the given output in the text view.
/// </summary>
void ShowOutput(AvalonEditTextOutput textOutput, IHighlightingDefinition highlighting = null)
{
Debug.WriteLine("Showing {0} characters of output", textOutput.TextLength);
Stopwatch w = Stopwatch.StartNew();
@ -149,7 +162,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -149,7 +162,7 @@ namespace ICSharpCode.ILSpy.TextView
uiElementGenerator.UIElements = textOutput.UIElements;
referenceElementGenerator.References = textOutput.References;
definitionLookup = textOutput.DefinitionLookup;
textEditor.SyntaxHighlighting = language != null ? language.SyntaxHighlighting : null;
textEditor.SyntaxHighlighting = highlighting;
Debug.WriteLine(" Set-up: {0}", w.Elapsed); w.Restart();
textEditor.Document = textOutput.GetDocument();
@ -163,8 +176,11 @@ namespace ICSharpCode.ILSpy.TextView @@ -163,8 +176,11 @@ namespace ICSharpCode.ILSpy.TextView
#endregion
#region Decompile (for display)
const int defaultOutputLengthLimit = 5000000; // more than 5M characters is too slow to output (when user browses treeview)
const int extendedOutputLengthLimit = 75000000; // more than 75M characters can get us into trouble with memory usage
// more than 5M characters is too slow to output (when user browses treeview)
public const int DefaultOutputLengthLimit = 5000000;
// more than 75M characters can get us into trouble with memory usage
public const int ExtendedOutputLengthLimit = 75000000;
DecompilationContext nextDecompilationRun;
@ -183,7 +199,8 @@ namespace ICSharpCode.ILSpy.TextView @@ -183,7 +199,8 @@ namespace ICSharpCode.ILSpy.TextView
delegate {
var context = this.nextDecompilationRun;
this.nextDecompilationRun = null;
DoDecompile(context, defaultOutputLengthLimit);
if (context != null)
DoDecompile(context, DefaultOutputLengthLimit);
}
));
}
@ -213,7 +230,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -213,7 +230,7 @@ namespace ICSharpCode.ILSpy.TextView
delegate (Task<AvalonEditTextOutput> task) { // handling the result
try {
AvalonEditTextOutput textOutput = task.Result;
ShowOutput(textOutput, context.Language);
ShowOutput(textOutput, context.Language.SyntaxHighlighting);
} catch (AggregateException aggregateException) {
textEditor.SyntaxHighlighting = null;
Debug.WriteLine("Decompiler crashed: " + aggregateException.ToString());
@ -224,7 +241,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -224,7 +241,7 @@ namespace ICSharpCode.ILSpy.TextView
ex = ex.InnerException;
AvalonEditTextOutput output = new AvalonEditTextOutput();
if (ex is OutputLengthExceededException) {
WriteOutputLengthExceededMessage(output, context, outputLengthLimit == defaultOutputLengthLimit);
WriteOutputLengthExceededMessage(output, context, outputLengthLimit == DefaultOutputLengthLimit);
} else {
output.WriteLine(ex.ToString());
}
@ -252,7 +269,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -252,7 +269,7 @@ namespace ICSharpCode.ILSpy.TextView
DecompileNodes(context, textOutput);
textOutput.PrepareDocument();
return textOutput;
});
}, TaskCreationOptions.LongRunning);
}
static void DecompileNodes(DecompilationContext context, ITextOutput textOutput)
@ -282,46 +299,21 @@ namespace ICSharpCode.ILSpy.TextView @@ -282,46 +299,21 @@ namespace ICSharpCode.ILSpy.TextView
}
output.WriteLine();
if (wasNormalLimit) {
output.AddUIElement(MakeButton(
output.AddButton(
Images.ViewCode, "Display Code",
delegate {
DoDecompile(context, extendedOutputLengthLimit);
}));
DoDecompile(context, ExtendedOutputLengthLimit);
});
output.WriteLine();
}
output.AddUIElement(MakeButton(
output.AddButton(
Images.Save, "Save Code",
delegate {
SaveToDisk(context.Language, context.TreeNodes, context.Options);
}));
});
output.WriteLine();
}
/// <summary>
/// Creates a button for use with <see cref="ISmartTextOutput.AddUIElement"/>.
/// </summary>
static Func<Button> MakeButton(ImageSource icon, string text, RoutedEventHandler click)
{
return () => {
Button button = new Button();
button.Cursor = Cursors.Arrow;
button.Margin = new Thickness(2);
if (icon != null) {
button.Content = new StackPanel {
Orientation = Orientation.Horizontal,
Children = {
new Image { Width = 16, Height = 16, Source = icon, Margin = new Thickness(0, 0, 4, 0) },
new TextBlock { Text = text }
}
};
} else {
button.Content = text;
}
button.Click += click;
return button;
};
}
#endregion
#region JumpToReference
@ -402,15 +394,15 @@ namespace ICSharpCode.ILSpy.TextView @@ -402,15 +394,15 @@ namespace ICSharpCode.ILSpy.TextView
AvalonEditTextOutput output = new AvalonEditTextOutput();
output.WriteLine("Decompilation complete.");
output.WriteLine();
output.AddUIElement(MakeButton(
output.AddButton(
null, "Open Explorer",
delegate {
Process.Start("explorer", "/select,\"" + fileName + "\"");
}
));
);
output.WriteLine();
return output;
});
}, TaskCreationOptions.LongRunning);
},
delegate (Task<AvalonEditTextOutput> task) {
try {
@ -433,7 +425,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -433,7 +425,7 @@ namespace ICSharpCode.ILSpy.TextView
/// <summary>
/// Cleans up a node name for use as a file name.
/// </summary>
static string CleanUpName(string text)
internal static string CleanUpName(string text)
{
int pos = text.IndexOf(':');
if (pos > 0)

15
ILSpy/TreeNodes/AssemblyTreeNode.cs

@ -78,7 +78,13 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -78,7 +78,13 @@ namespace ICSharpCode.ILSpy.TreeNodes
}
public override object Icon {
get { return assemblyTask.IsFaulted ? Images.AssemblyWarning : Images.Assembly; }
get {
if (assemblyTask.IsCompleted) {
return assemblyTask.IsFaulted ? Images.AssemblyWarning : Images.Assembly;
} else {
return Images.AssemblyLoading;
}
}
}
public override bool ShowExpander {
@ -102,10 +108,11 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -102,10 +108,11 @@ namespace ICSharpCode.ILSpy.TreeNodes
void OnAssemblyLoaded(Task<AssemblyDefinition> assemblyTask)
{
// change from "Loading" icon to final icon
RaisePropertyChanged("Icon");
RaisePropertyChanged("ExpandedIcon");
if (assemblyTask.IsFaulted) {
RaisePropertyChanged("Icon");
RaisePropertyChanged("ExpandedIcon");
RaisePropertyChanged("ShowExpander");
RaisePropertyChanged("ShowExpander"); // cannot expand assemblies with load error
} else {
AssemblyDefinition assembly = assemblyTask.Result;
if (shortName != assembly.Name.Name) {

20
ILSpy/TreeNodes/ILSpyTreeNode.cs

@ -70,6 +70,26 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -70,6 +70,26 @@ namespace ICSharpCode.ILSpy.TreeNodes
public virtual void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
}
/// <summary>
/// Used to implement special view logic for some items.
/// This method is called on the main thread when only a single item is selected.
/// If it returns false, normal decompilation is used to view the item.
/// </summary>
internal virtual bool View(TextView.DecompilerTextView textView)
{
return false;
}
/// <summary>
/// Used to implement special save logic for some items.
/// This method is called on the main thread when only a single item is selected.
/// If it returns false, normal decompilation is used to save the item.
/// </summary>
public virtual bool Save()
{
return false;
}
}
enum FilterResult

66
ILSpy/TreeNodes/ResourceListTreeNode.cs

@ -2,6 +2,14 @@ @@ -2,6 +2,14 @@
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
using System;
using System.IO;
using System.Text;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Utils;
using ICSharpCode.Decompiler;
using ICSharpCode.ILSpy.TextView;
using Microsoft.Win32;
using Mono.Cecil;
namespace ICSharpCode.ILSpy.TreeNodes
@ -51,6 +59,10 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -51,6 +59,10 @@ namespace ICSharpCode.ILSpy.TreeNodes
this.r = r;
}
public Resource Resource {
get { return r; }
}
public override object Text {
get { return r.Name; }
}
@ -68,5 +80,59 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -68,5 +80,59 @@ namespace ICSharpCode.ILSpy.TreeNodes
else
return FilterResult.Hidden;
}
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.WriteCommentLine(output, string.Format("{0} ({1}, {2})", r.Name, r.ResourceType, r.Attributes));
ISmartTextOutput smartOutput = output as ISmartTextOutput;
if (smartOutput != null && r is EmbeddedResource) {
smartOutput.AddButton(Images.Save, "Save", delegate { Save(); });
output.WriteLine();
}
}
internal override bool View(DecompilerTextView textView)
{
EmbeddedResource er = r as EmbeddedResource;
if (er != null) {
Stream s = er.GetResourceStream();
if (s != null && s.Length < DecompilerTextView.DefaultOutputLengthLimit) {
s.Position = 0;
FileType type = GuessFileType.DetectFileType(s);
if (type != FileType.Binary) {
s.Position = 0;
AvalonEditTextOutput output = new AvalonEditTextOutput();
output.Write(FileReader.OpenStream(s, Encoding.UTF8).ReadToEnd());
string ext;
if (type == FileType.Xml)
ext = ".xml";
else
ext = Path.GetExtension(DecompilerTextView.CleanUpName(er.Name));
textView.Show(output, HighlightingManager.Instance.GetDefinitionByExtension(ext));
return true;
}
}
}
return false;
}
public override bool Save()
{
EmbeddedResource er = r as EmbeddedResource;
if (er != null) {
SaveFileDialog dlg = new SaveFileDialog();
dlg.FileName = DecompilerTextView.CleanUpName(er.Name);
if (dlg.ShowDialog() == true) {
Stream s = er.GetResourceStream();
s.Position = 0;
using (var fs = dlg.OpenFile()) {
s.CopyTo(fs);
}
}
return true;
}
return false;
}
}
}

2
SharpTreeView/SharpTreeNode.cs

@ -325,6 +325,8 @@ namespace ICSharpCode.TreeView @@ -325,6 +325,8 @@ namespace ICSharpCode.TreeView
#endregion
#region Cut / Copy / Paste / Delete
public bool IsCut { get { return false; } }
/*
static List<SharpTreeNode> cuttedNodes = new List<SharpTreeNode>();
static IDataObject cuttedData;

Loading…
Cancel
Save