Browse Source

Merge branch 'master' of https://github.com/icsharpcode/ILSpy into srm

pull/1030/head
Siegfried Pammer 7 years ago
parent
commit
42591a0ea0
  1. 1
      BuildTools/appveyor-install.ps1
  2. 1
      BuildTools/update-assemblyinfo.ps1
  3. 64
      ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs
  4. 2
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  5. 36
      ICSharpCode.Decompiler/IL/ILReader.cs
  6. 15
      ICSharpCode.Decompiler/IL/Instructions/LdFlda.cs
  7. 3
      ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs
  8. 33
      ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs
  9. 540
      ICSharpCode.Decompiler/Util/ResXResourceWriter.cs
  10. 526
      ICSharpCode.Decompiler/Util/ResourcesFile.cs
  11. 109
      ILSpy.AddIn/AssemblyFileFinder.cs
  12. 3
      ILSpy.AddIn/Commands/OpenCodeItemCommand.cs
  13. 9
      ILSpy.AddIn/Commands/OpenILSpyCommand.cs
  14. 1
      ILSpy.AddIn/ILSpy.AddIn.csproj
  15. 11
      ILSpy.BamlDecompiler.Tests/BamlTestRunner.cs
  16. 8
      ILSpy/Languages/CSharpLanguage.cs
  17. 43
      ILSpy/TreeNodes/ResourceNodes/ResourcesFileTreeNode.cs
  18. 3
      appveyor.yml
  19. 99
      doc/Resources.txt

1
BuildTools/appveyor-install.ps1

@ -3,6 +3,7 @@ $ErrorActionPreference = "Stop" @@ -3,6 +3,7 @@ $ErrorActionPreference = "Stop"
$baseCommit = "d779383cb85003d6dabeb976f0845631e07bf463";
$baseCommitRev = 1;
# make sure this list matches artifacts-only branches list in appveyor.yml!
$masterBranches = @("master", "3.2.x");
$globalAssemblyInfoTemplateFile = "ILSpy/Properties/AssemblyInfo.template.cs";

1
BuildTools/update-assemblyinfo.ps1

@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
$baseCommit = "d779383cb85003d6dabeb976f0845631e07bf463";
$baseCommitRev = 1;
# make sure this list matches artifacts-only branches list in appveyor.yml!
$masterBranches = @("master", "3.2.x");
$globalAssemblyInfoTemplateFile = "ILSpy/Properties/AssemblyInfo.template.cs";

64
ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs

@ -21,7 +21,6 @@ using System.Collections; @@ -21,7 +21,6 @@ using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Resources;
using System.Threading.Tasks;
using System.Xml;
using ICSharpCode.Decompiler.CSharp.OutputVisitor;
@ -30,6 +29,7 @@ using ICSharpCode.Decompiler.CSharp.Transforms; @@ -30,6 +29,7 @@ using ICSharpCode.Decompiler.CSharp.Transforms;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
using System.Threading;
using System.Text;
using System.Reflection.PortableExecutable;
using System.Reflection.Metadata;
using static ICSharpCode.Decompiler.Metadata.DotNetCorePathFinderExtensions;
@ -340,23 +340,40 @@ namespace ICSharpCode.Decompiler.CSharp @@ -340,23 +340,40 @@ namespace ICSharpCode.Decompiler.CSharp
Stream stream = r.TryOpenStream();
stream.Position = 0;
IEnumerable<DictionaryEntry> entries;
if (r.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)) {
if (GetEntries(stream, out entries) && entries.All(e => e.Value is Stream)) {
foreach (var pair in entries) {
string fileName = Path.Combine(((string)pair.Key).Split('/').Select(p => CleanUpFileName(p)).ToArray());
bool decodedIntoIndividualFiles;
var individualResources = new List<Tuple<string, string>>();
try {
var resourcesFile = new ResourcesFile(stream);
if (resourcesFile.AllEntriesAreStreams()) {
foreach (var (name, value) in resourcesFile) {
string fileName = Path.Combine(name.Split('/').Select(p => CleanUpFileName(p)).ToArray());
string dirName = Path.GetDirectoryName(fileName);
if (!string.IsNullOrEmpty(dirName) && directories.Add(dirName)) {
Directory.CreateDirectory(Path.Combine(targetDirectory, dirName));
}
Stream entryStream = (Stream)pair.Value;
Stream entryStream = (Stream)value;
entryStream.Position = 0;
WriteResourceToFile(Path.Combine(targetDirectory, fileName), (string)pair.Key, entryStream);
individualResources.AddRange(
WriteResourceToFile(Path.Combine(targetDirectory, fileName), (string)name, entryStream));
}
decodedIntoIndividualFiles = true;
} else {
decodedIntoIndividualFiles = false;
}
} catch (BadImageFormatException) {
decodedIntoIndividualFiles = false;
}
if (decodedIntoIndividualFiles) {
foreach (var entry in individualResources) {
yield return entry;
}
} else {
stream.Position = 0;
string fileName = GetFileNameForResource(Path.ChangeExtension(r.Name, ".resource"));
WriteResourceToFile(fileName, r.Name, stream);
string fileName = Path.ChangeExtension(GetFileNameForResource(r.Name), ".resource");
foreach (var entry in WriteResourceToFile(fileName, r.Name, stream)) {
yield return entry;
}
}
} else {
string fileName = GetFileNameForResource(r.Name);
@ -391,17 +408,6 @@ namespace ICSharpCode.Decompiler.CSharp @@ -391,17 +408,6 @@ namespace ICSharpCode.Decompiler.CSharp
}
return fileName;
}
bool GetEntries(Stream stream, out IEnumerable<DictionaryEntry> entries)
{
try {
entries = new ResourceSet(stream).Cast<DictionaryEntry>();
return true;
} catch (ArgumentException) {
entries = null;
return false;
}
}
#endregion
/// <summary>
@ -416,9 +422,21 @@ namespace ICSharpCode.Decompiler.CSharp @@ -416,9 +422,21 @@ namespace ICSharpCode.Decompiler.CSharp
if (pos > 0)
text = text.Substring(0, pos);
text = text.Trim();
foreach (char c in Path.GetInvalidFileNameChars())
text = text.Replace(c, '-');
return text;
// Whitelist allowed characters, replace everything else:
StringBuilder b = new StringBuilder(text.Length);
foreach (var c in text) {
if (char.IsLetterOrDigit(c) || c == '-' || c == '_')
b.Append(c);
else if (c == '.' && b.Length > 0 && b[b.Length - 1] != '.')
b.Append('.'); // allow dot, but never two in a row
else
b.Append('-');
if (b.Length >= 64)
break; // limit to 64 chars
}
if (b.Length == 0)
b.Append('-');
return b.ToString();
}
public static string GetPlatformName(Metadata.PEFile module)

2
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -372,6 +372,8 @@ @@ -372,6 +372,8 @@
<Compile Include="Util\GraphVizGraph.cs" />
<Compile Include="Util\KeyComparer.cs" />
<Compile Include="Util\LongDict.cs" />
<Compile Include="Util\ResourcesFile.cs" />
<Compile Include="Util\ResXResourceWriter.cs" />
<Compile Include="Util\UnicodeNewline.cs" />
<Compile Include="FlowAnalysis\ControlFlowNode.cs" />
<Compile Include="FlowAnalysis\DataFlowVisitor.cs" />

