mirror of https://github.com/icsharpcode/ILSpy.git
8 changed files with 1238 additions and 62 deletions
@ -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
|
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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…
Reference in new issue