diff --git a/DnSpyCommon.props b/DnSpyCommon.props index 6b7a8c6dba..ddc935493e 100644 --- a/DnSpyCommon.props +++ b/DnSpyCommon.props @@ -43,7 +43,7 @@ 1.7.0 - 3.6.0 + 4.1.0 1.20.0 17.1.0 1.1.142101 diff --git a/Extensions/dnSpy.AsmEditor/Resources/ResourceCommands.cs b/Extensions/dnSpy.AsmEditor/Resources/ResourceCommands.cs index 8dffff5754..430cd24bb6 100644 --- a/Extensions/dnSpy.AsmEditor/Resources/ResourceCommands.cs +++ b/Extensions/dnSpy.AsmEditor/Resources/ResourceCommands.cs @@ -766,7 +766,7 @@ static void Execute(Lazy undoCommandService, IAppService ap return; var outStream = new MemoryStream(); - ResourceWriter.Write(module, outStream, new ResourceElementSet()); + ResourceWriter.Write(module, outStream, ResourceElementSet.CreateForResourceReader(module)); var er = new EmbeddedResource(data.Name, outStream.ToArray(), data.Attributes); var treeView = appService.DocumentTreeView.TreeView; var treeNodeGroup = appService.DocumentTreeView.DocumentTreeNodeGroups.GetGroup(DocumentTreeNodeGroupType.ResourceTreeNodeGroup); @@ -2038,7 +2038,8 @@ static void Execute(Lazy undoCommandService, IAppService ap var opts = data.CreateResourceElementOptions(); string? error; try { - opts = new ResourceElementOptions(SerializedImageUtilities.Serialize(opts.Create())); + var format = ((BinaryResourceData)imgRsrcElNode.ResourceElement.ResourceData).Format; + opts = new ResourceElementOptions(SerializedImageUtilities.Serialize(opts.Create(), format)); error = imgRsrcElNode.CheckCanUpdateData(opts.Create()); } catch (Exception ex) { diff --git a/Extensions/dnSpy.AsmEditor/Resources/ResourceElementVM.cs b/Extensions/dnSpy.AsmEditor/Resources/ResourceElementVM.cs index 1c916de69e..8f9fd38118 100644 --- a/Extensions/dnSpy.AsmEditor/Resources/ResourceElementVM.cs +++ b/Extensions/dnSpy.AsmEditor/Resources/ResourceElementVM.cs @@ -292,7 +292,7 @@ IResourceData CreateResourceData() { case ResourceElementType.TimeSpan: return new BuiltInResourceData((ResourceTypeCode)code, TimeSpanVM.Value); case ResourceElementType.ByteArray: return new BuiltInResourceData((ResourceTypeCode)code, Data ?? Array.Empty()); case ResourceElementType.Stream: return new BuiltInResourceData((ResourceTypeCode)code, Data ?? Array.Empty()); - case ResourceElementType.SerializedType: return new BinaryResourceData(new UserResourceType(UserTypeVM.TypeFullName, ResourceTypeCode.UserTypes), UserTypeVM.GetSerializedData()); + case ResourceElementType.SerializedType: return new BinaryResourceData(new UserResourceType(UserTypeVM.TypeFullName, ResourceTypeCode.UserTypes), UserTypeVM.GetSerializedData(), SerializationFormat.BinaryFormatter); default: throw new InvalidOperationException(); } } diff --git a/dnSpy/dnSpy.Contracts.DnSpy/Documents/TreeView/Resources/SerializationUtilities.cs b/dnSpy/dnSpy.Contracts.DnSpy/Documents/TreeView/Resources/SerializationUtilities.cs index a7e7269c51..e9dfbae570 100644 --- a/dnSpy/dnSpy.Contracts.DnSpy/Documents/TreeView/Resources/SerializationUtilities.cs +++ b/dnSpy/dnSpy.Contracts.DnSpy/Documents/TreeView/Resources/SerializationUtilities.cs @@ -56,7 +56,7 @@ static ResourceElement CreateSerializedImage(Stream stream, string filename) { var userType = new UserResourceType(typeName, ResourceTypeCode.UserTypes); var rsrcElem = new ResourceElement { Name = Path.GetFileName(filename), - ResourceData = new BinaryResourceData(userType, serializedData), + ResourceData = new BinaryResourceData(userType, serializedData, SerializationFormat.BinaryFormatter), }; return rsrcElem; diff --git a/dnSpy/dnSpy.Contracts.DnSpy/Documents/TreeView/Resources/SerializedImageListStreamerUtilities.cs b/dnSpy/dnSpy.Contracts.DnSpy/Documents/TreeView/Resources/SerializedImageListStreamerUtilities.cs index 020d38944e..d7641656d6 100644 --- a/dnSpy/dnSpy.Contracts.DnSpy/Documents/TreeView/Resources/SerializedImageListStreamerUtilities.cs +++ b/dnSpy/dnSpy.Contracts.DnSpy/Documents/TreeView/Resources/SerializedImageListStreamerUtilities.cs @@ -93,7 +93,7 @@ public static ResourceElement Serialize(ImageListOptions opts) { var typeName = SystemWindowsFormsImageListStreamer.AssemblyQualifiedName; return new ResourceElement { Name = opts.Name, - ResourceData = new BinaryResourceData(new UserResourceType(typeName, ResourceTypeCode.UserTypes), SerializationUtilities.Serialize(obj)), + ResourceData = new BinaryResourceData(new UserResourceType(typeName, ResourceTypeCode.UserTypes), SerializationUtilities.Serialize(obj), SerializationFormat.BinaryFormatter), }; } diff --git a/dnSpy/dnSpy.Contracts.DnSpy/Documents/TreeView/Resources/SerializedImageUtilities.cs b/dnSpy/dnSpy.Contracts.DnSpy/Documents/TreeView/Resources/SerializedImageUtilities.cs index f9bcf38ee9..a7ef3939c8 100644 --- a/dnSpy/dnSpy.Contracts.DnSpy/Documents/TreeView/Resources/SerializedImageUtilities.cs +++ b/dnSpy/dnSpy.Contracts.DnSpy/Documents/TreeView/Resources/SerializedImageUtilities.cs @@ -18,8 +18,10 @@ You should have received a copy of the GNU General Public License */ using System; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Text; using dnlib.DotNet; using dnlib.DotNet.Resources; @@ -36,34 +38,84 @@ public static class SerializedImageUtilities { /// Serialized data /// Updated with the image data /// - public static bool GetImageData(ModuleDef? module, string typeName, byte[] serializedData, [NotNullWhen(true)] out byte[]? imageData) { + public static bool GetImageData(ModuleDef? module, string typeName, byte[] serializedData, [NotNullWhen(true)] out byte[]? imageData) => + GetImageData(module, typeName, serializedData, SerializationFormat.BinaryFormatter, out imageData); + + /// + /// Gets the image data + /// + /// Module + /// Name of type + /// Serialized data + /// Format of serialized data + /// Updated with the image data + /// + public static bool GetImageData(ModuleDef? module, string typeName, byte[] serializedData, SerializationFormat format, [NotNullWhen(true)] out byte[]? imageData) { imageData = null; + if (CouldBeBitmap(module, typeName)) { - var dict = Deserializer.Deserialize(SystemDrawingBitmap.DefinitionAssembly.FullName, SystemDrawingBitmap.ReflectionFullName, serializedData); - // Bitmap loops over every item looking for "Data" (case insensitive) - foreach (var v in dict.Values) { - var d = v.Value as byte[]; - if (d is null) - continue; - if ("Data".Equals(v.Name, StringComparison.OrdinalIgnoreCase)) { - imageData = d; - return true; + if (format == SerializationFormat.BinaryFormatter) { + var dict = Deserializer.Deserialize(SystemDrawingBitmap.DefinitionAssembly.FullName, SystemDrawingBitmap.ReflectionFullName, serializedData); + // Bitmap loops over every item looking for "Data" (case insensitive) + foreach (var v in dict.Values) { + var d = v.Value as byte[]; + if (d is null) + continue; + if ("Data".Equals(v.Name, StringComparison.OrdinalIgnoreCase)) { + imageData = d; + return true; + } } + return false; + } + if (format == SerializationFormat.ActivatorStream) { + imageData = serializedData; + return true; + } + if (format == SerializationFormat.TypeConverterByteArray) { + imageData = GetBitmapData(serializedData) ?? serializedData; + return true; } - return false; } if (CouldBeIcon(module, typeName)) { - var dict = Deserializer.Deserialize(SystemDrawingIcon.DefinitionAssembly.FullName, SystemDrawingIcon.ReflectionFullName, serializedData); - if (!dict.TryGetValue("IconData", out var info)) - return false; - imageData = info.Value as byte[]; - return imageData is not null; + if (format == SerializationFormat.BinaryFormatter) { + var dict = Deserializer.Deserialize(SystemDrawingIcon.DefinitionAssembly.FullName, SystemDrawingIcon.ReflectionFullName, serializedData); + if (!dict.TryGetValue("IconData", out var info)) + return false; + imageData = info.Value as byte[]; + return imageData is not null; + } + if (format == SerializationFormat.ActivatorStream || format == SerializationFormat.TypeConverterByteArray) { + imageData = serializedData; + return true; + } } return false; } + static byte[]? GetBitmapData(byte[] rawData) { + // Based on ImageConverter.GetBitmapStream + // See https://github.com/dotnet/winforms/blob/main/src/System.Drawing.Common/src/System/Drawing/ImageConverter.cs + if (rawData.Length <= 18) + return null; + + short sig = (short)(rawData[0] | rawData[1] << 8); + if (sig != 0x1C15) + return null; + + short headerSize = (short)(rawData[2] | rawData[3] << 8); + if (rawData.Length <= headerSize + 18) + return null; + if (Encoding.ASCII.GetString(rawData, headerSize + 12, 6) != "PBrush") + return null; + + var newData = new byte[rawData.Length - 78]; + Buffer.BlockCopy(rawData, 78, newData, 0, newData.Length); + return newData; + } + static bool CouldBeBitmap(ModuleDef? module, string name) => CheckType(module, name, SystemDrawingBitmap); static bool CouldBeIcon(ModuleDef? module, string name) => CheckType(module, name, SystemDrawingIcon); @@ -99,7 +151,15 @@ public static bool CheckType(ModuleDef? module, string name, TypeRef expectedTyp /// /// Resource element /// - public static ResourceElement Serialize(ResourceElement resElem) { + public static ResourceElement Serialize(ResourceElement resElem) => Serialize(resElem, SerializationFormat.BinaryFormatter); + + /// + /// Serializes the image + /// + /// Resource element + /// Serialization format to use + /// + public static ResourceElement Serialize(ResourceElement resElem, SerializationFormat format) { var data = (byte[])((BuiltInResourceData)resElem.ResourceData).Data; bool isIcon = BitConverter.ToUInt32(data, 0) == 0x00010000; @@ -114,9 +174,32 @@ public static ResourceElement Serialize(ResourceElement resElem) { typeName = SystemDrawingBitmap.AssemblyQualifiedName; } + byte[] serializedData; + if (format == SerializationFormat.BinaryFormatter) { + serializedData = SerializationUtilities.Serialize(obj); + } + else if (format == SerializationFormat.TypeConverterByteArray) { + var converter = TypeDescriptor.GetConverter(obj.GetType()); + var byteArr = converter.ConvertTo(obj, typeof(byte[])); + if (byteArr is not byte[] d) + throw new InvalidOperationException("Failed to serialize image"); + serializedData = d; + } + else if (format == SerializationFormat.ActivatorStream) { + using (var stream = new MemoryStream()) { + if (obj is System.Drawing.Bitmap bitmap) + bitmap.Save(stream, bitmap.RawFormat); + else + ((System.Drawing.Icon)obj).Save(stream); + serializedData = stream.ToArray(); + } + } + else + throw new ArgumentOutOfRangeException(nameof(format)); + return new ResourceElement { Name = resElem.Name, - ResourceData = new BinaryResourceData(new UserResourceType(typeName, ResourceTypeCode.UserTypes), SerializationUtilities.Serialize(obj)), + ResourceData = new BinaryResourceData(new UserResourceType(typeName, ResourceTypeCode.UserTypes), serializedData, format), }; } } diff --git a/dnSpy/dnSpy.Contracts.DnSpy/Documents/TreeView/Resources/SerializedResourceElementNode.cs b/dnSpy/dnSpy.Contracts.DnSpy/Documents/TreeView/Resources/SerializedResourceElementNode.cs index 8560f692fb..50acb6a645 100644 --- a/dnSpy/dnSpy.Contracts.DnSpy/Documents/TreeView/Resources/SerializedResourceElementNode.cs +++ b/dnSpy/dnSpy.Contracts.DnSpy/Documents/TreeView/Resources/SerializedResourceElementNode.cs @@ -17,10 +17,13 @@ You should have received a copy of the GNU General Public License along with dnSpy. If not, see . */ +using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Runtime.Serialization.Formatters.Binary; +using System.Text; using System.Threading; using dnlib.DotNet.Resources; using dnSpy.Contracts.Images; @@ -91,16 +94,59 @@ public void Deserialize() { if (!CanDeserialize) return; - var serializedData = ((BinaryResourceData)ResourceElement.ResourceData).Data; - var formatter = new BinaryFormatter(); - try { + var binaryResourceData = ((BinaryResourceData)ResourceElement.ResourceData); + var serializedData = binaryResourceData.Data; + if (binaryResourceData.Format == SerializationFormat.BinaryFormatter) { + var formatter = new BinaryFormatter(); + try { #pragma warning disable SYSLIB0011 - deserializedData = formatter.Deserialize(new MemoryStream(serializedData)); + deserializedData = formatter.Deserialize(new MemoryStream(serializedData)); #pragma warning restore SYSLIB0011 + } + catch { + return; + } } - catch { - return; + else if (binaryResourceData.Format == SerializationFormat.TypeConverterByteArray) { + try { + var type = Type.GetType(binaryResourceData.TypeName); + if (type is null) + return; + var converter = TypeDescriptor.GetConverter(type); + if (converter is null) + return; + deserializedData = converter.ConvertFrom(serializedData); + } + catch { + return; + } } + else if (binaryResourceData.Format == SerializationFormat.TypeConverterString) { + try { + var type = Type.GetType(binaryResourceData.TypeName); + if (type is null) + return; + var converter = TypeDescriptor.GetConverter(type); + if (converter is null) + return; + deserializedData = converter.ConvertFromInvariantString(Encoding.UTF8.GetString(serializedData)); + } + catch { + return; + } + } + else if (binaryResourceData.Format == SerializationFormat.ActivatorStream) { + try { + var type = Type.GetType(binaryResourceData.TypeName); + if (type is null) + return; + deserializedData = Activator.CreateInstance(type, new MemoryStream(serializedData)); + } + catch { + return; + } + } + if (deserializedData is null) return; diff --git a/dnSpy/dnSpy.Decompiler/MSBuild/ResXResourceFileWriter.cs b/dnSpy/dnSpy.Decompiler/MSBuild/ResXResourceFileWriter.cs index 717cc87765..94fd9187e3 100644 --- a/dnSpy/dnSpy.Decompiler/MSBuild/ResXResourceFileWriter.cs +++ b/dnSpy/dnSpy.Decompiler/MSBuild/ResXResourceFileWriter.cs @@ -1,4 +1,4 @@ -/* +/* Copyright (C) 2023 ElektroKill This file is part of dnSpy @@ -182,8 +182,19 @@ ResXResourceInfo GetNodeInfo(IResourceData resourceData) { throw new ArgumentOutOfRangeException(); } } - if (resourceData is BinaryResourceData binaryResourceData) - return new ResXResourceInfo(ToBase64WrappedString(binaryResourceData.Data), binaryResourceData.TypeName, ResXResourceWriter.BinSerializedObjectMimeType); + if (resourceData is BinaryResourceData binaryResourceData) { + switch (binaryResourceData.Format) { + case SerializationFormat.BinaryFormatter: + return new ResXResourceInfo(ToBase64WrappedString(binaryResourceData.Data), binaryResourceData.TypeName, ResXResourceWriter.BinSerializedObjectMimeType); + case SerializationFormat.TypeConverterByteArray: + case SerializationFormat.ActivatorStream: + // RESX does not have a way to represent creation of an object using Activator.CreateInstance, + // so we fallback to the same representation as data passed into TypeConverter. + return new ResXResourceInfo(ToBase64WrappedString(binaryResourceData.Data), binaryResourceData.TypeName, ResXResourceWriter.ByteArraySerializedObjectMimeType); + case SerializationFormat.TypeConverterString: + return new ResXResourceInfo(Encoding.UTF8.GetString(binaryResourceData.Data), binaryResourceData.TypeName); + } + } throw new ArgumentOutOfRangeException(); } diff --git a/dnSpy/dnSpy/Documents/TreeView/Resources/ResourceElementSetNodeImpl.cs b/dnSpy/dnSpy/Documents/TreeView/Resources/ResourceElementSetNodeImpl.cs index 0db9a99c2c..0fe368892a 100644 --- a/dnSpy/dnSpy/Documents/TreeView/Resources/ResourceElementSetNodeImpl.cs +++ b/dnSpy/dnSpy/Documents/TreeView/Resources/ResourceElementSetNodeImpl.cs @@ -99,7 +99,7 @@ public override void RegenerateEmbeddedResource() { void RegenerateEmbeddedResource(ModuleDef module) { TreeNode.EnsureChildrenLoaded(); var outStream = new MemoryStream(); - var resources = new ResourceElementSet(); + var resources = resourceElementSet.Clone(); foreach (DocumentTreeNodeData child in TreeNode.DataChildren) { var resourceElement = ResourceElementNode.GetResourceElement(child); if (resourceElement is null) diff --git a/dnSpy/dnSpy/Documents/TreeView/Resources/SerializedImageResourceElementNode.cs b/dnSpy/dnSpy/Documents/TreeView/Resources/SerializedImageResourceElementNode.cs index d17a7ef783..045ad294e8 100644 --- a/dnSpy/dnSpy/Documents/TreeView/Resources/SerializedImageResourceElementNode.cs +++ b/dnSpy/dnSpy/Documents/TreeView/Resources/SerializedImageResourceElementNode.cs @@ -42,7 +42,7 @@ sealed class SerializedImageResourceElementNodeProvider : IResourceNodeProvider if (serializedData is null) return null; - if (SerializedImageUtilities.GetImageData(module, serializedData.TypeName, serializedData.Data, out var imageData)) + if (SerializedImageUtilities.GetImageData(module, serializedData.TypeName, serializedData.Data, serializedData.Format, out var imageData)) return new SerializedImageResourceElementNodeImpl(treeNodeGroup, resourceElement, imageData); return null; @@ -94,7 +94,7 @@ protected override IEnumerable GetDeserializedData() { return res; var binData = (BinaryResourceData)newResElem.ResourceData; - if (!SerializedImageUtilities.GetImageData(this.GetModule(), binData.TypeName, binData.Data, out var imageData)) + if (!SerializedImageUtilities.GetImageData(this.GetModule(), binData.TypeName, binData.Data, binData.Format, out var imageData)) return dnSpy_Resources.NewDataIsNotAnImage; try { @@ -111,7 +111,7 @@ public override void UpdateData(ResourceElement newResElem) { base.UpdateData(newResElem); var binData = (BinaryResourceData)newResElem.ResourceData; - SerializedImageUtilities.GetImageData(this.GetModule(), binData.TypeName, binData.Data, out var imageData); + SerializedImageUtilities.GetImageData(this.GetModule(), binData.TypeName, binData.Data, binData.Format, out var imageData); Debug2.Assert(imageData is not null); InitializeImageData(imageData); } diff --git a/dnSpy/dnSpy/app.config b/dnSpy/dnSpy/app.config index cdb732c52e..16f54a9c01 100644 --- a/dnSpy/dnSpy/app.config +++ b/dnSpy/dnSpy/app.config @@ -45,7 +45,7 @@ - +