36
ICSharpCode.Decompiler/IL/ILReader.cs

@ -1116,21 +1116,47 @@ namespace ICSharpCode.Decompiler.IL @@ -1116,21 +1116,47 @@ namespace ICSharpCode.Decompiler.IL
return inst;
default:
Warn("Expected native int or pointer, but got " + inst.ResultType);
return inst;
return new Conv(inst, PrimitiveType.I, false, Sign.None);
}
}
ILInstruction PopFieldTarget(IField field)
{
return field.DeclaringType.IsReferenceType == true ? Pop(StackType.O) : PopPointer();
switch (field.DeclaringType.IsReferenceType) {
case true:
return Pop(StackType.O);
case false:
return PopPointer();
default:
// field in unresolved type
if (PeekStackType() == StackType.O)
return Pop();
else
return PopPointer();
}
}
/// <summary>
/// Like PopFieldTarget, but supports ldfld's special behavior for fields of temporary value types.
/// </summary>
ILInstruction PopLdFldTarget(IField field)
{
if (field.DeclaringType.IsReferenceType == true)
switch (field.DeclaringType.IsReferenceType) {
case true:
return Pop(StackType.O);
return PeekStackType() == StackType.O ? new AddressOf(Pop()) : PopPointer();
case false:
// field of value type: ldfld can handle temporaries
if (PeekStackType() == StackType.O)
return new AddressOf(Pop());
else
return PopPointer();
default:
// field in unresolved type
if (PeekStackType() == StackType.O)
return Pop(StackType.O);
else
return PopPointer();
}
}
private ILInstruction Return()

15
ICSharpCode.Decompiler/IL/Instructions/LdFlda.cs

@ -25,7 +25,20 @@ namespace ICSharpCode.Decompiler.IL @@ -25,7 +25,20 @@ namespace ICSharpCode.Decompiler.IL
internal override void CheckInvariant(ILPhase phase)
{
base.CheckInvariant(phase);
Debug.Assert(field.DeclaringType.IsReferenceType == true ? (target.ResultType == StackType.O) : (target.ResultType == StackType.I || target.ResultType == StackType.Ref));
switch (field.DeclaringType.IsReferenceType) {
case true:
Debug.Assert(target.ResultType == StackType.O,
"Class fields can only be accessed with an object on the stack");
break;
case false:
Debug.Assert(target.ResultType == StackType.I || target.ResultType == StackType.Ref,
"Struct fields can only be accessed with a pointer on the stack");
break;
case null:
// field of unresolved type
Debug.Assert(target.ResultType == StackType.O || target.ResultType == StackType.I || target.ResultType == StackType.Ref);
break;
}
}
}
}

3
ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs

@ -23,6 +23,7 @@ using System.IO; @@ -23,6 +23,7 @@ using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using ICSharpCode.Decompiler.Util;
using LightJson.Serialization;
namespace ICSharpCode.Decompiler.Metadata
@ -44,7 +45,7 @@ namespace ICSharpCode.Decompiler.Metadata @@ -44,7 +45,7 @@ namespace ICSharpCode.Decompiler.Metadata
this.Version = parts[1];
this.Type = type;
this.Path = path;
this.RuntimeComponents = runtimeComponents ?? new string[0];
this.RuntimeComponents = runtimeComponents ?? Empty<string>.Array;
}
}

33
ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs

@ -19,6 +19,8 @@ @@ -19,6 +19,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
@ -26,7 +28,11 @@ namespace ICSharpCode.Decompiler.Metadata @@ -26,7 +28,11 @@ namespace ICSharpCode.Decompiler.Metadata
{
public static class DotNetCorePathFinderExtensions
{
public static string DetectTargetFrameworkId(this PEReader assembly)
static readonly string RefPathPattern =
@"(Reference Assemblies[/\\]Microsoft[/\\]Framework[/\\](?<1>.NETFramework)[/\\]v(?<2>[^/\\]+)[/\\])" +
@"|(NuGetFallbackFolder[/\\](?<1>[^/\\]+)\\(?<2>[^/\\]+)([/\\].*)?[/\\]ref[/\\])";
public static string DetectTargetFrameworkId(this PEReader assembly, string assemblyPath = null)
{
if (assembly == null)
throw new ArgumentNullException(nameof(assembly));
@ -44,6 +50,31 @@ namespace ICSharpCode.Decompiler.Metadata @@ -44,6 +50,31 @@ namespace ICSharpCode.Decompiler.Metadata
}
}
// Optionally try to detect target version through assembly path as a fallback (use case: reference assemblies)
if (assemblyPath != null) {
/*
* Detected path patterns (examples):
*
* - .NETFramework -> C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\mscorlib.dll
* - .NETCore -> C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.netcore.app\2.1.0\ref\netcoreapp2.1\System.Console.dll
* - .NETStandard -> C:\Program Files\dotnet\sdk\NuGetFallbackFolder\netstandard.library\2.0.3\build\netstandard2.0\ref\netstandard.dll
*/
var pathMatch = Regex.Match(assemblyPath, RefPathPattern,
RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture);
if (pathMatch.Success) {
var type = pathMatch.Groups[1].Value;
var version = pathMatch.Groups[2].Value;
if (type == ".NETFramework") {
return $".NETFramework,Version=v{version}";
} else if (type.Contains("netcore")) {
return $".NETCoreApp,Version=v{version}";
} else if (type.Contains("netstandard")) {
return $".NETStandard,Version=v{version}";
}
}
}
return string.Empty;
}
}

540
ICSharpCode.Decompiler/Util/ResXResourceWriter.cs

