// 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();
}
}
}