Browse Source

Implemented "Save Assembly as C# Project"

pull/37/head
Daniel Grunwald 15 years ago
parent
commit
275f0f6d21
  1. 26
      ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs
  2. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  3. 1
      ICSharpCode.Decompiler/ITextOutput.cs
  4. 55
      ICSharpCode.Decompiler/TextOutputWriter.cs
  5. 173
      ILSpy/CSharpLanguage.cs
  6. 5
      ILSpy/DecompilationOptions.cs
  7. 4
      ILSpy/Language.cs
  8. 2
      ILSpy/MainWindow.xaml.cs
  9. 8
      ILSpy/TextView/DecompilerTextView.cs
  10. 33
      ILSpy/TreeNodes/AssemblyTreeNode.cs
  11. 2
      ILSpy/TreeNodes/ILSpyTreeNode.cs
  12. 4
      ILSpy/TreeNodes/ResourceEntryNode.cs
  13. 4
      ILSpy/TreeNodes/ResourceListTreeNode.cs

26
ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@ -690,8 +691,33 @@ namespace Decompiler
return target.Invoke(cecilMethod.Name, ConvertTypeArguments(cecilMethod), methodArgs).WithAnnotation(cecilMethod); return target.Invoke(cecilMethod.Name, ConvertTypeArguments(cecilMethod), methodArgs).WithAnnotation(cecilMethod);
} }
#if DEBUG
static readonly ConcurrentDictionary<ILCode, int> unhandledOpcodes = new ConcurrentDictionary<ILCode, int>();
#endif
[Conditional("DEBUG")]
public static void ClearUnhandledOpcodes()
{
#if DEBUG
unhandledOpcodes.Clear();
#endif
}
[Conditional("DEBUG")]
public static void PrintNumberOfUnhandledOpcodes()
{
#if DEBUG
foreach (var pair in unhandledOpcodes) {
Debug.WriteLine("AddMethodBodyBuilder unhandled opcode: {1}x {0}", pair.Key, pair.Value);
}
#endif
}
static Expression InlineAssembly(ILExpression byteCode, List<Ast.Expression> args) static Expression InlineAssembly(ILExpression byteCode, List<Ast.Expression> args)
{ {
#if DEBUG
unhandledOpcodes.AddOrUpdate(byteCode.Code, c => 1, (c, n) => n+1);
#endif
// Output the operand of the unknown IL code as well // Output the operand of the unknown IL code as well
if (byteCode.Operand != null) { if (byteCode.Operand != null) {
args.Insert(0, new IdentifierExpression(FormatByteCodeOperand(byteCode.Operand))); args.Insert(0, new IdentifierExpression(FormatByteCodeOperand(byteCode.Operand)));

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -97,6 +97,7 @@
<Compile Include="Options.cs" /> <Compile Include="Options.cs" />
<Compile Include="PlainTextOutput.cs" /> <Compile Include="PlainTextOutput.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TextOutputWriter.cs" />
<None Include="Properties\AssemblyInfo.template.cs" /> <None Include="Properties\AssemblyInfo.template.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

1
ICSharpCode.Decompiler/ITextOutput.cs

@ -17,6 +17,7 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.IO;
namespace ICSharpCode.Decompiler namespace ICSharpCode.Decompiler
{ {

55
ICSharpCode.Decompiler/TextOutputWriter.cs

@ -0,0 +1,55 @@
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
//
// 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.IO;
using System.Text;
namespace ICSharpCode.Decompiler
{
public class TextOutputWriter : TextWriter
{
readonly ITextOutput output;
public TextOutputWriter(ITextOutput output)
{
if (output == null)
throw new ArgumentNullException("output");
this.output = output;
}
public override Encoding Encoding {
get { return Encoding.UTF8; }
}
public override void Write(char value)
{
output.Write(value);
}
public override void Write(string value)
{
output.Write(value);
}
public override void WriteLine()
{
output.WriteLine();
}
}
}

173
ILSpy/CSharpLanguage.cs

@ -21,6 +21,8 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Decompiler; using Decompiler;
using Decompiler.Transforms; using Decompiler.Transforms;
using ICSharpCode.Decompiler; using ICSharpCode.Decompiler;
@ -70,6 +72,10 @@ namespace ICSharpCode.ILSpy
get { return ".cs"; } get { return ".cs"; }
} }
public override string ProjectFileExtension {
get { return ".csproj"; }
}
public override void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options) public override void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options)
{ {
AstBuilder codeDomBuilder = CreateAstBuilder(options, method.DeclaringType); AstBuilder codeDomBuilder = CreateAstBuilder(options, method.DeclaringType);
@ -108,17 +114,174 @@ namespace ICSharpCode.ILSpy
public override void DecompileAssembly(AssemblyDefinition assembly, string fileName, ITextOutput output, DecompilationOptions options) public override void DecompileAssembly(AssemblyDefinition assembly, string fileName, ITextOutput output, DecompilationOptions options)
{ {
if (options.FullDecompilation) { if (options.FullDecompilation) {
foreach (TypeDefinition type in assembly.MainModule.Types) { if (options.SaveAsProjectDirectory != null) {
AstBuilder codeDomBuilder = CreateAstBuilder(options, type); var files = WriteFilesInProject(assembly, options);
codeDomBuilder.AddType(type); WriteProjectFile(new TextOutputWriter(output), files, assembly.MainModule);
codeDomBuilder.GenerateCode(output, transformAbortCondition); } else {
output.WriteLine(); foreach (TypeDefinition type in assembly.MainModule.Types) {
AstBuilder codeDomBuilder = CreateAstBuilder(options, type);
codeDomBuilder.AddType(type);
codeDomBuilder.GenerateCode(output, transformAbortCondition);
output.WriteLine();
}
} }
} else { } else {
base.DecompileAssembly(assembly, fileName, output, options); base.DecompileAssembly(assembly, fileName, output, options);
} }
} }
void WriteProjectFile(TextWriter writer, IEnumerable<string> files, ModuleDefinition module)
{
const string ns = "http://schemas.microsoft.com/developer/msbuild/2003";
string platformName;
switch (module.Architecture) {
case TargetArchitecture.I386:
if ((module.Attributes & ModuleAttributes.Required32Bit) == ModuleAttributes.Required32Bit)
platformName = "x86";
else
platformName = "AnyCPU";
break;
case TargetArchitecture.AMD64:
platformName = "x64";
break;
case TargetArchitecture.IA64:
platformName = "Itanium";
break;
default:
throw new NotSupportedException("Invalid value for TargetArchitecture");
}
using (XmlTextWriter w = new XmlTextWriter(writer)) {
w.Formatting = Formatting.Indented;
w.WriteStartDocument();
w.WriteStartElement("Project", ns);
w.WriteAttributeString("ToolsVersion", "4.0");
w.WriteAttributeString("DefaultTargets", "Build");
w.WriteStartElement("PropertyGroup");
w.WriteElementString("ProjectGuid", Guid.NewGuid().ToString().ToUpperInvariant());
w.WriteStartElement("Configuration");
w.WriteAttributeString("Condition", " '$(Configuration)' == '' ");
w.WriteValue("Debug");
w.WriteEndElement(); // </Configuration>
w.WriteStartElement("Platform");
w.WriteAttributeString("Condition", " '$(Platform)' == '' ");
w.WriteValue(platformName);
w.WriteEndElement(); // </Platform>
switch (module.Kind) {
case ModuleKind.Windows:
w.WriteElementString("OutputType", "WinExe");
break;
case ModuleKind.Console:
w.WriteElementString("OutputType", "Exe");
break;
default:
w.WriteElementString("OutputType", "Library");
break;
}
w.WriteElementString("AssemblyName", module.Assembly.Name.Name);
switch (module.Runtime) {
case TargetRuntime.Net_1_0:
w.WriteElementString("TargetFrameworkVersion", "v1.0");
break;
case TargetRuntime.Net_1_1:
w.WriteElementString("TargetFrameworkVersion", "v1.1");
break;
case TargetRuntime.Net_2_0:
w.WriteElementString("TargetFrameworkVersion", "v2.0");
// TODO: Detect when .NET 3.0/3.5 is required
break;
default:
w.WriteElementString("TargetFrameworkVersion", "v4.0");
// TODO: Detect TargetFrameworkProfile
break;
}
w.WriteElementString("WarningLevel", "4");
w.WriteEndElement(); // </PropertyGroup>
w.WriteStartElement("PropertyGroup"); // platform-specific
w.WriteAttributeString("Condition", " '$(Platform)' == '" + platformName + "' ");
w.WriteElementString("PlatformTarget", platformName);
w.WriteEndElement(); // </PropertyGroup> (platform-specific)
w.WriteStartElement("PropertyGroup"); // Debug
w.WriteAttributeString("Condition", " '$(Configuration)' == 'Debug' ");
w.WriteElementString("OutputPath", "bin\\Debug\\");
w.WriteElementString("DebugSymbols", "true");
w.WriteElementString("DebugType", "full");
w.WriteElementString("Optimize", "false");
w.WriteEndElement(); // </PropertyGroup> (Debug)
w.WriteStartElement("PropertyGroup"); // Release
w.WriteAttributeString("Condition", " '$(Configuration)' == 'Release' ");
w.WriteElementString("OutputPath", "bin\\Release\\");
w.WriteElementString("DebugSymbols", "true");
w.WriteElementString("DebugType", "pdbonly");
w.WriteElementString("Optimize", "true");
w.WriteEndElement(); // </PropertyGroup> (Release)
w.WriteStartElement("ItemGroup"); // References
foreach (AssemblyNameReference r in module.AssemblyReferences) {
if (r.Name != "mscorlib") {
w.WriteStartElement("Reference");
w.WriteAttributeString("Include", r.Name);
// TODO: RequiredTargetFramework
w.WriteEndElement();
}
}
w.WriteEndElement(); // </ItemGroup> (References)
w.WriteStartElement("ItemGroup"); // Code
foreach (string file in files.OrderBy(f => f, StringComparer.OrdinalIgnoreCase)) {
w.WriteStartElement("Compile");
w.WriteAttributeString("Include", file);
w.WriteEndElement();
}
w.WriteEndElement();
w.WriteStartElement("Import");
w.WriteAttributeString("Project", "$(MSBuildToolsPath)\\Microsoft.CSharp.targets");
w.WriteEndElement();
w.WriteEndDocument();
}
}
IEnumerable<string> WriteFilesInProject(AssemblyDefinition assembly, DecompilationOptions options)
{
var files = assembly.MainModule.Types.Where(t => t.Name != "<Module>").GroupBy(
delegate (TypeDefinition type) {
string file = TextView.DecompilerTextView.CleanUpName(type.Name) + this.FileExtension;
if (string.IsNullOrEmpty(type.Namespace)) {
return file;
} else {
string dir = TextView.DecompilerTextView.CleanUpName(type.Namespace);
Directory.CreateDirectory(Path.Combine(options.SaveAsProjectDirectory, dir));
return Path.Combine(dir, file);
}
}, StringComparer.OrdinalIgnoreCase).ToList();
AstMethodBodyBuilder.ClearUnhandledOpcodes();
Parallel.ForEach(
files,
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
delegate (IGrouping<string, TypeDefinition> file) {
using (StreamWriter w = new StreamWriter(Path.Combine(options.SaveAsProjectDirectory, file.Key))) {
AstBuilder codeDomBuilder = CreateAstBuilder(options, null);
foreach (TypeDefinition type in file)
codeDomBuilder.AddType(type);
codeDomBuilder.GenerateCode(new PlainTextOutput(w), transformAbortCondition);
}
});
AstMethodBodyBuilder.PrintNumberOfUnhandledOpcodes();
return files.Select(f => f.Key);
}
AstBuilder CreateAstBuilder(DecompilationOptions options, TypeDefinition currentType) AstBuilder CreateAstBuilder(DecompilationOptions options, TypeDefinition currentType)
{ {
return new AstBuilder( return new AstBuilder(

5
ILSpy/DecompilationOptions.cs

@ -32,6 +32,11 @@ namespace ICSharpCode.ILSpy
/// </summary> /// </summary>
public bool FullDecompilation { get; set; } public bool FullDecompilation { get; set; }
/// <summary>
/// Gets/Sets the directory into which the project is saved.
/// </summary>
public string SaveAsProjectDirectory { get; set; }
/// <summary> /// <summary>
/// Gets the cancellation token that is used to abort the decompiler. /// Gets the cancellation token that is used to abort the decompiler.
/// </summary> /// </summary>

4
ILSpy/Language.cs

@ -40,6 +40,10 @@ namespace ICSharpCode.ILSpy
/// </summary> /// </summary>
public abstract string FileExtension { get; } public abstract string FileExtension { get; }
public virtual string ProjectFileExtension {
get { return null; }
}
/// <summary> /// <summary>
/// Gets the syntax highlighting used for this language. /// Gets the syntax highlighting used for this language.
/// </summary> /// </summary>

2
ILSpy/MainWindow.xaml.cs

@ -392,7 +392,7 @@ namespace ICSharpCode.ILSpy
{ {
if (treeView.SelectedItems.Count == 1) { if (treeView.SelectedItems.Count == 1) {
ILSpyTreeNode node = treeView.SelectedItem as ILSpyTreeNode; ILSpyTreeNode node = treeView.SelectedItem as ILSpyTreeNode;
if (node != null && node.Save()) if (node != null && node.Save(decompilerTextView))
return; return;
} }
decompilerTextView.SaveToDisk(sessionSettings.FilterSettings.Language, decompilerTextView.SaveToDisk(sessionSettings.FilterSettings.Language,

8
ILSpy/TextView/DecompilerTextView.cs

@ -384,6 +384,11 @@ namespace ICSharpCode.ILSpy.TextView
} }
} }
public void SaveToDisk(ILSpy.Language language, IEnumerable<ILSpyTreeNode> treeNodes, DecompilationOptions options, string fileName)
{
SaveToDisk(new DecompilationContext(language, treeNodes.ToArray(), options), fileName);
}
/// <summary> /// <summary>
/// Starts the decompilation of the given nodes. /// Starts the decompilation of the given nodes.
/// The result will be saved to the given file name. /// The result will be saved to the given file name.
@ -458,6 +463,9 @@ namespace ICSharpCode.ILSpy.TextView
internal static string CleanUpName(string text) internal static string CleanUpName(string text)
{ {
int pos = text.IndexOf(':'); int pos = text.IndexOf(':');
if (pos > 0)
text = text.Substring(0, pos);
pos = text.IndexOf('`');
if (pos > 0) if (pos > 0)
text = text.Substring(0, pos); text = text.Substring(0, pos);
text = text.Trim(); text = text.Trim();

33
ILSpy/TreeNodes/AssemblyTreeNode.cs

@ -27,7 +27,9 @@ using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Threading; using System.Windows.Threading;
using ICSharpCode.Decompiler; using ICSharpCode.Decompiler;
using ICSharpCode.ILSpy.TextView;
using ICSharpCode.TreeView; using ICSharpCode.TreeView;
using Microsoft.Win32;
using Mono.Cecil; using Mono.Cecil;
namespace ICSharpCode.ILSpy.TreeNodes namespace ICSharpCode.ILSpy.TreeNodes
@ -201,5 +203,36 @@ namespace ICSharpCode.ILSpy.TreeNodes
assembly.WaitUntilLoaded(); // necessary so that load errors are passed on to the caller assembly.WaitUntilLoaded(); // necessary so that load errors are passed on to the caller
language.DecompileAssembly(assembly.AssemblyDefinition, assembly.FileName, output, options); language.DecompileAssembly(assembly.AssemblyDefinition, assembly.FileName, output, options);
} }
public override bool Save(DecompilerTextView textView)
{
Language language = this.Language;
if (string.IsNullOrEmpty(language.ProjectFileExtension))
return false;
SaveFileDialog dlg = new SaveFileDialog();
dlg.FileName = DecompilerTextView.CleanUpName(assembly.ShortName);
dlg.Filter = language.Name + " project|*" + language.ProjectFileExtension + "|" + language.Name + " single file|*" + language.FileExtension + "|All files|*.*";
if (dlg.ShowDialog() == true) {
DecompilationOptions options = new DecompilationOptions();
options.FullDecompilation = true;
if (dlg.FilterIndex == 1) {
options.SaveAsProjectDirectory = Path.GetDirectoryName(dlg.FileName);
foreach (string entry in Directory.GetFileSystemEntries(options.SaveAsProjectDirectory)) {
if (!string.Equals(entry, dlg.FileName, StringComparison.OrdinalIgnoreCase)) {
var result = MessageBox.Show(
"The directory is not empty. File will be overwritten." + Environment.NewLine +
"Are you sure you want to continue?",
"Project Directory not empty",
MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);
if (result == MessageBoxResult.No)
return true; // don't save, but mark the Save operation as handled
break;
}
}
}
textView.SaveToDisk(language, new[]{this}, options, dlg.FileName);
}
return true;
}
} }
} }

2
ILSpy/TreeNodes/ILSpyTreeNode.cs

@ -79,7 +79,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
/// This method is called on the main thread when only a single item is selected. /// 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. /// If it returns false, normal decompilation is used to save the item.
/// </summary> /// </summary>
public virtual bool Save() public virtual bool Save(TextView.DecompilerTextView textView)
{ {
return false; return false;
} }

4
ILSpy/TreeNodes/ResourceEntryNode.cs

@ -75,7 +75,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
image.EndInit(); image.EndInit();
output.AddUIElement(() => new Image { Source = image }); output.AddUIElement(() => new Image { Source = image });
output.WriteLine(); output.WriteLine();
output.AddButton(Images.Save, "Save", delegate { Save(); }); output.AddButton(Images.Save, "Save", delegate { Save(null); });
} catch (Exception) { } catch (Exception) {
return false; return false;
} }
@ -106,7 +106,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
return true; return true;
} }
public override bool Save() public override bool Save(DecompilerTextView textView)
{ {
SaveFileDialog dlg = new SaveFileDialog(); SaveFileDialog dlg = new SaveFileDialog();
dlg.FileName = Path.GetFileName(DecompilerTextView.CleanUpName(key)); dlg.FileName = Path.GetFileName(DecompilerTextView.CleanUpName(key));

4
ILSpy/TreeNodes/ResourceListTreeNode.cs

@ -102,7 +102,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
ISmartTextOutput smartOutput = output as ISmartTextOutput; ISmartTextOutput smartOutput = output as ISmartTextOutput;
if (smartOutput != null && r is EmbeddedResource) { if (smartOutput != null && r is EmbeddedResource) {
smartOutput.AddButton(Images.Save, "Save", delegate { Save(); }); smartOutput.AddButton(Images.Save, "Save", delegate { Save(null); });
output.WriteLine(); output.WriteLine();
} }
} }
@ -132,7 +132,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
return false; return false;
} }
public override bool Save() public override bool Save(TextView.DecompilerTextView textView)
{ {
EmbeddedResource er = r as EmbeddedResource; EmbeddedResource er = r as EmbeddedResource;
if (er != null) { if (er != null) {

Loading…
Cancel
Save