// dnlib: See LICENSE.txt for more info using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.Serialization; using System.Text; using System.Text.RegularExpressions; using dnlib.IO; namespace dnlib.DotNet.Resources { /// /// Thrown by /// [Serializable] public sealed class ResourceReaderException : Exception { /// /// Constructor /// public ResourceReaderException() { } /// /// Constructor /// /// Message public ResourceReaderException(string msg) : base(msg) { } /// /// Constructor /// /// /// public ResourceReaderException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// Gets called to create a from serialized data. Returns null /// if a default instance should be created. /// /// ResourceDataFactory /// Serialized type /// Serialized data /// Format of the serialized data /// public delegate IResourceData CreateResourceDataDelegate(ResourceDataFactory resourceDataFactory, UserResourceType type, byte[] serializedData, SerializationFormat format); /// /// Reads .NET resources /// public struct ResourceReader { DataReader reader; readonly uint baseFileOffset; readonly ResourceDataFactory resourceDataFactory; readonly CreateResourceDataDelegate createResourceDataDelegate; ResourceReader(ResourceDataFactory resourceDataFactory, ref DataReader reader, CreateResourceDataDelegate createResourceDataDelegate) { this.reader = reader; this.resourceDataFactory = resourceDataFactory; this.createResourceDataDelegate = createResourceDataDelegate; baseFileOffset = reader.StartOffset; } /// /// Returns true if it's possibly resources file data /// /// Reader /// public static bool CouldBeResourcesFile(DataReader reader) => reader.CanRead(4U) && reader.ReadUInt32() == 0xBEEFCACE; /// /// Reads a .NET resource /// /// Owner module /// Data of resource /// public static ResourceElementSet Read(ModuleDef module, DataReader reader) => Read(module, reader, null); /// /// Reads a .NET resource /// /// Owner module /// Data of resource /// Call back that gets called to create a instance. Can be null. /// public static ResourceElementSet Read(ModuleDef module, DataReader reader, CreateResourceDataDelegate createResourceDataDelegate) => Read(new ResourceDataFactory(module), reader, createResourceDataDelegate); /// /// Reads a .NET resource /// /// User type resource data factory /// Data of resource /// Call back that gets called to create a instance. Can be null. /// public static ResourceElementSet Read(ResourceDataFactory resourceDataFactory, DataReader reader, CreateResourceDataDelegate createResourceDataDelegate) => new ResourceReader(resourceDataFactory, ref reader, createResourceDataDelegate).Read(); ResourceElementSet Read() { uint sig = reader.ReadUInt32(); if (sig != 0xBEEFCACE) throw new ResourceReaderException($"Invalid resource sig: {sig:X8}"); var resources = ReadHeader(); if (resources is null) throw new ResourceReaderException("Invalid resource reader"); resources.FormatVersion = reader.ReadInt32(); if (resources.FormatVersion != 2 && resources.FormatVersion != 1) throw new ResourceReaderException($"Invalid resource version: {resources.FormatVersion}"); int numResources = reader.ReadInt32(); if (numResources < 0) throw new ResourceReaderException($"Invalid number of resources: {numResources}"); int numUserTypes = reader.ReadInt32(); if (numUserTypes < 0) throw new ResourceReaderException($"Invalid number of user types: {numUserTypes}"); var userTypes = new List(); for (int i = 0; i < numUserTypes; i++) userTypes.Add(new UserResourceType(reader.ReadSerializedString(), ResourceTypeCode.UserTypes + i)); reader.Position = (reader.Position + 7) & ~7U; var hashes = new int[numResources]; for (int i = 0; i < numResources; i++) hashes[i] = reader.ReadInt32(); var offsets = new int[numResources]; for (int i = 0; i < numResources; i++) offsets[i] = reader.ReadInt32(); long baseOffset = reader.Position; long dataBaseOffset = reader.ReadInt32(); long nameBaseOffset = reader.Position; long end = reader.Length; var infos = new List(numResources); for (int i = 0; i < numResources; i++) { reader.Position = (uint)(nameBaseOffset + offsets[i]); var name = reader.ReadSerializedString(Encoding.Unicode); long offset = dataBaseOffset + reader.ReadInt32(); infos.Add(new ResourceInfo(name, offset)); } infos.Sort((a, b) => a.offset.CompareTo(b.offset)); for (int i = 0; i < infos.Count; i++) { var info = infos[i]; var element = new ResourceElement(); element.Name = info.name; reader.Position = (uint)info.offset; long nextDataOffset = i == infos.Count - 1 ? end : infos[i + 1].offset; int size = (int)(nextDataOffset - info.offset); element.ResourceData = resources.FormatVersion == 1 ? ReadResourceDataV1(userTypes, resources.ReaderType, size) : ReadResourceDataV2(userTypes, resources.ReaderType, size); element.ResourceData.StartOffset = baseFileOffset + (FileOffset)info.offset; element.ResourceData.EndOffset = baseFileOffset + (FileOffset)reader.Position; resources.Add(element); } return resources; } sealed class ResourceInfo { public readonly string name; public readonly long offset; public ResourceInfo(string name, long offset) { this.name = name; this.offset = offset; } public override string ToString() => $"{offset:X8} - {name}"; } IResourceData ReadResourceDataV2(List userTypes, ResourceReaderType readerType, int size) { uint endPos = reader.Position + (uint)size; uint code = reader.Read7BitEncodedUInt32(); switch ((ResourceTypeCode)code) { case ResourceTypeCode.Null: return resourceDataFactory.CreateNull(); case ResourceTypeCode.String: return resourceDataFactory.Create(reader.ReadSerializedString()); case ResourceTypeCode.Boolean: return resourceDataFactory.Create(reader.ReadBoolean()); case ResourceTypeCode.Char: return resourceDataFactory.Create(reader.ReadChar()); case ResourceTypeCode.Byte: return resourceDataFactory.Create(reader.ReadByte()); case ResourceTypeCode.SByte: return resourceDataFactory.Create(reader.ReadSByte()); case ResourceTypeCode.Int16: return resourceDataFactory.Create(reader.ReadInt16()); case ResourceTypeCode.UInt16: return resourceDataFactory.Create(reader.ReadUInt16()); case ResourceTypeCode.Int32: return resourceDataFactory.Create(reader.ReadInt32()); case ResourceTypeCode.UInt32: return resourceDataFactory.Create(reader.ReadUInt32()); case ResourceTypeCode.Int64: return resourceDataFactory.Create(reader.ReadInt64()); case ResourceTypeCode.UInt64: return resourceDataFactory.Create(reader.ReadUInt64()); case ResourceTypeCode.Single: return resourceDataFactory.Create(reader.ReadSingle()); case ResourceTypeCode.Double: return resourceDataFactory.Create(reader.ReadDouble()); case ResourceTypeCode.Decimal: return resourceDataFactory.Create(reader.ReadDecimal()); case ResourceTypeCode.DateTime: return resourceDataFactory.Create(DateTime.FromBinary(reader.ReadInt64())); case ResourceTypeCode.TimeSpan: return resourceDataFactory.Create(new TimeSpan(reader.ReadInt64())); case ResourceTypeCode.ByteArray:return resourceDataFactory.Create(reader.ReadBytes(reader.ReadInt32())); case ResourceTypeCode.Stream: return resourceDataFactory.CreateStream(reader.ReadBytes(reader.ReadInt32())); default: int userTypeIndex = (int)(code - (uint)ResourceTypeCode.UserTypes); if (userTypeIndex < 0 || userTypeIndex >= userTypes.Count) throw new ResourceReaderException($"Invalid resource data code: {code}"); return ReadSerializedObject(endPos, readerType, userTypes[userTypeIndex]); } } IResourceData ReadResourceDataV1(List userTypes, ResourceReaderType readerType, int size) { uint endPos = reader.Position + (uint)size; int typeIndex = reader.Read7BitEncodedInt32(); if (typeIndex == -1) return resourceDataFactory.CreateNull(); if (typeIndex < 0 || typeIndex >= userTypes.Count) throw new ResourceReaderException($"Invalid resource type index: {typeIndex}"); var type = userTypes[typeIndex]; var commaIndex = type.Name.IndexOf(','); string actualName = commaIndex == -1 ? type.Name : type.Name.Remove(commaIndex); switch (actualName) { case "System.String": return resourceDataFactory.Create(reader.ReadSerializedString()); case "System.Int32": return resourceDataFactory.Create(reader.ReadInt32()); case "System.Byte": return resourceDataFactory.Create(reader.ReadByte()); case "System.SByte": return resourceDataFactory.Create(reader.ReadSByte()); case "System.Int16": return resourceDataFactory.Create(reader.ReadInt16()); case "System.Int64": return resourceDataFactory.Create(reader.ReadInt64()); case "System.UInt16": return resourceDataFactory.Create(reader.ReadUInt16()); case "System.UInt32": return resourceDataFactory.Create(reader.ReadUInt32()); case "System.UInt64": return resourceDataFactory.Create(reader.ReadUInt64()); case "System.Single": return resourceDataFactory.Create(reader.ReadSingle()); case "System.Double": return resourceDataFactory.Create(reader.ReadDouble()); case "System.DateTime": return resourceDataFactory.Create(new DateTime(reader.ReadInt64())); case "System.TimeSpan": return resourceDataFactory.Create(new TimeSpan(reader.ReadInt64())); case "System.Decimal": return resourceDataFactory.Create(reader.ReadDecimal()); default: return ReadSerializedObject(endPos, readerType, type); } } IResourceData ReadSerializedObject(uint endPos, ResourceReaderType readerType, UserResourceType type) { byte[] serializedData; IResourceData res; switch (readerType) { case ResourceReaderType.ResourceReader: serializedData = reader.ReadBytes((int)(endPos - reader.Position)); res = createResourceDataDelegate?.Invoke(resourceDataFactory, type, serializedData, SerializationFormat.BinaryFormatter); return res ?? resourceDataFactory.CreateSerialized(serializedData, SerializationFormat.BinaryFormatter, type); case ResourceReaderType.DeserializingResourceReader: { var format = (SerializationFormat)reader.Read7BitEncodedInt32(); if (format < SerializationFormat.BinaryFormatter || format > SerializationFormat.ActivatorStream) throw new ResourceReaderException($"Invalid serialization format: {format}"); int length = reader.Read7BitEncodedInt32(); Debug.Assert(length == (int)(endPos - reader.Position)); serializedData = reader.ReadBytes(length); res = createResourceDataDelegate?.Invoke(resourceDataFactory, type, serializedData, format); return res ?? resourceDataFactory.CreateSerialized(serializedData, format, type); } default: throw new ResourceReaderException($"Invalid reader type: {readerType}"); } } ResourceElementSet ReadHeader() { int headerVersion = reader.ReadInt32(); if (headerVersion != 1) throw new ResourceReaderException($"Invalid or unsupported header version: {headerVersion}"); int headerSize = reader.ReadInt32(); if (headerSize < 0) throw new ResourceReaderException($"Invalid header size: {headerSize:X8}"); string resourceReaderTypeName = reader.ReadSerializedString(); string resourceSetTypeName = reader.ReadSerializedString(); ResourceReaderType readerType; if (Regex.IsMatch(resourceReaderTypeName, ResourceElementSet.ResourceReaderTypeNameRegex)) readerType = ResourceReaderType.ResourceReader; else if (Regex.IsMatch(resourceReaderTypeName, ResourceElementSet.DeserializingResourceReaderTypeNameRegex)) readerType = ResourceReaderType.DeserializingResourceReader; else return null; return new ResourceElementSet(resourceReaderTypeName, resourceSetTypeName, readerType); } } }