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 @@ @@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@ -690,8 +691,33 @@ namespace Decompiler @@ -690,8 +691,33 @@ namespace Decompiler
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)
{
#if DEBUG
unhandledOpcodes.AddOrUpdate(byteCode.Code, c => 1, (c, n) => n+1);
#endif
// Output the operand of the unknown IL code as well
if (byteCode.Operand != null) {
args.Insert(0, new IdentifierExpression(FormatByteCodeOperand(byteCode.Operand)));

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

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

1
ICSharpCode.Decompiler/ITextOutput.cs

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

55
ICSharpCode.Decompiler/TextOutputWriter.cs

@ -0,0 +1,55 @@ @@ -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; @@ -21,6 +21,8 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Decompiler;
using Decompiler.Transforms;
using ICSharpCode.Decompiler;
@ -70,6 +72,10 @@ namespace ICSharpCode.ILSpy @@ -70,6 +72,10 @@ namespace ICSharpCode.ILSpy
get { return ".cs"; }
}
public override string ProjectFileExtension {
get { return ".csproj"; }
}
public override void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options)
{
AstBuilder codeDomBuilder = CreateAstBuilder(options, method.DeclaringType);
@ -108,17 +114,174 @@ namespace ICSharpCode.ILSpy @@ -108,17 +114,174 @@ namespace ICSharpCode.ILSpy
public override void DecompileAssembly(AssemblyDefinition assembly, string fileName, ITextOutput output, DecompilationOptions options)
{
if (options.FullDecompilation) {
foreach (TypeDefinition type in assembly.MainModule.Types) {
AstBuilder codeDomBuilder = CreateAstBuilder(options, type);
codeDomBuilder.AddType(type);
codeDomBuilder.GenerateCode(output, transformAbortCondition);
output.WriteLine();
if (options.SaveAsProjectDirectory != null) {
var files = WriteFilesInProject(assembly, options);
WriteProjectFile(new TextOutputWriter(output), files, assembly.MainModule);
} else {
foreach (TypeDefinition type in assembly.MainModule.Types) {
AstBuilder codeDomBuilder = CreateAstBuilder(options, type);
codeDomBuilder.AddType(type);
codeDomBuilder.GenerateCode(output, transformAbortCondition);
output.WriteLine();
}
}
} else {
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)
{
return new AstBuilder(

5
ILSpy/DecompilationOptions.cs

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

4
ILSpy/Language.cs

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

2
ILSpy/MainWindow.xaml.cs

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

8
ILSpy/TextView/DecompilerTextView.cs

@ -384,6 +384,11 @@ namespace ICSharpCode.ILSpy.TextView @@ -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>
/// Starts the decompilation of the given nodes.
/// The result will be saved to the given file name.
@ -458,6 +463,9 @@ namespace ICSharpCode.ILSpy.TextView @@ -458,6 +463,9 @@ namespace ICSharpCode.ILSpy.TextView
internal static string CleanUpName(string text)
{
int pos = text.IndexOf(':');
if (pos > 0)
text = text.Substring(0, pos);
pos = text.IndexOf('`');
if (pos > 0)
text = text.Substring(0, pos);
text = text.Trim();

33
ILSpy/TreeNodes/AssemblyTreeNode.cs

@ -27,7 +27,9 @@ using System.Windows; @@ -27,7 +27,9 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using ICSharpCode.Decompiler;
using ICSharpCode.ILSpy.TextView;
using ICSharpCode.TreeView;
using Microsoft.Win32;
using Mono.Cecil;
namespace ICSharpCode.ILSpy.TreeNodes
@ -201,5 +203,36 @@ 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
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 @@ -79,7 +79,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
/// 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()
public virtual bool Save(TextView.DecompilerTextView textView)
{
return false;
}

4
ILSpy/TreeNodes/ResourceEntryNode.cs

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

4
ILSpy/TreeNodes/ResourceListTreeNode.cs

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

Loading…
Cancel
Save