// dnlib: See LICENSE.txt for more info using System; using System.Collections.Generic; using dnlib.Utils; using dnlib.IO; using dnlib.PE; namespace dnlib.W32Resources { /// /// A Win32 resource directory (see IMAGE_RESOURCE_DIRECTORY in the Windows SDK) /// public abstract class ResourceDirectory : ResourceDirectoryEntry { /// See protected uint characteristics; /// See protected uint timeDateStamp; /// See protected ushort majorVersion; /// See protected ushort minorVersion; /// See private protected LazyList directories; /// See private protected LazyList data; /// /// Gets/sets the characteristics /// public uint Characteristics { get => characteristics; set => characteristics = value; } /// /// Gets/sets the time date stamp /// public uint TimeDateStamp { get => timeDateStamp; set => timeDateStamp = value; } /// /// Gets/sets the major version number /// public ushort MajorVersion { get => majorVersion; set => majorVersion = value; } /// /// Gets/sets the minor version number /// public ushort MinorVersion { get => minorVersion; set => minorVersion = value; } /// /// Gets all directory entries /// public IList Directories => directories; /// /// Gets all resource data /// public IList Data => data; /// /// Constructor /// /// Name protected ResourceDirectory(ResourceName name) : base(name) { } /// /// Finds a by name /// /// Name /// A or null if it wasn't found public ResourceDirectory FindDirectory(ResourceName name) { foreach (var dir in directories) { if (dir.Name == name) return dir; } return null; } /// /// Finds a by name /// /// Name /// A or null if it wasn't found public ResourceData FindData(ResourceName name) { foreach (var d in data) { if (d.Name == name) return d; } return null; } } /// /// A Win32 resource directory created by the user /// public class ResourceDirectoryUser : ResourceDirectory { /// /// Constructor /// /// Name public ResourceDirectoryUser(ResourceName name) : base(name) { directories = new LazyList(); data = new LazyList(); } } /// /// A Win32 resource directory created from a PE file /// public sealed class ResourceDirectoryPE : ResourceDirectory { /// /// To make sure we don't get stuck in an infinite loop, don't allow more than this /// many sub directories. /// const uint MAX_DIR_DEPTH = 10; /// Owner readonly Win32ResourcesPE resources; /// Directory depth. When creating more 's, /// the instances get this value + 1 uint depth; /// /// Info about all 's we haven't created yet /// List dataInfos; /// /// Info about all 's we haven't created yet /// List dirInfos; readonly struct EntryInfo { public readonly ResourceName name; /// Offset of resource directory / data public readonly uint offset; public EntryInfo(ResourceName name, uint offset) { this.name = name; this.offset = offset; } public override string ToString() => $"{offset:X8} {name}"; } /// /// Constructor /// /// Starts from 0. If it's big enough, we'll stop reading more data. /// Name /// Resources /// Reader positioned at the start of this resource directory public ResourceDirectoryPE(uint depth, ResourceName name, Win32ResourcesPE resources, ref DataReader reader) : base(name) { this.resources = resources; this.depth = depth; Initialize(ref reader); } /// /// Reads the directory header and initializes and /// . /// /// void Initialize(ref DataReader reader) { if (depth > MAX_DIR_DEPTH || !reader.CanRead(16U)) { InitializeDefault(); return; } characteristics = reader.ReadUInt32(); timeDateStamp = reader.ReadUInt32(); majorVersion = reader.ReadUInt16(); minorVersion = reader.ReadUInt16(); ushort numNamed = reader.ReadUInt16(); ushort numIds = reader.ReadUInt16(); int total = numNamed + numIds; if (!reader.CanRead((uint)total * 8)) { InitializeDefault(); return; } dataInfos = new List(); dirInfos = new List(); uint offset = reader.Position; for (int i = 0; i < total; i++, offset += 8) { reader.Position = offset; uint nameOrId = reader.ReadUInt32(); uint dataOrDirectory = reader.ReadUInt32(); ResourceName name; if ((nameOrId & 0x80000000) != 0) name = new ResourceName(ReadString(ref reader, nameOrId & 0x7FFFFFFF) ?? string.Empty); else name = new ResourceName((int)nameOrId); if ((dataOrDirectory & 0x80000000) == 0) dataInfos.Add(new EntryInfo(name, dataOrDirectory)); else dirInfos.Add(new EntryInfo(name, dataOrDirectory & 0x7FFFFFFF)); } directories = new LazyList(dirInfos.Count, null, (ctx, i) => ReadResourceDirectory(i)); data = new LazyList(dataInfos.Count, null, (ctx, i) => ReadResourceData(i)); } /// /// Reads a string /// /// Reader /// Offset of string /// The string or null if we could not read it static string ReadString(ref DataReader reader, uint offset) { reader.Position = offset; if (!reader.CanRead(2U)) return null; int size = reader.ReadUInt16(); int sizeInBytes = size * 2; if (!reader.CanRead((uint)sizeInBytes)) return null; try { return reader.ReadUtf16String(sizeInBytes / 2); } catch { return null; } } ResourceDirectory ReadResourceDirectory(int i) { var info = dirInfos[i]; var reader = resources.GetResourceReader(); reader.Position = Math.Min(reader.Length, info.offset); return new ResourceDirectoryPE(depth + 1, info.name, resources, ref reader); } ResourceData ReadResourceData(int i) { var info = dataInfos[i]; var reader = resources.GetResourceReader(); reader.Position = Math.Min(reader.Length, info.offset); ResourceData data; if (reader.CanRead(16U)) { var rva = (RVA)reader.ReadUInt32(); uint size = reader.ReadUInt32(); uint codePage = reader.ReadUInt32(); uint reserved = reader.ReadUInt32(); resources.GetDataReaderInfo(rva, size, out var dataReaderFactory, out uint dataOffset, out uint dataLength); data = new ResourceData(info.name, dataReaderFactory, dataOffset, dataLength, codePage, reserved); } else data = new ResourceData(info.name); return data; } void InitializeDefault() { directories = new LazyList(); data = new LazyList(); } } }