@ -0,0 +1,540 @@ @@ -0,0 +1,540 @@
// Copyright (c) 2018 Daniel Grunwald
// This file is based on the Mono implementation of ResXResourceWriter.
// It is modified to add support for "ResourceSerializedObject" values.
//
// 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.
//
// Copyright (c) 2004-2005 Novell, Inc.
//
// Authors:
// Duncan Mak duncan@ximian.com
// Gonzalo Paniagua Javier gonzalo@ximian.com
// Peter Bartok pbartok@novell.com
// Gary Barnett gary.barnett.mono@gmail.com
// includes code by Mike Krüger and Lluis Sanchez
using System.ComponentModel;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Xml;
using System;
namespace ICSharpCode.Decompiler.Util
{
#if INSIDE_SYSTEM_WEB
internal
#else
public
#endif
class ResXResourceWriter : IDisposable
{
#region Local Variables
private string filename;
private Stream stream;
private TextWriter textwriter;
private XmlTextWriter writer;
private bool written;
private string base_path;
#endregion // Local Variables
#region Static Fields
public static readonly string BinSerializedObjectMimeType = "application/x-microsoft.net.object.binary.base64";
public static readonly string ByteArraySerializedObjectMimeType = "application/x-microsoft.net.object.bytearray.base64";
public static readonly string DefaultSerializedObjectMimeType = BinSerializedObjectMimeType;
public static readonly string ResMimeType = "text/microsoft-resx";
public static readonly string ResourceSchema = schema;
public static readonly string SoapSerializedObjectMimeType = "application/x-microsoft.net.object.soap.base64";
public static readonly string Version = "2.0";
#endregion // Static Fields
#region Constructors & Destructor
public ResXResourceWriter(Stream stream)
{
if (stream == null)
throw new ArgumentNullException("stream");
if (!stream.CanWrite)
throw new ArgumentException("stream is not writable.", "stream");
this.stream = stream;
}
public ResXResourceWriter(TextWriter textWriter)
{
if (textWriter == null)
throw new ArgumentNullException("textWriter");
this.textwriter = textWriter;
}
public ResXResourceWriter(string fileName)
{
if (fileName == null)
throw new ArgumentNullException("fileName");
this.filename = fileName;
}
~ResXResourceWriter()
{
Dispose(false);
}
#endregion // Constructors & Destructor
const string WinFormsAssemblyName = ", System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
const string ResXNullRefTypeName = "System.Resources.ResXNullRef" + WinFormsAssemblyName;
void InitWriter()
{
if (filename != null)
stream = File.Open(filename, FileMode.Create);
if (textwriter == null)
textwriter = new StreamWriter(stream, Encoding.UTF8);
writer = new XmlTextWriter(textwriter);
writer.Formatting = Formatting.Indented;
writer.WriteStartDocument();
writer.WriteStartElement("root");
writer.WriteRaw(schema);
WriteHeader("resmimetype", "text/microsoft-resx");
WriteHeader("version", "1.3");
WriteHeader("reader", "System.Resources.ResXResourceReader" + WinFormsAssemblyName);
WriteHeader("writer", "System.Resources.ResXResourceWriter" + WinFormsAssemblyName);
}
void WriteHeader(string name, string value)
{
writer.WriteStartElement("resheader");
writer.WriteAttributeString("name", name);
writer.WriteStartElement("value");
writer.WriteString(value);
writer.WriteEndElement();
writer.WriteEndElement();
}
void WriteNiceBase64(byte[] value, int offset, int length)
{
string b64;
StringBuilder sb;
int pos;
int inc;
string ins;
b64 = Convert.ToBase64String(value, offset, length);
// Wild guess; two extra newlines, and one newline/tab pair for every 80 chars
sb = new StringBuilder(b64, b64.Length + ((b64.Length + 160) / 80) * 3);
pos = 0;
inc = 80 + Environment.NewLine.Length + 1;
ins = Environment.NewLine + "\t";
while (pos < sb.Length) {
sb.Insert(pos, ins);
pos += inc;
}
sb.Insert(sb.Length, Environment.NewLine);
writer.WriteString(sb.ToString());
}
void WriteBytes(string name, Type type, byte[] value, int offset, int length, string comment)
{
writer.WriteStartElement("data");
writer.WriteAttributeString("name", name);
if (type != null) {
writer.WriteAttributeString("type", type.AssemblyQualifiedName);
// byte[] should never get a mimetype, otherwise MS.NET won't be able
// to parse the data.
if (type != typeof(byte[]))
writer.WriteAttributeString("mimetype", ByteArraySerializedObjectMimeType);
writer.WriteStartElement("value");
WriteNiceBase64(value, offset, length);
} else {
writer.WriteAttributeString("mimetype", BinSerializedObjectMimeType);
writer.WriteStartElement("value");
writer.WriteBase64(value, offset, length);
}
writer.WriteEndElement();
if (!(comment == null || comment.Equals(String.Empty))) {
writer.WriteStartElement("comment");
writer.WriteString(comment);
writer.WriteEndElement();
}
writer.WriteEndElement();
}
void WriteBytes(string name, Type type, byte[] value, string comment)
{
WriteBytes(name, type, value, 0, value.Length, comment);
}
void WriteString(string name, string value)
{
WriteString(name, value, null);
}
void WriteString(string name, string value, string type)
{
WriteString(name, value, type, String.Empty);
}
void WriteString(string name, string value, string type, string comment)
{
writer.WriteStartElement("data");
writer.WriteAttributeString("name", name);
if (type != null)
writer.WriteAttributeString("type", type);
writer.WriteStartElement("value");
writer.WriteString(value);
writer.WriteEndElement();
if (!(comment == null || comment.Equals(String.Empty))) {
writer.WriteStartElement("comment");
writer.WriteString(comment);
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteWhitespace("\n ");
}
public void AddResource(string name, byte[] value)
{
if (name == null)
throw new ArgumentNullException("name");
if (value == null)
throw new ArgumentNullException("value");
if (written)
throw new InvalidOperationException("The resource is already generated.");
if (writer == null)
InitWriter();
WriteBytes(name, value.GetType(), value, null);
}
public void AddResource(string name, object value)
{
AddResource(name, value, String.Empty);
}
private void AddResource(string name, object value, string comment)
{
if (value is string) {
AddResource(name, (string)value, comment);
return;
}
if (name == null)
throw new ArgumentNullException("name");
if (written)
throw new InvalidOperationException("The resource is already generated.");
if (writer == null)
InitWriter();
if (value is byte[]) {
WriteBytes(name, value.GetType(), (byte[])value, comment);
return;
}
if (value is ResourceSerializedObject rso) {
var bytes = rso.GetBytes();
WriteBytes(name, null, bytes, 0, bytes.Length, comment);
return;
}
if (value == null) {
// nulls written as ResXNullRef
WriteString(name, "", ResXNullRefTypeName, comment);
return;
}
if (value != null && !value.GetType().IsSerializable)
throw new InvalidOperationException(String.Format("The element '{0}' of type '{1}' is not serializable.", name, value.GetType().Name));
TypeConverter converter = TypeDescriptor.GetConverter(value);
if (converter != null && converter.CanConvertTo(typeof(string)) && converter.CanConvertFrom(typeof(string))) {
string str = (string)converter.ConvertToInvariantString(value);
WriteString(name, str, value.GetType().AssemblyQualifiedName, comment);
return;
}
if (converter != null && converter.CanConvertTo(typeof(byte[])) && converter.CanConvertFrom(typeof(byte[]))) {
byte[] b = (byte[])converter.ConvertTo(value, typeof(byte[]));
WriteBytes(name, value.GetType(), b, comment);
return;
}
MemoryStream ms = new MemoryStream();
BinaryFormatter fmt = new BinaryFormatter();
try {
fmt.Serialize(ms, value);
} catch (Exception e) {
throw new InvalidOperationException("Cannot add a " + value.GetType() +
"because it cannot be serialized: " +
e.Message);
}
WriteBytes(name, null, ms.GetBuffer(), 0, (int)ms.Length, comment);
ms.Close();
}
public void AddResource(string name, string value)
{
AddResource(name, value, string.Empty);
}
private void AddResource(string name, string value, string comment)
{
if (name == null)
throw new ArgumentNullException("name");
if (value == null)
throw new ArgumentNullException("value");
if (written)
throw new InvalidOperationException("The resource is already generated.");
if (writer == null)
InitWriter();
WriteString(name, value, null, comment);
}
public void AddMetadata(string name, string value)
{
if (name == null)
throw new ArgumentNullException("name");
if (value == null)
throw new ArgumentNullException("value");
if (written)
throw new InvalidOperationException("The resource is already generated.");
if (writer == null)
InitWriter();
writer.WriteStartElement("metadata");
writer.WriteAttributeString("name", name);
writer.WriteAttributeString("xml:space", "preserve");
writer.WriteElementString("value", value);
writer.WriteEndElement();
}
public void AddMetadata(string name, byte[] value)
{
if (name == null)
throw new ArgumentNullException("name");
if (value == null)
throw new ArgumentNullException("value");
if (written)
throw new InvalidOperationException("The resource is already generated.");
if (writer == null)
InitWriter();
writer.WriteStartElement("metadata");
writer.WriteAttributeString("name", name);
writer.WriteAttributeString("type", value.GetType().AssemblyQualifiedName);
writer.WriteStartElement("value");
WriteNiceBase64(value, 0, value.Length);
writer.WriteEndElement();
writer.WriteEndElement();
}
public void AddMetadata(string name, object value)
{
if (value is string) {
AddMetadata(name, (string)value);
return;
}
if (value is byte[]) {
AddMetadata(name, (byte[])value);
return;
}
if (name == null)
throw new ArgumentNullException("name");
if (value == null)
throw new ArgumentNullException("value");
if (!value.GetType().IsSerializable)
throw new InvalidOperationException(String.Format("The element '{0}' of type '{1}' is not serializable.", name, value.GetType().Name));
if (written)
throw new InvalidOperationException("The resource is already generated.");
if (writer == null)
InitWriter();
Type type = value.GetType();
TypeConverter converter = TypeDescriptor.GetConverter(value);
if (converter != null && converter.CanConvertTo(typeof(string)) && converter.CanConvertFrom(typeof(string))) {
string str = (string)converter.ConvertToInvariantString(value);
writer.WriteStartElement("metadata");
writer.WriteAttributeString("name", name);
if (type != null)
writer.WriteAttributeString("type", type.AssemblyQualifiedName);
writer.WriteStartElement("value");
writer.WriteString(str);
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteWhitespace("\n ");
return;
}
if (converter != null && converter.CanConvertTo(typeof(byte[])) && converter.CanConvertFrom(typeof(byte[]))) {
byte[] b = (byte[])converter.ConvertTo(value, typeof(byte[]));
writer.WriteStartElement("metadata");
writer.WriteAttributeString("name", name);
if (type != null) {
writer.WriteAttributeString("type", type.AssemblyQualifiedName);
writer.WriteAttributeString("mimetype", ByteArraySerializedObjectMimeType);
writer.WriteStartElement("value");
WriteNiceBase64(b, 0, b.Length);
} else {
writer.WriteAttributeString("mimetype", BinSerializedObjectMimeType);
writer.WriteStartElement("value");
writer.WriteBase64(b, 0, b.Length);
}
writer.WriteEndElement();
writer.WriteEndElement();
return;
}
MemoryStream ms = new MemoryStream();
BinaryFormatter fmt = new BinaryFormatter();
try {
fmt.Serialize(ms, value);
} catch (Exception e) {
throw new InvalidOperationException("Cannot add a " + value.GetType() +
"because it cannot be serialized: " +
e.Message);
}
writer.WriteStartElement("metadata");
writer.WriteAttributeString("name", name);
if (type != null) {
writer.WriteAttributeString("type", type.AssemblyQualifiedName);
writer.WriteAttributeString("mimetype", ByteArraySerializedObjectMimeType);
writer.WriteStartElement("value");
WriteNiceBase64(ms.GetBuffer(), 0, ms.GetBuffer().Length);
} else {
writer.WriteAttributeString("mimetype", BinSerializedObjectMimeType);
writer.WriteStartElement("value");
writer.WriteBase64(ms.GetBuffer(), 0, ms.GetBuffer().Length);
}
writer.WriteEndElement();
writer.WriteEndElement();
ms.Close();
}
public void Close()
{
if (!written) {
Generate();
}
if (writer != null) {
writer.Close();
stream = null;
filename = null;
textwriter = null;
}
}
public virtual void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Generate()
{
if (written)
throw new InvalidOperationException("The resource is already generated.");
written = true;
writer.WriteEndElement();
writer.Flush();
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
Close();
}
static string schema = @"
<xsd:schema id='root' xmlns='' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:msdata='urn:schemas-microsoft-com:xml-msdata'>
<xsd:element name='root' msdata:IsDataSet='true'>
<xsd:complexType>
<xsd:choice maxOccurs='unbounded'>
<xsd:element name='data'>
<xsd:complexType>
<xsd:sequence>
<xsd:element name='value' type='xsd:string' minOccurs='0' msdata:Ordinal='1' />
<xsd:element name='comment' type='xsd:string' minOccurs='0' msdata:Ordinal='2' />
</xsd:sequence>
<xsd:attribute name='name' type='xsd:string' msdata:Ordinal='1' />
<xsd:attribute name='type' type='xsd:string' msdata:Ordinal='3' />
<xsd:attribute name='mimetype' type='xsd:string' msdata:Ordinal='4' />
</xsd:complexType>
</xsd:element>
<xsd:element name='resheader'>
<xsd:complexType>
<xsd:sequence>
<xsd:element name='value' type='xsd:string' minOccurs='0' msdata:Ordinal='1' />
</xsd:sequence>
<xsd:attribute name='name' type='xsd:string' use='required' />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
".Replace("'", "\"").Replace("\t", " ");
#region Public Properties
public string BasePath {
get { return base_path; }
set { base_path = value; }
}
#endregion
}
}

526
ICSharpCode.Decompiler/Util/ResourcesFile.cs

@ -0,0 +1,526 @@ @@ -0,0 +1,526 @@
// Copyright (c) 2018 Daniel Grunwald
// Based on the .NET Core ResourceReader; make available under the MIT license
// by the .NET Foundation.
//
// 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.Diagnostics;
using System.IO;
using System.Text;
namespace ICSharpCode.Decompiler.Util
{
/// <summary>
/// .resources file.
/// </summary>
public class ResourcesFile : IEnumerable<KeyValuePair<string, object>>, IDisposable
{
sealed class MyBinaryReader : BinaryReader
{
public MyBinaryReader(Stream input, bool leaveOpen) : base(input, Encoding.UTF8, leaveOpen)
{
}
// upgrade from protected to public visibility
public new int Read7BitEncodedInt()
{
return base.Read7BitEncodedInt();
}
public void Seek(long pos, SeekOrigin origin)
{
BaseStream.Seek(pos, origin);
}
}
enum ResourceTypeCode
{
Null = 0,
String = 1,
Boolean = 2,
Char = 3,
Byte = 4,
SByte = 5,
Int16 = 6,
UInt16 = 7,
Int32 = 8,
UInt32 = 9,
Int64 = 10,
UInt64 = 11,
Single = 12,
Double = 13,
Decimal = 14,
DateTime = 0xF,
TimeSpan = 0x10,
LastPrimitive = 0x10,
ByteArray = 0x20,
Stream = 33,
StartOfUserTypes = 0x40
}
/// <summary>Holds the number used to identify resource files.</summary>
public const int MagicNumber = unchecked((int)0xBEEFCACE);
const int ResourceSetVersion = 2;
readonly MyBinaryReader reader;
readonly int version;
readonly int numResources;
readonly string[] typeTable;
readonly int[] namePositions;
readonly long fileStartPosition;
readonly long nameSectionPosition;
readonly long dataSectionPosition;
long[] startPositions;
/// <summary>
/// Creates a new ResourcesFile.
/// </summary>
/// <param name="stream">Input stream.</param>
/// <param name="leaveOpen">Whether the stream should be help open when the ResourcesFile is disposed.</param>
/// <remarks>
/// The stream is must be held open while the ResourcesFile is in use.
/// The stream must be seekable; any operation using the ResourcesFile will end up seeking the stream.
/// </remarks>
public ResourcesFile(Stream stream, bool leaveOpen = true)
{
fileStartPosition = stream.Position;
reader = new MyBinaryReader(stream, leaveOpen);
const string ResourcesHeaderCorrupted = "Resources header corrupted.";
// Read ResourceManager header
// Check for magic number
int magicNum = reader.ReadInt32();
if (magicNum != MagicNumber)
throw new BadImageFormatException("Not a .resources file - invalid magic number");
// Assuming this is ResourceManager header V1 or greater, hopefully
// after the version number there is a number of bytes to skip
// to bypass the rest of the ResMgr header. For V2 or greater, we
// use this to skip to the end of the header
int resMgrHeaderVersion = reader.ReadInt32();
int numBytesToSkip = reader.ReadInt32();
if (numBytesToSkip < 0 || resMgrHeaderVersion < 0) {
throw new BadImageFormatException(ResourcesHeaderCorrupted);
}
if (resMgrHeaderVersion > 1) {
reader.BaseStream.Seek(numBytesToSkip, SeekOrigin.Current);
} else {
// We don't care about numBytesToSkip; read the rest of the header
// readerType:
reader.ReadString();
// resourceSetType:
reader.ReadString();
}
// Read RuntimeResourceSet header
// Do file version check
version = reader.ReadInt32();
if (version != ResourceSetVersion && version != 1)
throw new BadImageFormatException($"Unsupported resource set version: {version}");
numResources = reader.ReadInt32();
if (numResources < 0) {
throw new BadImageFormatException(ResourcesHeaderCorrupted);
}
// Read type positions into type positions array.
// But delay initialize the type table.
int numTypes = reader.ReadInt32();
if (numTypes < 0) {
throw new BadImageFormatException(ResourcesHeaderCorrupted);
}
typeTable = new string[numTypes];
for (int i = 0; i < numTypes; i++) {
typeTable[i] = reader.ReadString();
}
// Prepare to read in the array of name hashes
// Note that the name hashes array is aligned to 8 bytes so
// we can use pointers into it on 64 bit machines. (4 bytes
// may be sufficient, but let's plan for the future)
// Skip over alignment stuff. All public .resources files
// should be aligned No need to verify the byte values.
long pos = reader.BaseStream.Position - fileStartPosition;
int alignBytes = unchecked((int)pos) & 7;
if (alignBytes != 0) {
for (int i = 0; i < 8 - alignBytes; i++) {
reader.ReadByte();
}
}
// Skip over the array of name hashes
try {
reader.Seek(checked(4 * numResources), SeekOrigin.Current);
} catch (OverflowException) {
throw new BadImageFormatException(ResourcesHeaderCorrupted);
}
// Read in the array of relative positions for all the names.
namePositions = new int[numResources];
for (int i = 0; i < numResources; i++) {
int namePosition = reader.ReadInt32();
if (namePosition < 0) {
throw new BadImageFormatException(ResourcesHeaderCorrupted);
}
namePositions[i] = namePosition;
}
// Read location of data section.
int dataSectionOffset = reader.ReadInt32();
if (dataSectionOffset < 0) {
throw new BadImageFormatException(ResourcesHeaderCorrupted);
}
// Store current location as start of name section
nameSectionPosition = reader.BaseStream.Position;
dataSectionPosition = fileStartPosition + dataSectionOffset;
// _nameSectionOffset should be <= _dataSectionOffset; if not, it's corrupt
if (dataSectionPosition < nameSectionPosition) {
throw new BadImageFormatException(ResourcesHeaderCorrupted);
}
}
public void Dispose()
{
reader.Dispose();
}
public int ResourceCount => numResources;
public string GetResourceName(int index)
{
return GetResourceName(index, out _);
}
int GetResourceDataOffset(int index)
{
GetResourceName(index, out int dataOffset);
return dataOffset;
}
string GetResourceName(int index, out int dataOffset)
{
long pos = nameSectionPosition + namePositions[index];
byte[] bytes;
lock (reader) {
reader.Seek(pos, SeekOrigin.Begin);
// Can't use reader.ReadString, since it's using UTF-8!
int byteLen = reader.Read7BitEncodedInt();
if (byteLen < 0) {
throw new BadImageFormatException("Resource name has negative length");
}
bytes = new byte[byteLen];
// We must read byteLen bytes, or we have a corrupted file.
// Use a blocking read in case the stream doesn't give us back
// everything immediately.
int count = byteLen;
while (count > 0) {
int n = reader.Read(bytes, byteLen - count, count);
if (n == 0)
throw new BadImageFormatException("End of stream within a resource name");
count -= n;
}
dataOffset = reader.ReadInt32();
if (dataOffset < 0) {
throw new BadImageFormatException("Negative data offset");
}
}
return Encoding.Unicode.GetString(bytes);
}
internal bool AllEntriesAreStreams()
{
if (version != 2)
return false;
lock (reader) {
for (int i = 0; i < numResources; i++) {
int dataOffset = GetResourceDataOffset(i);
reader.Seek(dataSectionPosition + dataOffset, SeekOrigin.Begin);
var typeCode = (ResourceTypeCode)reader.Read7BitEncodedInt();
if (typeCode != ResourceTypeCode.Stream)
return false;
}
}
return true;
}
object LoadObject(int dataOffset)
{
try {
lock (reader) {
if (version == 1) {
return LoadObjectV1(dataOffset);
} else {
return LoadObjectV2(dataOffset);
}
}
} catch (EndOfStreamException e) {
throw new BadImageFormatException("Invalid resource file", e);
}
}
string FindType(int typeIndex)
{
if (typeIndex < 0 || typeIndex >= typeTable.Length)
throw new BadImageFormatException("Type index out of bounds");
return typeTable[typeIndex];
}
// This takes a virtual offset into the data section and reads an Object
// from that location.
// Anyone who calls LoadObject should make sure they take a lock so
// no one can cause us to do a seek in here.
private object LoadObjectV1(int dataOffset)
{
Debug.Assert(System.Threading.Monitor.IsEntered(reader));
reader.Seek(dataSectionPosition + dataOffset, SeekOrigin.Begin);
int typeIndex = reader.Read7BitEncodedInt();
if (typeIndex == -1)
return null;
string typeName = FindType(typeIndex);
int comma = typeName.IndexOf(',');
if (comma > 0) {
// strip assembly name
typeName = typeName.Substring(0, comma);
}
switch (typeName) {
case "System.String":
return reader.ReadString();
case "System.Byte":
return reader.ReadByte();
case "System.SByte":
return reader.ReadSByte();
case "System.Int16":
return reader.ReadInt16();
case "System.UInt16":
return reader.ReadUInt16();
case "System.Int32":
return reader.ReadInt32();
case "System.UInt32":
return reader.ReadUInt32();
case "System.Int64":
return reader.ReadInt64();
case "System.UInt64":
return reader.ReadUInt64();
case "System.Single":
return reader.ReadSingle();
case "System.Double":
return reader.ReadDouble();
case "System.DateTime":
// Ideally we should use DateTime's ToBinary & FromBinary,
// but we can't for compatibility reasons.
return new DateTime(reader.ReadInt64());
case "System.TimeSpan":
return new TimeSpan(reader.ReadInt64());
case "System.Decimal":
int[] bits = new int[4];
for (int i = 0; i < bits.Length; i++)
bits[i] = reader.ReadInt32();
return new decimal(bits);
default:
return new ResourceSerializedObject(FindType(typeIndex), this, reader.BaseStream.Position);
}
}
private object LoadObjectV2(int dataOffset)
{
Debug.Assert(System.Threading.Monitor.IsEntered(reader));
reader.Seek(dataSectionPosition + dataOffset, SeekOrigin.Begin);
var typeCode = (ResourceTypeCode)reader.Read7BitEncodedInt();
switch (typeCode) {
case ResourceTypeCode.Null:
return null;
case ResourceTypeCode.String:
return reader.ReadString();
case ResourceTypeCode.Boolean:
return reader.ReadBoolean();
case ResourceTypeCode.Char:
return (char)reader.ReadUInt16();
case ResourceTypeCode.Byte:
return reader.ReadByte();
case ResourceTypeCode.SByte:
return reader.ReadSByte();
case ResourceTypeCode.Int16:
return reader.ReadInt16();
case ResourceTypeCode.UInt16:
return reader.ReadUInt16();
case ResourceTypeCode.Int32:
return reader.ReadInt32();
case ResourceTypeCode.UInt32:
return reader.ReadUInt32();
case ResourceTypeCode.Int64:
return reader.ReadInt64();
case ResourceTypeCode.UInt64:
return reader.ReadUInt64();
case ResourceTypeCode.Single:
return reader.ReadSingle();
case ResourceTypeCode.Double:
return reader.ReadDouble();
case ResourceTypeCode.Decimal:
return reader.ReadDecimal();
case ResourceTypeCode.DateTime:
// Use DateTime's ToBinary & FromBinary.
long data = reader.ReadInt64();
return DateTime.FromBinary(data);
case ResourceTypeCode.TimeSpan:
long ticks = reader.ReadInt64();
return new TimeSpan(ticks);
// Special types
case ResourceTypeCode.ByteArray: {
int len = reader.ReadInt32();
if (len < 0) {
throw new BadImageFormatException("Resource with negative length");
}
return reader.ReadBytes(len);
}
case ResourceTypeCode.Stream: {
int len = reader.ReadInt32();
if (len < 0) {
throw new BadImageFormatException("Resource with negative length");
}
byte[] bytes = reader.ReadBytes(len);
return new MemoryStream(bytes, writable: false);
}
default:
if (typeCode < ResourceTypeCode.StartOfUserTypes) {
throw new BadImageFormatException("Invalid typeCode");
}
return new ResourceSerializedObject(FindType(typeCode - ResourceTypeCode.StartOfUserTypes), this, reader.BaseStream.Position);
}
}
public object GetResourceValue(int index)
{
GetResourceName(index, out int dataOffset);
return LoadObject(dataOffset);
}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
for (int i = 0; i < numResources; i++) {
string name = GetResourceName(i, out int dataOffset);
object val = LoadObject(dataOffset);
yield return new KeyValuePair<string, object>(name, val);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
long[] GetStartPositions()
{
long[] positions = LazyInit.VolatileRead(ref startPositions);
if (positions != null)
return positions;
lock (reader) {
// double-checked locking
positions = LazyInit.VolatileRead(ref startPositions);
if (positions != null)
return positions;
positions = new long[numResources * 2];
int outPos = 0;
for (int i = 0; i < numResources; i++) {
positions[outPos++] = nameSectionPosition + namePositions[i];
positions[outPos++] = dataSectionPosition + GetResourceDataOffset(i);
}
Array.Sort(positions);
return LazyInit.GetOrSet(ref startPositions, positions);
}
}
internal byte[] GetBytesForSerializedObject(long pos)
{
long[] positions = GetStartPositions();
int i = Array.BinarySearch(positions, pos);
if (i < 0) {
// 'pos' the the start position of the serialized object data
// This is the position after the type code, so it should not appear in the 'positions' array.
// Set i to the index of the next position after 'pos'.
i = ~i;
// Note: if 'pos' does exist in the array, that means the stream has length 0,
// so we keep the i that we found.
}
lock (reader) {
long endPos;
if (i == positions.Length) {
endPos = reader.BaseStream.Length;
} else {
endPos = positions[i];
}
int len = (int)(endPos - pos);
reader.Seek(pos, SeekOrigin.Begin);
return reader.ReadBytes(len);
}
}
}
public class ResourceSerializedObject
{
public string TypeName { get; }
readonly ResourcesFile file;
readonly long position;
internal ResourceSerializedObject(string typeName, ResourcesFile file, long position)
{
this.TypeName = typeName;
this.file = file;
this.position = position;
}
/// <summary>
/// Gets a stream that starts with the serialized object data.
/// </summary>
public Stream GetStream()
{
return new MemoryStream(file.GetBytesForSerializedObject(position), writable: false);
}
/// <summary>
/// Gets the serialized object data.
/// </summary>
public byte[] GetBytes()
{
return file.GetBytesForSerializedObject(position);
}
}
}

109
ILSpy.AddIn/AssemblyFileFinder.cs

@ -0,0 +1,109 @@ @@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Util;
using Mono.Cecil;
namespace ICSharpCode.ILSpy.AddIn
{
public class AssemblyFileFinder
{
public static string FindAssemblyFile(AssemblyDefinition assemblyDefinition, string assemblyFile)
{
var assemblyName = assemblyDefinition.Name;
var detectedTargetFramework = DetectTargetFrameworkId(assemblyDefinition, assemblyFile);
if (string.IsNullOrEmpty(detectedTargetFramework)) {
// Without a target framework id it makes no sense to continue
return null;
}
var targetFramework = detectedTargetFramework.Split(new[] { ",Version=v" }, StringSplitOptions.None);
string file = null;
switch (targetFramework[0]) {
case ".NETCoreApp":
case ".NETStandard":
if (targetFramework.Length != 2)
return FindAssemblyFromGAC(assemblyDefinition);
var version = targetFramework[1].Length == 3 ? targetFramework[1] + ".0" : targetFramework[1];
var dotNetCorePathFinder = new DotNetCorePathFinder(assemblyFile, detectedTargetFramework, version);
file = dotNetCorePathFinder.TryResolveDotNetCore(Decompiler.Metadata.AssemblyNameReference.Parse(assemblyName.FullName));
if (file != null)
return file;
return FindAssemblyFromGAC(assemblyDefinition);
default:
return FindAssemblyFromGAC(assemblyDefinition);
}
}
static string FindAssemblyFromGAC(AssemblyDefinition assemblyDefinition)
{
return GacInterop.FindAssemblyInNetGac(Decompiler.Metadata.AssemblyNameReference.Parse(assemblyDefinition.Name.FullName));
}
static readonly string RefPathPattern = @"NuGetFallbackFolder[/\\][^/\\]+[/\\][^/\\]+[/\\]ref[/\\]";
public static bool IsReferenceAssembly(AssemblyDefinition assemblyDef, string assemblyFile)
{
if (assemblyDef.CustomAttributes.Any(ca => ca.AttributeType.FullName == "System.Runtime.CompilerServices.ReferenceAssemblyAttribute"))
return true;
// Try to detect reference assembly through specific path pattern
var refPathMatch = Regex.Match(assemblyFile, RefPathPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
return refPathMatch.Success;
}
static readonly string DetectTargetFrameworkIdRefPathPattern =
@"(Reference Assemblies[/\\]Microsoft[/\\]Framework[/\\](?<1>.NETFramework)[/\\]v(?<2>[^/\\]+)[/\\])" +
@"|(NuGetFallbackFolder[/\\](?<1>[^/\\]+)\\(?<2>[^/\\]+)([/\\].*)?[/\\]ref[/\\])";
public static string DetectTargetFrameworkId(AssemblyDefinition assembly, string assemblyPath = null)
{
if (assembly == null)
throw new ArgumentNullException(nameof(assembly));
const string TargetFrameworkAttributeName = "System.Runtime.Versioning.TargetFrameworkAttribute";
foreach (var attribute in assembly.CustomAttributes) {
if (attribute.AttributeType.FullName != TargetFrameworkAttributeName)
continue;
if (attribute.HasConstructorArguments) {
if (attribute.ConstructorArguments[0].Value is string value)
return value;
}
}
// Optionally try to detect target version through assembly path as a fallback (use case: reference assemblies)
if (assemblyPath != null) {
/*
* Detected path patterns (examples):
*
* - .NETFramework -> C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\mscorlib.dll
* - .NETCore -> C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.netcore.app\2.1.0\ref\netcoreapp2.1\System.Console.dll
* - .NETStandard -> C:\Program Files\dotnet\sdk\NuGetFallbackFolder\netstandard.library\2.0.3\build\netstandard2.0\ref\netstandard.dll
*/
var pathMatch = Regex.Match(assemblyPath, DetectTargetFrameworkIdRefPathPattern,
RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture);
if (pathMatch.Success) {
var type = pathMatch.Groups[1].Value;
var version = pathMatch.Groups[2].Value;
if (type == ".NETFramework") {
return $".NETFramework,Version=v{version}";
} else if (type.Contains("netcore")) {
return $".NETCoreApp,Version=v{version}";
} else if (type.Contains("netstandard")) {
return $".NETStandard,Version=v{version}";
}
}
}
return string.Empty;
}
}
}

3
ILSpy.AddIn/Commands/OpenCodeItemCommand.cs

@ -106,8 +106,7 @@ namespace ICSharpCode.ILSpy.AddIn.Commands @@ -106,8 +106,7 @@ namespace ICSharpCode.ILSpy.AddIn.Commands
owner.ShowMessage(OLEMSGICON.OLEMSGICON_WARNING,
"Symbol can't be opened. This might happen while project is loading.",
Environment.NewLine, invalidSymbolReference.AssemblyFile);
}
if (invalidSymbolReference.IsProjectReference) {
} else if (invalidSymbolReference.IsProjectReference) {
// Some project references don't have assemblies, maybe not compiled yet?
if (owner.ShowMessage(
OLEMSGBUTTON.OLEMSGBUTTON_YESNO, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST, OLEMSGICON.OLEMSGICON_WARNING,

9
ILSpy.AddIn/Commands/OpenILSpyCommand.cs

@ -89,8 +89,8 @@ namespace ICSharpCode.ILSpy.AddIn.Commands @@ -89,8 +89,8 @@ namespace ICSharpCode.ILSpy.AddIn.Commands
foreach (var reference in parentProject.MetadataReferences) {
using (var assemblyDef = AssemblyDefinition.ReadAssembly(reference.Display)) {
string assemblyName = assemblyDef.Name.Name;
if (IsReferenceAssembly(assemblyDef)) {
string resolvedAssemblyFile = GacInterop.FindAssemblyInNetGac(Decompiler.Metadata.AssemblyNameReference.Parse(assemblyDef.FullName));
if (AssemblyFileFinder.IsReferenceAssembly(assemblyDef, reference.Display)) {
string resolvedAssemblyFile = AssemblyFileFinder.FindAssemblyFile(assemblyDef, reference.Display);
dict.Add(assemblyName,
new DetectedReference(assemblyName, resolvedAssemblyFile, false));
} else {
@ -127,11 +127,6 @@ namespace ICSharpCode.ILSpy.AddIn.Commands @@ -127,11 +127,6 @@ namespace ICSharpCode.ILSpy.AddIn.Commands
return null;
}
protected bool IsReferenceAssembly(AssemblyDefinition assemblyDef)
{
return assemblyDef.CustomAttributes.Any(ca => ca.AttributeType.FullName == "System.Runtime.CompilerServices.ReferenceAssemblyAttribute");
}
}
class OpenILSpyCommand : ILSpyCommand

1
ILSpy.AddIn/ILSpy.AddIn.csproj

@ -84,6 +84,7 @@ @@ -84,6 +84,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PkgCmdID.cs" />
<Compile Include="SyntaxNodeExtensions.cs" />
<Compile Include="AssemblyFileFinder.cs" />
<Compile Include="Utils.cs" />
</ItemGroup>

11
ILSpy.BamlDecompiler.Tests/BamlTestRunner.cs

@ -5,11 +5,11 @@ using System; @@ -5,11 +5,11 @@ using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Resources;
using System.Threading;
using System.Xml.Linq;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Tests.Helpers;
using ICSharpCode.Decompiler.Util;
using NUnit.Framework;
namespace ILSpy.BamlDecompiler.Tests
@ -144,15 +144,16 @@ namespace ILSpy.BamlDecompiler.Tests @@ -144,15 +144,16 @@ namespace ILSpy.BamlDecompiler.Tests
{
if (res.ResourceType != ResourceType.Embedded) return null;
Stream s = res.TryOpenStream();
if (s == null) return null;
s.Position = 0;
ResourceReader reader;
ResourcesFile resources;
try {
reader = new ResourceReader(s);
resources = new ResourcesFile(s);
} catch (ArgumentException) {
return null;
}
foreach (DictionaryEntry entry in reader.Cast<DictionaryEntry>().OrderBy(e => e.Key.ToString())) {
if (entry.Key.ToString() == name) {
foreach (var entry in resources.OrderBy(e => e.Key)) {
if (entry.Key == name) {
if (entry.Value is Stream)
return (Stream)entry.Value;
if (entry.Value is byte[])

8
ILSpy/Languages/CSharpLanguage.cs

@ -17,12 +17,10 @@ @@ -17,12 +17,10 @@
// 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.Windows;
using System.Windows.Controls;
using ICSharpCode.Decompiler;
@ -388,11 +386,11 @@ namespace ICSharpCode.ILSpy @@ -388,11 +386,11 @@ namespace ICSharpCode.ILSpy
protected override IEnumerable<Tuple<string, string>> WriteResourceToFile(string fileName, string resourceName, Stream entryStream)
{
if (fileName.EndsWith(".resource", StringComparison.OrdinalIgnoreCase)) {
using (ResourceReader reader = new ResourceReader(entryStream))
fileName = Path.ChangeExtension(fileName, ".resx");
using (FileStream fs = new FileStream(Path.Combine(targetDirectory, fileName), FileMode.Create, FileAccess.Write))
using (ResXResourceWriter writer = new ResXResourceWriter(fs)) {
foreach (DictionaryEntry entry in reader) {
writer.AddResource((string)entry.Key, entry.Value);
foreach (var entry in new ResourcesFile(entryStream)) {
writer.AddResource(entry.Key, entry.Value);
}
}
return new[] { Tuple.Create("EmbeddedResource", fileName) };

43
ILSpy/TreeNodes/ResourceNodes/ResourcesFileTreeNode.cs

@ -17,15 +17,12 @@ @@ -17,15 +17,12 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Resources;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Util;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.ILSpy.Controls;
using ICSharpCode.ILSpy.TextView;
@ -61,8 +58,7 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -61,8 +58,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
this.LazyLoading = true;
}
public override object Icon
{
public override object Icon {
get { return Images.ResourceResourcesFile; }
}
@ -71,43 +67,39 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -71,43 +67,39 @@ namespace ICSharpCode.ILSpy.TreeNodes
Stream s = Resource.TryOpenStream();
if (s == null) return;
s.Position = 0;
ResourceReader reader;
try {
reader = new ResourceReader(s);
}
catch (ArgumentException) {
return;
}
foreach (DictionaryEntry entry in reader.Cast<DictionaryEntry>().OrderBy(e => e.Key.ToString())) {
foreach (var entry in new ResourcesFile(s)) {
ProcessResourceEntry(entry);
}
} catch (BadImageFormatException) {
// ignore errors
}
}
private void ProcessResourceEntry(DictionaryEntry entry)
private void ProcessResourceEntry(KeyValuePair<string, object> entry)
{
var keyString = entry.Key.ToString();
if (entry.Value is String) {
stringTableEntries.Add(new KeyValuePair<string, string>(keyString, (string)entry.Value));
stringTableEntries.Add(new KeyValuePair<string, string>(entry.Key, (string)entry.Value));
return;
}
if (entry.Value is byte[]) {
Children.Add(ResourceEntryNode.Create(keyString, new MemoryStream((byte[])entry.Value)));
Children.Add(ResourceEntryNode.Create(entry.Key, new MemoryStream((byte[])entry.Value)));
return;
}
var node = ResourceEntryNode.Create(keyString, entry.Value);
var node = ResourceEntryNode.Create(entry.Key, entry.Value);
if (node != null) {
Children.Add(node);
return;
}
string entryType = entry.Value.GetType().FullName;
if (entry.Value is System.Globalization.CultureInfo) {
otherEntries.Add(new SerializedObjectRepresentation(keyString, entryType, ((System.Globalization.CultureInfo)entry.Value).DisplayName));
if (entry.Value == null) {
otherEntries.Add(new SerializedObjectRepresentation(entry.Key, "null", ""));
} else if (entry.Value is ResourceSerializedObject so) {
otherEntries.Add(new SerializedObjectRepresentation(entry.Key, so.TypeName, "<serialized>"));
} else {
otherEntries.Add(new SerializedObjectRepresentation(keyString, entryType, entry.Value.ToString()));
otherEntries.Add(new SerializedObjectRepresentation(entry.Key, entry.Value.GetType().FullName, entry.Value.ToString()));
}
}
@ -127,10 +119,9 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -127,10 +119,9 @@ namespace ICSharpCode.ILSpy.TreeNodes
}
break;
case 2:
var reader = new ResourceReader(s);
using (var writer = new ResXResourceWriter(dlg.OpenFile())) {
foreach (DictionaryEntry entry in reader) {
writer.AddResource(entry.Key.ToString(), entry.Value);
foreach (var entry in new ResourcesFile(s)) {
writer.AddResource(entry.Key, entry.Value);
}
}
break;

3
appveyor.yml

@ -31,8 +31,7 @@ for: @@ -31,8 +31,7 @@ for:
- branches:
only:
- master
- 3.0.x
- 3.1.x
- 3.2.x
artifacts:
- path: ILSpy_binaries.zip
name: ILSpy %APPVEYOR_REPO_BRANCH% %ILSPY_VERSION_NUMBER% binaries

99
doc/Resources.txt

@ -0,0 +1,99 @@ @@ -0,0 +1,99 @@
System.Resources.ResourceReader is unsuitable for deserializing resources in the ILSpy context,
because it tries to deserialize custom resource types, which fails if the types are in a non-GAC assembly.
So we are instead using our own "class ResourcesFile", which is based on the
.NET Core ResourceReader implementation.
struct ResourcesFileFormat {
int32 magicNum; [check == ResourceManager.MagicNumber]
// ResourceManager header:
int32 resMgrHeaderVersion; [check >= 0]
int32 numBytesToSkip; [check >= 0]
if (resMgrHeaderVersion <= 1) {
string readerType;
string resourceSetType;
} else {
byte _[numBytesToSkip];
}
// RuntimeResourceSet header:
int32 version; [check in (1, 2)]
int32 numResources; [check >=0]
int32 numTypes; [check >=0]
string typeName[numTypes];
.align 8;
int32 nameHashes[numResources];
int32 namePositions[numResources]; [check >= 0]
int32 dataSectionOffset; [check >= current position in file]
byte remainderOfFile[];
}
// normal strings in this file format are stored as:
struct string {
compressedint len;
byte value[len]; // interpret as UTF-8
}
// NameEntry #i is stored starting at remainderOfFile[namePositions[i]]
// (that is, namePositions is interpreted relative to the start of the remainderOfFile array)
struct NameEntry {
compressedint len;
byte name[len]; // interpret as UTF-16
int32 dataOffset; [check >= 0]
}
// Found at position ResourcesFileFormat.dataSectionOffset+NameEntry.dataOffset in the file.
struct ValueEntry {
if (version == 1) {
compressedint typeIndex;
if (typeIndex == -1) {
// no value stored; value is implicitly null
} else {
switch (typeName[typeIndex]) {
case string:
case int, uint, long, ulong, sbyte, byte, short, ushort:
case float:
case double:
T value; // value directly stored
case DateTime:
int64 value; // new DateTime(_store.ReadInt64())
case TimeSpan:
int64 value; // new TimeSpan(_store.ReadInt64())
case decimal:
int32 value[4];
default:
byte data[...]; // BinaryFormatter-serialized data using typeName[typeIndex]
}
}
} else if (version == 2) {
compressedint typeCode;
// note: unlike v1, no lookup into the typeName array!
switch (typeCode) {
case null:
// no value stored; value is implicitly null
case string:
case bool:
case int, uint, long, ulong, sbyte, byte, short, ushort:
T value;
case char:
uint16 value;
case float:
case double:
case decimal:
T value;
case DateTime:
int64 value; // DateTime.FromBinary(_store.ReadInt64())
case TimeSpan:
int64 value; // new TimeSpan(_store.ReadInt64())
case ResourceTypeCode.ByteArray:
int32 len;
byte value[len];
case ResourceTypeCode.Stream:
int32 len;
byte value[len];
case >= ResourceTypeCode.StartOfUserTypes:
byte data[...]; // BinaryFormatter-serialized data using typeName[typeCode - ResourceTypeCode.StartOfUserTypes]
}
}
}
Loading…
Cancel
Save