You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
497 lines
19 KiB
497 lines
19 KiB
// 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.Collections;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel.Composition;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Resources;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
using System.Xaml;
|
|
using System.Xml;
|
|
using ICSharpCode.Decompiler;
|
|
using ICSharpCode.Decompiler.Ast;
|
|
using ICSharpCode.Decompiler.Ast.Transforms;
|
|
using ICSharpCode.NRefactory.CSharp;
|
|
using Mono.Cecil;
|
|
|
|
namespace ICSharpCode.ILSpy
|
|
{
|
|
/// <summary>
|
|
/// Decompiler logic for C#.
|
|
/// </summary>
|
|
[Export(typeof(Language))]
|
|
public class CSharpLanguage : Language
|
|
{
|
|
string name = "C#";
|
|
bool showAllMembers = false;
|
|
Predicate<IAstTransform> transformAbortCondition = null;
|
|
|
|
public CSharpLanguage()
|
|
{
|
|
}
|
|
|
|
#if DEBUG
|
|
internal static IEnumerable<CSharpLanguage> GetDebugLanguages()
|
|
{
|
|
DecompilerContext context = new DecompilerContext(ModuleDefinition.CreateModule("dummy", ModuleKind.Dll));
|
|
string lastTransformName = "no transforms";
|
|
foreach (Type _transformType in TransformationPipeline.CreatePipeline(context).Select(v => v.GetType()).Distinct()) {
|
|
Type transformType = _transformType; // copy for lambda
|
|
yield return new CSharpLanguage {
|
|
transformAbortCondition = v => transformType.IsInstanceOfType(v),
|
|
name = "C# - " + lastTransformName,
|
|
showAllMembers = true
|
|
};
|
|
lastTransformName = "after " + transformType.Name;
|
|
}
|
|
yield return new CSharpLanguage {
|
|
name = "C# - " + lastTransformName,
|
|
showAllMembers = true
|
|
};
|
|
}
|
|
#endif
|
|
|
|
public override string Name {
|
|
get { return name; }
|
|
}
|
|
|
|
public override string FileExtension {
|
|
get { return ".cs"; }
|
|
}
|
|
|
|
public override string ProjectFileExtension {
|
|
get { return ".csproj"; }
|
|
}
|
|
|
|
public override void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options)
|
|
{
|
|
WriteCommentLine(output, TypeToString(method.DeclaringType, includeNamespace: true));
|
|
AstBuilder codeDomBuilder = CreateAstBuilder(options, currentType: method.DeclaringType, isSingleMember: true);
|
|
codeDomBuilder.AddMethod(method);
|
|
codeDomBuilder.RunTransformations(transformAbortCondition);
|
|
codeDomBuilder.GenerateCode(output);
|
|
}
|
|
|
|
public override void DecompileProperty(PropertyDefinition property, ITextOutput output, DecompilationOptions options)
|
|
{
|
|
WriteCommentLine(output, TypeToString(property.DeclaringType, includeNamespace: true));
|
|
AstBuilder codeDomBuilder = CreateAstBuilder(options, currentType: property.DeclaringType, isSingleMember: true);
|
|
codeDomBuilder.AddProperty(property);
|
|
codeDomBuilder.RunTransformations(transformAbortCondition);
|
|
codeDomBuilder.GenerateCode(output);
|
|
}
|
|
|
|
public override void DecompileField(FieldDefinition field, ITextOutput output, DecompilationOptions options)
|
|
{
|
|
WriteCommentLine(output, TypeToString(field.DeclaringType, includeNamespace: true));
|
|
AstBuilder codeDomBuilder = CreateAstBuilder(options, currentType: field.DeclaringType, isSingleMember: true);
|
|
codeDomBuilder.AddField(field);
|
|
codeDomBuilder.RunTransformations(transformAbortCondition);
|
|
codeDomBuilder.GenerateCode(output);
|
|
}
|
|
|
|
public override void DecompileEvent(EventDefinition ev, ITextOutput output, DecompilationOptions options)
|
|
{
|
|
WriteCommentLine(output, TypeToString(ev.DeclaringType, includeNamespace: true));
|
|
AstBuilder codeDomBuilder = CreateAstBuilder(options, currentType: ev.DeclaringType, isSingleMember: true);
|
|
codeDomBuilder.AddEvent(ev);
|
|
codeDomBuilder.RunTransformations(transformAbortCondition);
|
|
codeDomBuilder.GenerateCode(output);
|
|
}
|
|
|
|
public override void DecompileType(TypeDefinition type, ITextOutput output, DecompilationOptions options)
|
|
{
|
|
AstBuilder codeDomBuilder = CreateAstBuilder(options, currentType: type);
|
|
codeDomBuilder.AddType(type);
|
|
codeDomBuilder.RunTransformations(transformAbortCondition);
|
|
codeDomBuilder.GenerateCode(output);
|
|
}
|
|
|
|
public override void DecompileAssembly(AssemblyDefinition assembly, string fileName, ITextOutput output, DecompilationOptions options)
|
|
{
|
|
if (options.FullDecompilation && options.SaveAsProjectDirectory != null) {
|
|
HashSet<string> directories = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
var files = WriteCodeFilesInProject(assembly, options, directories).ToList();
|
|
files.AddRange(WriteResourceFilesInProject(assembly, fileName, options, directories));
|
|
WriteProjectFile(new TextOutputWriter(output), files, assembly.MainModule);
|
|
} else {
|
|
base.DecompileAssembly(assembly, fileName, output, options);
|
|
// don't automatically load additional assemblies when an assembly node is selected in the tree view
|
|
using (options.FullDecompilation ? null : LoadedAssembly.DisableAssemblyLoad()) {
|
|
AstBuilder codeDomBuilder = CreateAstBuilder(options, currentModule: assembly.MainModule);
|
|
codeDomBuilder.AddAssembly(assembly, onlyAssemblyLevel: !options.FullDecompilation);
|
|
codeDomBuilder.RunTransformations(transformAbortCondition);
|
|
codeDomBuilder.GenerateCode(output);
|
|
}
|
|
}
|
|
}
|
|
|
|
#region WriteProjectFile
|
|
void WriteProjectFile(TextWriter writer, IEnumerable<Tuple<string, 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)
|
|
|
|
foreach (IGrouping<string, string> gr in (from f in files group f.Item2 by f.Item1 into g orderby g.Key select g)) {
|
|
w.WriteStartElement("ItemGroup");
|
|
foreach (string file in gr.OrderBy(f => f, StringComparer.OrdinalIgnoreCase)) {
|
|
w.WriteStartElement(gr.Key);
|
|
w.WriteAttributeString("Include", file);
|
|
w.WriteEndElement();
|
|
}
|
|
w.WriteEndElement();
|
|
}
|
|
|
|
w.WriteStartElement("Import");
|
|
w.WriteAttributeString("Project", "$(MSBuildToolsPath)\\Microsoft.CSharp.targets");
|
|
w.WriteEndElement();
|
|
|
|
w.WriteEndDocument();
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region WriteCodeFilesInProject
|
|
bool IncludeTypeWhenDecompilingProject(TypeDefinition type, DecompilationOptions options)
|
|
{
|
|
if (type.Name == "<Module>" || AstBuilder.MemberIsHidden(type, options.DecompilerSettings))
|
|
return false;
|
|
if (type.Namespace == "XamlGeneratedNamespace" && type.Name == "GeneratedInternalTypeHelper")
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
IEnumerable<Tuple<string, string>> WriteCodeFilesInProject(AssemblyDefinition assembly, DecompilationOptions options, HashSet<string> directories)
|
|
{
|
|
var files = assembly.MainModule.Types.Where(t => IncludeTypeWhenDecompilingProject(t, options)).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);
|
|
if (directories.Add(dir))
|
|
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, currentModule: assembly.MainModule);
|
|
foreach (TypeDefinition type in file) {
|
|
codeDomBuilder.AddType(type);
|
|
}
|
|
codeDomBuilder.RunTransformations(transformAbortCondition);
|
|
codeDomBuilder.GenerateCode(new PlainTextOutput(w));
|
|
}
|
|
});
|
|
AstMethodBodyBuilder.PrintNumberOfUnhandledOpcodes();
|
|
return files.Select(f => Tuple.Create("Compile", f.Key));
|
|
}
|
|
#endregion
|
|
|
|
#region WriteResourceFilesInProject
|
|
IEnumerable<Tuple<string, string>> WriteResourceFilesInProject(AssemblyDefinition assembly, string assemblyFileName, DecompilationOptions options, HashSet<string> directories)
|
|
{
|
|
AppDomain bamlDecompilerAppDomain = null;
|
|
try {
|
|
foreach (EmbeddedResource r in assembly.MainModule.Resources.OfType<EmbeddedResource>()) {
|
|
string fileName;
|
|
Stream s = r.GetResourceStream();
|
|
s.Position = 0;
|
|
if (r.Name.EndsWith(".g.resources", StringComparison.OrdinalIgnoreCase)) {
|
|
IEnumerable<DictionaryEntry> rs = null;
|
|
try {
|
|
rs = new ResourceSet(s).Cast<DictionaryEntry>();
|
|
} catch (ArgumentException) {
|
|
}
|
|
if (rs != null && rs.All(e => e.Value is Stream)) {
|
|
foreach (var pair in rs) {
|
|
fileName = Path.Combine(((string)pair.Key).Split('/').Select(p => TextView.DecompilerTextView.CleanUpName(p)).ToArray());
|
|
string dirName = Path.GetDirectoryName(fileName);
|
|
if (!string.IsNullOrEmpty(dirName) && directories.Add(dirName)) {
|
|
Directory.CreateDirectory(Path.Combine(options.SaveAsProjectDirectory, dirName));
|
|
}
|
|
Stream entryStream = (Stream)pair.Value;
|
|
entryStream.Position = 0;
|
|
if (fileName.EndsWith(".baml", StringComparison.OrdinalIgnoreCase)) {
|
|
MemoryStream ms = new MemoryStream();
|
|
entryStream.CopyTo(ms);
|
|
var decompiler = Baml.BamlResourceEntryNode.CreateBamlDecompilerInAppDomain(ref bamlDecompilerAppDomain, assemblyFileName);
|
|
string xaml = null;
|
|
try {
|
|
xaml = decompiler.DecompileBaml(ms, assemblyFileName);
|
|
} catch (XamlXmlWriterException) {} // ignore XAML writer exceptions
|
|
if (xaml != null) {
|
|
File.WriteAllText(Path.Combine(options.SaveAsProjectDirectory, Path.ChangeExtension(fileName, ".xaml")), xaml);
|
|
yield return Tuple.Create("Page", Path.ChangeExtension(fileName, ".xaml"));
|
|
continue;
|
|
}
|
|
}
|
|
using (FileStream fs = new FileStream(Path.Combine(options.SaveAsProjectDirectory, fileName), FileMode.Create, FileAccess.Write)) {
|
|
entryStream.CopyTo(fs);
|
|
}
|
|
yield return Tuple.Create("Resource", fileName);
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
fileName = GetFileNameForResource(r.Name, directories);
|
|
using (FileStream fs = new FileStream(Path.Combine(options.SaveAsProjectDirectory, fileName), FileMode.Create, FileAccess.Write)) {
|
|
s.CopyTo(fs);
|
|
}
|
|
yield return Tuple.Create("EmbeddedResource", fileName);
|
|
}
|
|
} finally {
|
|
if (bamlDecompilerAppDomain != null)
|
|
AppDomain.Unload(bamlDecompilerAppDomain);
|
|
}
|
|
}
|
|
|
|
string GetFileNameForResource(string fullName, HashSet<string> directories)
|
|
{
|
|
string[] splitName = fullName.Split('.');
|
|
string fileName = TextView.DecompilerTextView.CleanUpName(fullName);
|
|
for (int i = splitName.Length - 1; i > 0; i--) {
|
|
string ns = string.Join(".", splitName, 0, i);
|
|
if (directories.Contains(ns)) {
|
|
string name = string.Join(".", splitName, i, splitName.Length - i);
|
|
fileName = Path.Combine(ns, TextView.DecompilerTextView.CleanUpName(name));
|
|
break;
|
|
}
|
|
}
|
|
return fileName;
|
|
}
|
|
#endregion
|
|
|
|
AstBuilder CreateAstBuilder(DecompilationOptions options, ModuleDefinition currentModule = null, TypeDefinition currentType = null, bool isSingleMember = false)
|
|
{
|
|
if (currentModule == null)
|
|
currentModule = currentType.Module;
|
|
DecompilerSettings settings = options.DecompilerSettings;
|
|
if (isSingleMember) {
|
|
settings = settings.Clone();
|
|
settings.UsingDeclarations = false;
|
|
}
|
|
return new AstBuilder(
|
|
new DecompilerContext(currentModule) {
|
|
CancellationToken = options.CancellationToken,
|
|
CurrentType = currentType,
|
|
Settings = settings
|
|
});
|
|
}
|
|
|
|
public override string TypeToString(TypeReference type, bool includeNamespace, ICustomAttributeProvider typeAttributes = null)
|
|
{
|
|
ConvertTypeOptions options = ConvertTypeOptions.IncludeTypeParameterDefinitions;
|
|
if (includeNamespace)
|
|
options |= ConvertTypeOptions.IncludeNamespace;
|
|
AstType astType = AstBuilder.ConvertType(type, typeAttributes, options);
|
|
|
|
StringWriter w = new StringWriter();
|
|
if (type.IsByReference) {
|
|
ParameterDefinition pd = typeAttributes as ParameterDefinition;
|
|
if (pd != null && (!pd.IsIn && pd.IsOut))
|
|
w.Write("out ");
|
|
else
|
|
w.Write("ref ");
|
|
|
|
if (astType is ComposedType && ((ComposedType)astType).PointerRank > 0)
|
|
((ComposedType)astType).PointerRank--;
|
|
}
|
|
|
|
astType.AcceptVisitor(new OutputVisitor(w, new CSharpFormattingOptions()), null);
|
|
return w.ToString();
|
|
}
|
|
|
|
public override string FormatPropertyName(PropertyDefinition property, bool? isIndexer)
|
|
{
|
|
if (property == null)
|
|
throw new ArgumentNullException("property");
|
|
|
|
if (!isIndexer.HasValue) {
|
|
isIndexer = property.IsIndexer();
|
|
}
|
|
if (isIndexer.Value) {
|
|
var buffer = new System.Text.StringBuilder();
|
|
var accessor = property.GetMethod ?? property.SetMethod;
|
|
if (accessor.HasOverrides) {
|
|
var declaringType = accessor.Overrides.First().DeclaringType;
|
|
buffer.Append(TypeToString(declaringType, includeNamespace: true));
|
|
buffer.Append(@".");
|
|
}
|
|
buffer.Append(@"this[");
|
|
bool addSeparator = false;
|
|
foreach (var p in property.Parameters) {
|
|
if (addSeparator)
|
|
buffer.Append(@", ");
|
|
else
|
|
addSeparator = true;
|
|
buffer.Append(TypeToString(p.ParameterType, includeNamespace: true));
|
|
}
|
|
buffer.Append(@"]");
|
|
return buffer.ToString();
|
|
} else
|
|
return property.Name;
|
|
}
|
|
|
|
public override bool ShowMember(MemberReference member)
|
|
{
|
|
return showAllMembers || !AstBuilder.MemberIsHidden(member, new DecompilationOptions().DecompilerSettings);
|
|
}
|
|
|
|
public override string GetTooltip(MemberReference member)
|
|
{
|
|
MethodDefinition md = member as MethodDefinition;
|
|
PropertyDefinition pd = member as PropertyDefinition;
|
|
EventDefinition ed = member as EventDefinition;
|
|
FieldDefinition fd = member as FieldDefinition;
|
|
if (md != null || pd != null || ed != null || fd != null) {
|
|
AstBuilder b = new AstBuilder(new DecompilerContext(member.Module) { Settings = new DecompilerSettings { UsingDeclarations = false } });
|
|
b.DecompileMethodBodies = false;
|
|
if (md != null)
|
|
b.AddMethod(md);
|
|
else if (pd != null)
|
|
b.AddProperty(pd);
|
|
else if (ed != null)
|
|
b.AddEvent(ed);
|
|
else
|
|
b.AddField(fd);
|
|
b.RunTransformations();
|
|
foreach (var attribute in b.CompilationUnit.Descendants.OfType<AttributeSection>())
|
|
attribute.Remove();
|
|
|
|
StringWriter w = new StringWriter();
|
|
b.GenerateCode(new PlainTextOutput(w));
|
|
return Regex.Replace(w.ToString(), @"\s+", " ").TrimEnd();
|
|
}
|
|
|
|
return base.GetTooltip(member);
|
|
}
|
|
}
|
|
}
|