diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index f1b1e5777..3f60f4548 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -130,7 +130,6 @@
-
@@ -356,7 +355,6 @@
-
diff --git a/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs b/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs
index 7d18ef551..80d712353 100644
--- a/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs
+++ b/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs
@@ -36,6 +36,8 @@ using System.IO;
using System.Text;
using System.Xml;
+using ICSharpCode.Decompiler.Metadata;
+
namespace ICSharpCode.Decompiler.Util
{
#if INSIDE_SYSTEM_WEB
@@ -150,7 +152,7 @@ namespace ICSharpCode.Decompiler.Util
{
writer.WriteAttributeString("mimetype", BinSerializedObjectMimeType);
writer.WriteStartElement("value");
- writer.WriteBase64(value, offset, length);
+ WriteNiceBase64(value, offset, length);
}
writer.WriteEndElement();
@@ -274,7 +276,7 @@ namespace ICSharpCode.Decompiler.Util
break;
case ResourceSerializedObject rso:
var bytes = rso.GetBytes();
- WriteBytes(name, null, bytes, 0, bytes.Length, comment);
+ WriteBytes(name, rso.TypeName, bytes, 0, bytes.Length, comment);
break;
default:
throw new NotSupportedException($"Value '{value}' of type {value.GetType().FullName} is not supported by this version of ResXResourceWriter. Use byte arrays or streams instead.");
diff --git a/ICSharpCode.Decompiler/Util/ResourcesFile.cs b/ICSharpCode.Decompiler/Util/ResourcesFile.cs
index 988fa01a2..5ef294a1f 100644
--- a/ICSharpCode.Decompiler/Util/ResourcesFile.cs
+++ b/ICSharpCode.Decompiler/Util/ResourcesFile.cs
@@ -76,12 +76,21 @@ namespace ICSharpCode.Decompiler.Util
StartOfUserTypes = 0x40
}
+ enum SerializationFormat
+ {
+ BinaryFormatter = 1,
+ TypeConverterByteArray = 2,
+ TypeConverterString = 3,
+ ActivatorStream = 4
+ }
+
/// Holds the number used to identify resource files.
public const int MagicNumber = unchecked((int)0xBEEFCACE);
const int ResourceSetVersion = 2;
readonly MyBinaryReader reader;
readonly int version;
+ readonly bool usesSerializationFormat;
readonly int numResources;
readonly string[] typeTable;
readonly int[] namePositions;
@@ -94,7 +103,7 @@ namespace ICSharpCode.Decompiler.Util
/// Creates a new ResourcesFile.
///
/// Input stream.
- /// Whether the stream should be help open when the ResourcesFile is disposed.
+ /// Whether the stream should be held open when the ResourcesFile is disposed.
///
/// 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.
@@ -130,7 +139,8 @@ namespace ICSharpCode.Decompiler.Util
// We don't care about numBytesToSkip; read the rest of the header
// readerType:
- reader.ReadString();
+ string readerType = reader.ReadString();
+ usesSerializationFormat = readerType == "System.Resources.Extensions.DeserializingResourceReader, System.Resources.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51";
// resourceSetType:
reader.ReadString();
}
@@ -369,7 +379,7 @@ namespace ICSharpCode.Decompiler.Util
bits[i] = reader.ReadInt32();
return new decimal(bits);
default:
- return new ResourceSerializedObject(FindType(typeIndex), this, reader.BaseStream.Position);
+ return new ResourceSerializedObject(FindType(typeIndex), this, reader.BaseStream.Position, usesSerializationFormat);
}
}
@@ -461,7 +471,7 @@ namespace ICSharpCode.Decompiler.Util
{
throw new BadImageFormatException("Invalid typeCode");
}
- return new ResourceSerializedObject(FindType(typeCode - ResourceTypeCode.StartOfUserTypes), this, reader.BaseStream.Position);
+ return new ResourceSerializedObject(FindType(typeCode - ResourceTypeCode.StartOfUserTypes), this, reader.BaseStream.Position, usesSerializationFormat);
}
}
@@ -509,7 +519,7 @@ namespace ICSharpCode.Decompiler.Util
}
}
- internal byte[] GetBytesForSerializedObject(long pos)
+ internal byte[] GetBytesForSerializedObject(long pos, bool usesSerializationFormat)
{
long[] positions = GetStartPositions();
int i = Array.BinarySearch(positions, pos);
@@ -535,6 +545,12 @@ namespace ICSharpCode.Decompiler.Util
}
int len = (int)(endPos - pos);
reader.Seek(pos, SeekOrigin.Begin);
+ if (usesSerializationFormat)
+ {
+ int kind = reader.Read7BitEncodedInt();
+ Debug.Assert(Enum.IsDefined(typeof(SerializationFormat), kind));
+ len = reader.Read7BitEncodedInt();
+ }
return reader.ReadBytes(len);
}
}
@@ -545,12 +561,14 @@ namespace ICSharpCode.Decompiler.Util
public string? TypeName { get; }
readonly ResourcesFile file;
readonly long position;
+ readonly bool usesSerializationFormat;
- internal ResourceSerializedObject(string? typeName, ResourcesFile file, long position)
+ internal ResourceSerializedObject(string? typeName, ResourcesFile file, long position, bool usesSerializationFormat)
{
- this.TypeName = typeName;
+ this.TypeName = usesSerializationFormat ? typeName : null;
this.file = file;
this.position = position;
+ this.usesSerializationFormat = usesSerializationFormat;
}
///
@@ -558,7 +576,7 @@ namespace ICSharpCode.Decompiler.Util
///
public Stream GetStream()
{
- return new MemoryStream(file.GetBytesForSerializedObject(position), writable: false);
+ return new MemoryStream(file.GetBytesForSerializedObject(position, usesSerializationFormat), writable: false);
}
///
@@ -566,7 +584,7 @@ namespace ICSharpCode.Decompiler.Util
///
public byte[] GetBytes()
{
- return file.GetBytesForSerializedObject(position);
+ return file.GetBytesForSerializedObject(position, usesSerializationFormat);
}
}
}
diff --git a/ILSpy.Tests/BitmapContainer.resources b/ILSpy.Tests/BitmapContainer.resources
new file mode 100644
index 000000000..d50495a74
Binary files /dev/null and b/ILSpy.Tests/BitmapContainer.resources differ
diff --git a/ILSpy.Tests/ILSpy.Tests.csproj b/ILSpy.Tests/ILSpy.Tests.csproj
index 0f44621f6..8340c2b46 100644
--- a/ILSpy.Tests/ILSpy.Tests.csproj
+++ b/ILSpy.Tests/ILSpy.Tests.csproj
@@ -41,6 +41,19 @@
+
+
+
+
+
+
+
+ ResXCodeGenerator
+
+
+
+
+
diff --git a/ILSpy.Tests/Icon.ico b/ILSpy.Tests/Icon.ico
new file mode 100644
index 000000000..5d06b9f28
Binary files /dev/null and b/ILSpy.Tests/Icon.ico differ
diff --git a/ILSpy.Tests/IconContainer.resx b/ILSpy.Tests/IconContainer.resx
new file mode 100644
index 000000000..842140e25
--- /dev/null
+++ b/ILSpy.Tests/IconContainer.resx
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+
+ Icon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
\ No newline at end of file
diff --git a/ICSharpCode.Decompiler.Tests/Util/ResourceReaderWriterTests.cs b/ILSpy.Tests/ResourceReaderWriterTests.cs
similarity index 63%
rename from ICSharpCode.Decompiler.Tests/Util/ResourceReaderWriterTests.cs
rename to ILSpy.Tests/ResourceReaderWriterTests.cs
index 59f8aadce..4ade2b91a 100644
--- a/ICSharpCode.Decompiler.Tests/Util/ResourceReaderWriterTests.cs
+++ b/ILSpy.Tests/ResourceReaderWriterTests.cs
@@ -17,11 +17,10 @@
// DEALINGS IN THE SOFTWARE.
using System;
+using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
-using System.Resources;
-using System.Runtime.CompilerServices;
using System.Xml.Linq;
using System.Xml.XPath;
@@ -30,13 +29,16 @@ using ICSharpCode.Decompiler.Util;
using NUnit.Framework;
using NUnit.Framework.Internal;
-namespace ICSharpCode.Decompiler.Tests.Util
+using TomsToolbox.Essentials;
+
+namespace ICSharpCode.ILSpy.Tests
{
[TestFixture]
public class ResourceReaderWriterTests
{
- const string WinFormsAssemblyName = ", System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
- const string MSCorLibAssemblyName = ", mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
+ const string winFormsAssemblyName = ", System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
+ const string msCorLibAssemblyName = ", mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
+ const string drawingAssemblyName = ", System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a";
[Serializable]
public class SerializableClass
@@ -45,22 +47,27 @@ namespace ICSharpCode.Decompiler.Tests.Util
public int Age { get; set; }
}
- static readonly object[][] TestWriteCases = {
- new object[] { "Decimal", 1.0m, "1.0", "System.Decimal" + MSCorLibAssemblyName },
- new object[] { "TimeSpan", TimeSpan.FromSeconds(42), "00:00:42", "System.TimeSpan" + MSCorLibAssemblyName },
- new object[] { "DateTime", DateTime.Parse("06/18/2023 21:36:30", CultureInfo.InvariantCulture), "06/18/2023 21:36:30", "System.DateTime" + MSCorLibAssemblyName },
+ static readonly object[][] testWriteCases = {
+ new object[] { "Decimal", 1.0m, "1.0", "System.Decimal" + msCorLibAssemblyName },
+ new object[] { "TimeSpan", TimeSpan.FromSeconds(42), "00:00:42", "System.TimeSpan" + msCorLibAssemblyName },
+ new object[] { "DateTime", DateTime.Parse("06/18/2023 21:36:30", CultureInfo.InvariantCulture), "06/18/2023 21:36:30", "System.DateTime" + msCorLibAssemblyName },
};
- static readonly object[][] TestReadCases = {
+ static readonly object[][] testReadCases = {
new object[] { "Decimal", 1.0m },
new object[] { "TimeSpan", TimeSpan.FromSeconds(42) },
new object[] { "DateTime", DateTime.Parse("06/18/2023 21:36:30", CultureInfo.InvariantCulture) },
};
+ static Stream GetResource(string fileName)
+ {
+ return typeof(ResourceReaderWriterTests).Assembly.GetManifestResourceStream(typeof(ResourceReaderWriterTests).Namespace + "." + fileName);
+ }
+
static MemoryStream ProduceResourcesTestFile(string name, T value)
{
var ms = new MemoryStream();
- var writer = new ResourceWriter(ms);
+ var writer = new System.Resources.ResourceWriter(ms);
writer.AddResource(name, value);
writer.Generate();
ms.Position = 0;
@@ -70,7 +77,7 @@ namespace ICSharpCode.Decompiler.Tests.Util
static XElement ProduceResXTest(string name, T value)
{
using var ms = new MemoryStream();
- var writer = new ResXResourceWriter(ms);
+ var writer = new Decompiler.Util.ResXResourceWriter(ms);
writer.AddResource(name, value);
writer.Generate();
ms.Position = 0;
@@ -94,7 +101,7 @@ namespace ICSharpCode.Decompiler.Tests.Util
[TestCase("Single", 1.0f)]
[TestCase("Double", 1.0d)]
[TestCase("Bytes", new byte[] { 42, 43, 44 })]
- [TestCaseSource(nameof(TestReadCases))]
+ [TestCaseSource(nameof(testReadCases))]
public void Read(string name, object value)
{
using var testFile = ProduceResourcesTestFile(name, value);
@@ -105,22 +112,22 @@ namespace ICSharpCode.Decompiler.Tests.Util
Assert.That(items[0].Value, Is.EqualTo(value));
}
- [TestCase("Null", null, null, "System.Resources.ResXNullRef" + WinFormsAssemblyName)]
+ [TestCase("Null", null, null, "System.Resources.ResXNullRef" + winFormsAssemblyName)]
[TestCase("String", "Hello World!", "Hello World!", null)]
- [TestCase("Bool", true, "True", "System.Boolean" + MSCorLibAssemblyName)]
- [TestCase("Bool", false, "False", "System.Boolean" + MSCorLibAssemblyName)]
- [TestCase("Char", 'A', "A", "System.Char" + MSCorLibAssemblyName)]
- [TestCase("Byte", (byte)1, "1", "System.Byte" + MSCorLibAssemblyName)]
- [TestCase("SByte", (sbyte)-1, "-1", "System.SByte" + MSCorLibAssemblyName)]
- [TestCase("Int16", (short)1, "1", "System.Int16" + MSCorLibAssemblyName)]
- [TestCase("UInt16", (ushort)1, "1", "System.UInt16" + MSCorLibAssemblyName)]
- [TestCase("Int32", 1, "1", "System.Int32" + MSCorLibAssemblyName)]
- [TestCase("UInt32", (uint)1, "1", "System.UInt32" + MSCorLibAssemblyName)]
- [TestCase("Int64", (long)1, "1", "System.Int64" + MSCorLibAssemblyName)]
- [TestCase("UInt64", (ulong)1, "1", "System.UInt64" + MSCorLibAssemblyName)]
- [TestCase("Single", 1.0f, "1", "System.Single" + MSCorLibAssemblyName)]
- [TestCase("Double", 1.0d, "1", "System.Double" + MSCorLibAssemblyName)]
- [TestCaseSource(nameof(TestWriteCases))]
+ [TestCase("Bool", true, "True", "System.Boolean" + msCorLibAssemblyName)]
+ [TestCase("Bool", false, "False", "System.Boolean" + msCorLibAssemblyName)]
+ [TestCase("Char", 'A', "A", "System.Char" + msCorLibAssemblyName)]
+ [TestCase("Byte", (byte)1, "1", "System.Byte" + msCorLibAssemblyName)]
+ [TestCase("SByte", (sbyte)-1, "-1", "System.SByte" + msCorLibAssemblyName)]
+ [TestCase("Int16", (short)1, "1", "System.Int16" + msCorLibAssemblyName)]
+ [TestCase("UInt16", (ushort)1, "1", "System.UInt16" + msCorLibAssemblyName)]
+ [TestCase("Int32", 1, "1", "System.Int32" + msCorLibAssemblyName)]
+ [TestCase("UInt32", (uint)1, "1", "System.UInt32" + msCorLibAssemblyName)]
+ [TestCase("Int64", (long)1, "1", "System.Int64" + msCorLibAssemblyName)]
+ [TestCase("UInt64", (ulong)1, "1", "System.UInt64" + msCorLibAssemblyName)]
+ [TestCase("Single", 1.0f, "1", "System.Single" + msCorLibAssemblyName)]
+ [TestCase("Double", 1.0d, "1", "System.Double" + msCorLibAssemblyName)]
+ [TestCaseSource(nameof(testWriteCases))]
public void Write(string name, object value, string serializedValue, string typeName)
{
var element = ProduceResXTest(name, value);
@@ -153,6 +160,8 @@ namespace ICSharpCode.Decompiler.Tests.Util
var item = items.FirstOrDefault(i => i.Key == "Bitmap");
Assert.That(item.Key, Is.Not.Null);
Assert.That(item.Value, Is.InstanceOf());
+ var rso = (ResourceSerializedObject)item.Value;
+ Assert.That(rso.TypeName, Is.Null);
}
[Test]
@@ -185,5 +194,39 @@ namespace ICSharpCode.Decompiler.Tests.Util
Assert.That(item.Key, Is.Not.Null);
Assert.That(item.Value, Is.InstanceOf());
}
+
+ [Test]
+ public void IconDataCanBeDeserializedFromResX()
+ {
+ // Uses new serialization format
+ var resourcesStream = GetResource("IconContainer.resources");
+ var reader = new ResourcesFile(resourcesStream);
+ var item = reader.Single();
+ Assert.That(item.Key, Is.EqualTo("Icon"));
+ Assert.That(item.Value, Is.InstanceOf());
+ var rso = (ResourceSerializedObject)item.Value;
+ var xml = ProduceResXTest("Icon", rso);
+ Assert.That(xml.GetAttribute("name"), Is.EqualTo("Icon"));
+ Assert.That(xml.GetAttribute("type"), Is.EqualTo("System.Drawing.Icon" + drawingAssemblyName));
+ Assert.That(xml.GetAttribute("mimetype"), Is.EqualTo("application/x-microsoft.net.object.bytearray.base64"));
+ var base64Icon = xml.Element("value").Value;
+ using var memory = new MemoryStream(Convert.FromBase64String(base64Icon));
+ new Icon(memory);
+ }
+
+ [Test]
+ public void BitmapDataCanBeDeserializedFromResX()
+ {
+ // Uses old serialization format
+ var resourcesStream = GetResource("BitmapContainer.resources");
+ var reader = new ResourcesFile(resourcesStream);
+ var item = reader.Single(x => x.Key == "Image1");
+ Assert.That(item.Value, Is.InstanceOf());
+ var rso = (ResourceSerializedObject)item.Value;
+ var xml = ProduceResXTest("Image1", rso);
+ Assert.That(xml.GetAttribute("name"), Is.EqualTo("Image1"));
+ Assert.That(xml.GetAttribute("type"), Is.Null);
+ Assert.That(xml.GetAttribute("mimetype"), Is.EqualTo("application/x-microsoft.net.object.binary.base64"));
+ }
}
}
\ No newline at end of file
diff --git a/ICSharpCode.Decompiler.Tests/Util/Test.resources b/ILSpy.Tests/Test.resources
similarity index 100%
rename from ICSharpCode.Decompiler.Tests/Util/Test.resources
rename to ILSpy.Tests/Test.resources