obfuz/Plugins/dnlib/W32Resources/ResourceDirectory.cs

275 lines
7.9 KiB
C#

// 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 {
/// <summary>
/// A Win32 resource directory (see IMAGE_RESOURCE_DIRECTORY in the Windows SDK)
/// </summary>
public abstract class ResourceDirectory : ResourceDirectoryEntry {
/// <summary>See <see cref="Characteristics"/></summary>
protected uint characteristics;
/// <summary>See <see cref="TimeDateStamp"/></summary>
protected uint timeDateStamp;
/// <summary>See <see cref="MajorVersion"/></summary>
protected ushort majorVersion;
/// <summary>See <see cref="MinorVersion"/></summary>
protected ushort minorVersion;
/// <summary>See <see cref="Directories"/></summary>
private protected LazyList<ResourceDirectory> directories;
/// <summary>See <see cref="Data"/></summary>
private protected LazyList<ResourceData> data;
/// <summary>
/// Gets/sets the characteristics
/// </summary>
public uint Characteristics {
get => characteristics;
set => characteristics = value;
}
/// <summary>
/// Gets/sets the time date stamp
/// </summary>
public uint TimeDateStamp {
get => timeDateStamp;
set => timeDateStamp = value;
}
/// <summary>
/// Gets/sets the major version number
/// </summary>
public ushort MajorVersion {
get => majorVersion;
set => majorVersion = value;
}
/// <summary>
/// Gets/sets the minor version number
/// </summary>
public ushort MinorVersion {
get => minorVersion;
set => minorVersion = value;
}
/// <summary>
/// Gets all directory entries
/// </summary>
public IList<ResourceDirectory> Directories => directories;
/// <summary>
/// Gets all resource data
/// </summary>
public IList<ResourceData> Data => data;
/// <summary>
/// Constructor
/// </summary>
/// <param name="name">Name</param>
protected ResourceDirectory(ResourceName name)
: base(name) {
}
/// <summary>
/// Finds a <see cref="ResourceDirectory"/> by name
/// </summary>
/// <param name="name">Name</param>
/// <returns>A <see cref="ResourceDirectory"/> or <c>null</c> if it wasn't found</returns>
public ResourceDirectory FindDirectory(ResourceName name) {
foreach (var dir in directories) {
if (dir.Name == name)
return dir;
}
return null;
}
/// <summary>
/// Finds a <see cref="ResourceData"/> by name
/// </summary>
/// <param name="name">Name</param>
/// <returns>A <see cref="ResourceData"/> or <c>null</c> if it wasn't found</returns>
public ResourceData FindData(ResourceName name) {
foreach (var d in data) {
if (d.Name == name)
return d;
}
return null;
}
}
/// <summary>
/// A Win32 resource directory created by the user
/// </summary>
public class ResourceDirectoryUser : ResourceDirectory {
/// <summary>
/// Constructor
/// </summary>
/// <param name="name">Name</param>
public ResourceDirectoryUser(ResourceName name)
: base(name) {
directories = new LazyList<ResourceDirectory>();
data = new LazyList<ResourceData>();
}
}
/// <summary>
/// A Win32 resource directory created from a PE file
/// </summary>
public sealed class ResourceDirectoryPE : ResourceDirectory {
/// <summary>
/// To make sure we don't get stuck in an infinite loop, don't allow more than this
/// many sub directories.
/// </summary>
const uint MAX_DIR_DEPTH = 10;
/// <summary>Owner</summary>
readonly Win32ResourcesPE resources;
/// <summary>Directory depth. When creating more <see cref="ResourceDirectoryPE"/>'s,
/// the instances get this value + 1</summary>
uint depth;
/// <summary>
/// Info about all <see cref="ResourceData"/>'s we haven't created yet
/// </summary>
List<EntryInfo> dataInfos;
/// <summary>
/// Info about all <see cref="ResourceDirectory"/>'s we haven't created yet
/// </summary>
List<EntryInfo> dirInfos;
readonly struct EntryInfo {
public readonly ResourceName name;
/// <summary>Offset of resource directory / data</summary>
public readonly uint offset;
public EntryInfo(ResourceName name, uint offset) {
this.name = name;
this.offset = offset;
}
public override string ToString() => $"{offset:X8} {name}";
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="depth">Starts from 0. If it's big enough, we'll stop reading more data.</param>
/// <param name="name">Name</param>
/// <param name="resources">Resources</param>
/// <param name="reader">Reader positioned at the start of this resource directory</param>
public ResourceDirectoryPE(uint depth, ResourceName name, Win32ResourcesPE resources, ref DataReader reader)
: base(name) {
this.resources = resources;
this.depth = depth;
Initialize(ref reader);
}
/// <summary>
/// Reads the directory header and initializes <see cref="ResourceDirectory.directories"/> and
/// <see cref="ResourceDirectory.data"/>.
/// </summary>
/// <param name="reader"></param>
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<EntryInfo>();
dirInfos = new List<EntryInfo>();
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<ResourceDirectory, object>(dirInfos.Count, null, (ctx, i) => ReadResourceDirectory(i));
data = new LazyList<ResourceData, object>(dataInfos.Count, null, (ctx, i) => ReadResourceData(i));
}
/// <summary>
/// Reads a string
/// </summary>
/// <param name="reader">Reader</param>
/// <param name="offset">Offset of string</param>
/// <returns>The string or <c>null</c> if we could not read it</returns>
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<ResourceDirectory>();
data = new LazyList<ResourceData>();
}
}
}