diff --git a/dnSpy/dnSpy.Decompiler/MSBuild/ResXProjectFile.cs b/dnSpy/dnSpy.Decompiler/MSBuild/ResXProjectFile.cs
index 7bd2e817c9..e4d0ded473 100644
--- a/dnSpy/dnSpy.Decompiler/MSBuild/ResXProjectFile.cs
+++ b/dnSpy/dnSpy.Decompiler/MSBuild/ResXProjectFile.cs
@@ -17,11 +17,7 @@ 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.Linq;
using dnlib.DotNet;
-using dnlib.DotNet.Emit;
using dnlib.DotNet.Resources;
using dnSpy.Decompiler.Properties;
@@ -29,43 +25,27 @@ namespace dnSpy.Decompiler.MSBuild {
sealed class ResXProjectFile : ProjectFile {
public override string Description => dnSpy_Decompiler_Resources.MSBuild_CreateResXFile;
public override BuildAction BuildAction => BuildAction.EmbeddedResource;
- public override string Filename => filename;
- readonly string filename;
-
+ public override string Filename { get; }
public string TypeFullName { get; }
public bool IsSatelliteFile { get; set; }
+ readonly ModuleDef module;
readonly ResourceElementSet resourceElementSet;
- readonly Dictionary newToOldAsm;
public ResXProjectFile(ModuleDef module, string filename, string typeFullName, ResourceElementSet resourceElementSet) {
- this.filename = filename;
+ this.module = module;
+ Filename = filename;
TypeFullName = typeFullName;
this.resourceElementSet = resourceElementSet;
-
- newToOldAsm = new Dictionary(new AssemblyNameComparer(AssemblyNameComparerFlags.All & ~AssemblyNameComparerFlags.Version));
- foreach (var asmRef in module.GetAssemblyRefs())
- newToOldAsm[asmRef] = asmRef;
}
public override void Create(DecompileContext ctx) {
- using (var writer = new ResXResourceFileWriter(Filename, TypeNameConverter)) {
+ using (var writer = new ResXResourceFileWriter(Filename, module)) {
foreach (var resourceElement in resourceElementSet.ResourceElements) {
ctx.CancellationToken.ThrowIfCancellationRequested();
writer.AddResourceData(resourceElement);
}
}
}
-
- string TypeNameConverter(Type type) {
- var newAsm = new AssemblyNameInfo(type.Assembly.GetName());
- if (!newToOldAsm.TryGetValue(newAsm, out var oldAsm))
- return type.AssemblyQualifiedName ?? throw new ArgumentException();
- if (type.IsGenericType)
- return type.AssemblyQualifiedName ?? throw new ArgumentException();
- if (AssemblyNameComparer.CompareAll.Equals(oldAsm, newAsm))
- return type.AssemblyQualifiedName ?? throw new ArgumentException();
- return $"{type.FullName}, {oldAsm.FullName}";
- }
}
}
diff --git a/dnSpy/dnSpy.Decompiler/MSBuild/ResXResourceFileWriter.cs b/dnSpy/dnSpy.Decompiler/MSBuild/ResXResourceFileWriter.cs
index 818cbe5d42..e0e49fe810 100644
--- a/dnSpy/dnSpy.Decompiler/MSBuild/ResXResourceFileWriter.cs
+++ b/dnSpy/dnSpy.Decompiler/MSBuild/ResXResourceFileWriter.cs
@@ -18,12 +18,15 @@ You should have received a copy of the GNU General Public License
*/
using System;
+using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Resources;
using System.Text;
using System.Xml;
+using dnlib.DotNet;
using dnlib.DotNet.Resources;
+using dnlib.DotNet.Writer;
namespace dnSpy.Decompiler.MSBuild {
///
@@ -54,12 +57,48 @@ public ResXResourceInfo(string valueString) {
}
}
- readonly Func typeNameConverter;
+ private const int LengthPropertyOffset = 196;
+ private const int CapacityPropertyOffset = 200;
+ private const int BufferLengthOffset = 214;
+ private const int BufferOffset = 219;
+ private static readonly byte[] memoryStreamBinaryFormatterTemplate = [
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x16, 0x53,
+ 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x49, 0x4F, 0x2E, 0x4D, 0x65, 0x6D,
+ 0x6F, 0x72, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6D, 0x0A, 0x00, 0x00,
+ 0x00, 0x07, 0x5F, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x07, 0x5F, 0x6F,
+ 0x72, 0x69, 0x67, 0x69, 0x6E, 0x09, 0x5F, 0x70, 0x6F, 0x73, 0x69, 0x74,
+ 0x69, 0x6F, 0x6E, 0x07, 0x5F, 0x6C, 0x65, 0x6E, 0x67, 0x74, 0x68, 0x09,
+ 0x5F, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x0B, 0x5F, 0x65,
+ 0x78, 0x70, 0x61, 0x6E, 0x64, 0x61, 0x62, 0x6C, 0x65, 0x09, 0x5F, 0x77,
+ 0x72, 0x69, 0x74, 0x61, 0x62, 0x6C, 0x65, 0x0A, 0x5F, 0x65, 0x78, 0x70,
+ 0x6F, 0x73, 0x61, 0x62, 0x6C, 0x65, 0x07, 0x5F, 0x69, 0x73, 0x4F, 0x70,
+ 0x65, 0x6E, 0x1D, 0x4D, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6C, 0x42, 0x79,
+ 0x52, 0x65, 0x66, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x2B, 0x5F, 0x5F,
+ 0x69, 0x64, 0x65, 0x6E, 0x74, 0x69, 0x74, 0x79, 0x07, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x08, 0x08, 0x08, 0x08, 0x01,
+ 0x01, 0x01, 0x01, 0x09, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, // Length
+ 0x05, 0x00, 0x00, 0x00, // Capacity
+ 0x00, 0x01, 0x00, 0x01, 0x0A, 0x0F, 0x02, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, // buffer length
+ 0x02
+ // buffer
+ // 0x0B
+ ];
+
+ readonly ModuleDef module;
+ readonly Dictionary newToOldAsm;
readonly XmlTextWriter writer;
bool written;
- public ResXResourceFileWriter(string fileName, Func typeNameConverter) {
- this.typeNameConverter = typeNameConverter;
+ public ResXResourceFileWriter(string fileName, ModuleDef module) {
+ this.module = module;
+ newToOldAsm = new Dictionary(new AssemblyNameComparer(AssemblyNameComparerFlags.All & ~AssemblyNameComparerFlags.Version));
+ foreach (var asmRef in module.GetAssemblyRefs())
+ newToOldAsm[asmRef] = asmRef;
+
writer = new XmlTextWriter(fileName, Encoding.UTF8) {
Formatting = Formatting.Indented,
Indentation = 2
@@ -94,14 +133,14 @@ void InitializeWriter() {
writer.WriteStartElement("resheader");
writer.WriteAttributeString("name", "reader");
writer.WriteStartElement("value");
- writer.WriteString(typeNameConverter(typeof(ResXResourceReader)));
+ writer.WriteString(GetTypeName(typeof(ResXResourceReader)));
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteStartElement("resheader");
writer.WriteAttributeString("name", "writer");
writer.WriteStartElement("value");
- writer.WriteString(typeNameConverter(typeof(ResXResourceWriter)));
+ writer.WriteString(GetTypeName(typeof(ResXResourceWriter)));
writer.WriteEndElement();
writer.WriteEndElement();
}
@@ -135,14 +174,14 @@ ResXResourceInfo GetNodeInfo(IResourceData resourceData) {
// Mimic formatting used in ResXDataNode and TypeConverter implementations
switch (builtInResourceData.Code) {
case ResourceTypeCode.Null:
- return new ResXResourceInfo("", typeNameConverter(typeof(ResXDataNode).Assembly.GetType("System.Resources.ResXNullRef")!));
+ return new ResXResourceInfo("", GetTypeName(ResourceTypeCode.Null));
case ResourceTypeCode.String:
return new ResXResourceInfo((string)builtInResourceData.Data);
case ResourceTypeCode.Boolean:
- return new ResXResourceInfo(((bool)builtInResourceData.Data).ToString(), typeNameConverter(typeof(bool)));
+ return new ResXResourceInfo(((bool)builtInResourceData.Data).ToString(), GetTypeName(ResourceTypeCode.Boolean));
case ResourceTypeCode.Char:
var c = (char)builtInResourceData.Data;
- return new ResXResourceInfo(c == '\0' ? "" : c.ToString(), typeNameConverter(typeof(char)));
+ return new ResXResourceInfo(c == '\0' ? "" : c.ToString(), GetTypeName(ResourceTypeCode.Char));
case ResourceTypeCode.Byte:
case ResourceTypeCode.SByte:
case ResourceTypeCode.Int16:
@@ -153,15 +192,15 @@ ResXResourceInfo GetNodeInfo(IResourceData resourceData) {
case ResourceTypeCode.UInt64:
case ResourceTypeCode.Decimal: {
var data = (IFormattable)builtInResourceData.Data;
- return new ResXResourceInfo(data.ToString("G", CultureInfo.InvariantCulture.NumberFormat), typeNameConverter(builtInResourceData.Data.GetType()));
+ return new ResXResourceInfo(data.ToString("G", CultureInfo.InvariantCulture.NumberFormat), GetTypeName(builtInResourceData.Code));
}
case ResourceTypeCode.Single:
case ResourceTypeCode.Double: {
var data = (IFormattable)builtInResourceData.Data;
- return new ResXResourceInfo(data.ToString("R", CultureInfo.InvariantCulture.NumberFormat), typeNameConverter(builtInResourceData.Data.GetType()));
+ return new ResXResourceInfo(data.ToString("R", CultureInfo.InvariantCulture.NumberFormat), GetTypeName(builtInResourceData.Code));
}
case ResourceTypeCode.TimeSpan:
- return new ResXResourceInfo(((IFormattable)builtInResourceData.Data).ToString()!, typeNameConverter(typeof(TimeSpan)));
+ return new ResXResourceInfo(((IFormattable)builtInResourceData.Data).ToString()!, GetTypeName(ResourceTypeCode.TimeSpan));
case ResourceTypeCode.DateTime:
var dateTime = (DateTime)builtInResourceData.Data;
string str;
@@ -171,11 +210,25 @@ ResXResourceInfo GetNodeInfo(IResourceData resourceData) {
str = dateTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
else
str = dateTime.ToString(CultureInfo.InvariantCulture);
- return new ResXResourceInfo(str, typeNameConverter(typeof(DateTime)));
+ return new ResXResourceInfo(str, GetTypeName(ResourceTypeCode.DateTime));
case ResourceTypeCode.ByteArray:
- return new ResXResourceInfo(ToBase64WrappedString((byte[])builtInResourceData.Data), typeNameConverter(typeof(byte[])));
- case ResourceTypeCode.Stream:
- return new ResXResourceInfo(ToBase64WrappedString((byte[])builtInResourceData.Data), null, ResXResourceWriter.BinSerializedObjectMimeType);
+ return new ResXResourceInfo(ToBase64WrappedString((byte[])builtInResourceData.Data), GetTypeName(ResourceTypeCode.ByteArray));
+ case ResourceTypeCode.Stream: {
+ var data = (byte[])builtInResourceData.Data;
+ var finalBuffer = new byte[memoryStreamBinaryFormatterTemplate.Length + data.Length + 1];
+ var bufWriter = new ArrayWriter(finalBuffer);
+ bufWriter.WriteBytes(memoryStreamBinaryFormatterTemplate);
+ bufWriter.Position = LengthPropertyOffset;
+ bufWriter.WriteInt32(data.Length);
+ bufWriter.Position = CapacityPropertyOffset;
+ bufWriter.WriteInt32(data.Length);
+ bufWriter.Position = BufferLengthOffset;
+ bufWriter.WriteInt32(data.Length);
+ bufWriter.Position = BufferOffset;
+ bufWriter.WriteBytes(data);
+ bufWriter.WriteByte(0x0B);
+ return new ResXResourceInfo(ToBase64WrappedString(finalBuffer), null, ResXResourceWriter.BinSerializedObjectMimeType);
+ }
default:
throw new ArgumentOutOfRangeException();
}
@@ -187,7 +240,7 @@ ResXResourceInfo GetNodeInfo(IResourceData resourceData) {
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.
+ // so we fall back 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);
@@ -221,6 +274,56 @@ static string ToBase64WrappedString(byte[] data) {
return raw;
}
+ string GetTypeName(Type type) {
+ IAssembly newAsm = new AssemblyNameInfo(type.Assembly.GetName());
+ if (!newToOldAsm.TryGetValue(newAsm, out var oldAsm))
+ return type.AssemblyQualifiedName ?? throw new ArgumentException();
+ if (type.IsGenericType)
+ return type.AssemblyQualifiedName ?? throw new ArgumentException();
+ if (AssemblyNameComparer.CompareAll.Equals(oldAsm, newAsm))
+ return type.AssemblyQualifiedName ?? throw new ArgumentException();
+ return $"{type.FullName}, {oldAsm.FullName}";
+ }
+
+ string GetTypeName(ResourceTypeCode typeCode) {
+ if (typeCode == ResourceTypeCode.Null) {
+ var asmRef = GetAssemblyRef("System.Windows.Forms");
+ if (asmRef is not null)
+ return new TypeRefUser(module, "System.Resources", "ResXNullRef", asmRef).AssemblyQualifiedName;
+ return GetTypeName(typeof(ResXDataNode).Assembly.GetType("System.Resources.ResXNullRef")!);
+ }
+ return typeCode switch {
+ ResourceTypeCode.String => module.CorLibTypes.String.AssemblyQualifiedName,
+ ResourceTypeCode.Boolean => module.CorLibTypes.Boolean.AssemblyQualifiedName,
+ ResourceTypeCode.Char => module.CorLibTypes.Char.AssemblyQualifiedName,
+ ResourceTypeCode.Byte => module.CorLibTypes.Byte.AssemblyQualifiedName,
+ ResourceTypeCode.SByte => module.CorLibTypes.SByte.AssemblyQualifiedName,
+ ResourceTypeCode.Int16 => module.CorLibTypes.Int16.AssemblyQualifiedName,
+ ResourceTypeCode.UInt16 => module.CorLibTypes.UInt16.AssemblyQualifiedName,
+ ResourceTypeCode.Int32 => module.CorLibTypes.Int32.AssemblyQualifiedName,
+ ResourceTypeCode.UInt32 => module.CorLibTypes.UInt32.AssemblyQualifiedName,
+ ResourceTypeCode.Int64 => module.CorLibTypes.Int64.AssemblyQualifiedName,
+ ResourceTypeCode.UInt64 => module.CorLibTypes.UInt64.AssemblyQualifiedName,
+ ResourceTypeCode.Single => module.CorLibTypes.Single.AssemblyQualifiedName,
+ ResourceTypeCode.Double => module.CorLibTypes.Double.AssemblyQualifiedName,
+ ResourceTypeCode.Decimal => module.CorLibTypes.GetTypeRef("System", "Decimal").AssemblyQualifiedName,
+ ResourceTypeCode.DateTime => module.CorLibTypes.GetTypeRef("System", "DateTime").AssemblyQualifiedName,
+ ResourceTypeCode.TimeSpan => module.CorLibTypes.GetTypeRef("System", "TimeSpan").AssemblyQualifiedName,
+ ResourceTypeCode.ByteArray => new SZArraySig(module.CorLibTypes.Byte).AssemblyQualifiedName,
+ ResourceTypeCode.Stream => module.CorLibTypes.GetTypeRef("System.IO", "MemoryStream")
+ .AssemblyQualifiedName,
+ _ => throw new ArgumentOutOfRangeException(nameof(typeCode), typeCode, null)
+ };
+ }
+
+ AssemblyRef? GetAssemblyRef(string name) {
+ foreach (var asmRef in module.GetAssemblyRefs()) {
+ if (asmRef.Name == name)
+ return asmRef;
+ }
+ return null;
+ }
+
~ResXResourceFileWriter() => Dispose(false);
public void Dispose() {