Skip to content

Commit

Permalink
Merge pull request #220 from Washi1337/feature/win32-res-qol-improvem…
Browse files Browse the repository at this point in the history
…ents

Win32 Resources QoL Improvements
  • Loading branch information
Washi1337 authored Nov 24, 2021
2 parents cfd0863 + 6a940de commit 42d6727
Show file tree
Hide file tree
Showing 7 changed files with 453 additions and 71 deletions.
98 changes: 94 additions & 4 deletions docs/peimage/win32resources.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,102 @@
Win32 Resources
===============

Win32 resources are additional files embedded into the PE image, and are typically stored in the ``.rsrc`` section.
Win32 resources are additional files embedded into the PE image, and are typically stored in the ``.rsrc`` section. All classes relevant to Win32 resources can be found in the following namespace:

Resources are exposed by the ``IPEImage.Resources`` property. This is an instance of an ``IResourceDirectory``, which contains the ``Entries`` property. Entries in a directory can either be another sub directory containing more entries, or a data entry (an instance of ``IResourceData``) with the raw contents of the resource.
.. code-block:: csharp
using AsmResolver.PE.Win32Resources;
Directories
-----------

Resources are exposed by the ``IPEImage.Resources`` property, which represents the root directory of all resources stored in the image. Every directory (including the root directory) is represented by instances of ``IResourceDirectory``. This type contains the ``Entries`` property. Entries in a directory can either be another sub directory containing more entries, or a data entry (an instance of ``IResourceData``) with the raw contents of the resource.

.. code-block:: csharp
IPEImage image = ...
IResourceDirectory root = image.Resources;
foreach (var entry in root.Entries)
{
if (entry.IsDirectory)
Console.WriteLine("Directory {0}.", entry.Id);
else // if (entry.IsData)
Console.WriteLine("Data {0}.", entry.Id);
}
Alternatively, you can access specific resources very easily by using the ``GetDirectory`` and ``GetData``:

.. code-block:: csharp
IPEImage image = ...
IResourceData stringDataEntry = image.Resources
.GetDirectory(ResourceType.String) // Get string tables directory.
.GetDirectory(251) // Get string block with ID 251
.GetData(1033); // Get string with language ID 1033
Adding or replacing entries can be by either modifying the ``Entries`` property directly, or by using the ``AddOrReplace`` method. The latter is recommended as it ensures that an existing entry with the same ID is replaced with the new one.

.. code-block:: csharp
IPEImage image = ...
var newDirectory = new ResourceDirectory(ResourceType.String);
image.Resources.Entries.Add(newDirectory);
.. code-block:: csharp
IPEImage image = ...
var newDirectory = new ResourceDirectory(ResourceType.String);
image.Resources.AddOrReplaceEntry(newDirectory);
Similarly, removing can be done by modifying the ``Entries`` directory, or by using the ``RemoveEntry`` method:

.. code-block:: csharp
IPEImage image = ...
image.Resources.RemoveEntry(ResourceType.String);
Data Entries
------------

Data entries are represented using the ``IResourceData`` interface, and contain a property called ``Contents`` which is of type ``ISegment``. You can check if this is a ``IReadableSegment``, or use the shortcuts ``CanRead`` and ``CreateReader`` methods instead to read the raw contents of the entry.

.. code-block:: csharp
IPEImage image = ...
IResourceData dataEntry = image.Resources
.GetDirectory(ResourceType.String) // Get string tables directory.
.GetDirectory(251) // Get string block with ID 251
.GetData(1033); // Get string with language ID 1033
if (dataEntry.CanRead)
{
BinaryStreamReader reader = dataEntry.CreateReader();
// ...
}
Adding new data entries can be done by using the ``ResourceData`` constructor:

.. code-block:: csharp
IPEImage image = ...
Example
-------
var data = new ResourceData(id: 1033, contents: new DataSegment(new byte[] { ... }));
image.Resources
.GetDirectory(ResourceType.String)
.GetDirectory(251)
.AddOrReplaceEntry(data);
Example Traversal
-----------------

The following example is a program that dumps the resources tree from a single PE image.

Expand Down
33 changes: 7 additions & 26 deletions src/AsmResolver.PE.Win32Resources/Icon/IconResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ public class IconResource : IWin32Resource
/// <exception cref="ArgumentException">Occurs when the resource data is not readable.</exception>
public static IconResource? FromDirectory(IResourceDirectory rootDirectory)
{
var groupIconDirectory = (IResourceDirectory) rootDirectory.Entries[
ResourceDirectoryHelper.IndexOfResourceDirectoryType(rootDirectory, ResourceType.GroupIcon)];

var iconDirectory = (IResourceDirectory) rootDirectory.Entries[
ResourceDirectoryHelper.IndexOfResourceDirectoryType(rootDirectory, ResourceType.Icon)];
if (!rootDirectory.TryGetDirectory(ResourceType.GroupIcon, out var groupIconDirectory)
|| !rootDirectory.TryGetDirectory(ResourceType.Icon, out var iconDirectory))
{
return null;
}

var result = new IconResource();

Expand Down Expand Up @@ -83,25 +83,6 @@ public IconGroupDirectory this[uint id]
/// <inheritdoc />
public void WriteToDirectory(IResourceDirectory rootDirectory)
{
// Find and remove old group icon directory.
int groupIconIndex = ResourceDirectoryHelper.IndexOfResourceDirectoryType(rootDirectory, ResourceType.GroupIcon);

if (groupIconIndex == -1)
groupIconIndex = rootDirectory.Entries.Count;
else
rootDirectory.Entries.RemoveAt(groupIconIndex);

// Find and remove old icon directory.
int iconIndex = ResourceDirectoryHelper.IndexOfResourceDirectoryType(rootDirectory, ResourceType.Icon);

if (iconIndex == -1)
iconIndex = rootDirectory.Entries.Count;
else
rootDirectory.Entries.RemoveAt(iconIndex);

if (groupIconIndex == iconIndex)
iconIndex += 1;

// Construct new directory.
var newGroupIconDirectory = new ResourceDirectory(ResourceType.GroupIcon);
foreach (var entry in _entries)
Expand All @@ -122,8 +103,8 @@ public void WriteToDirectory(IResourceDirectory rootDirectory)
}

// Insert.
rootDirectory.Entries.Insert(groupIconIndex, newGroupIconDirectory);
rootDirectory.Entries.Insert(iconIndex, newIconDirectory);
rootDirectory.AddOrReplaceEntry(newGroupIconDirectory);
rootDirectory.AddOrReplaceEntry(newIconDirectory);
}
}
}
28 changes: 0 additions & 28 deletions src/AsmResolver.PE.Win32Resources/ResourceDirectoryHelper.cs

This file was deleted.

13 changes: 3 additions & 10 deletions src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ public class VersionInfoResource : VersionTableEntry, IWin32Resource
/// <returns>The version info resource, or <c>null</c> if none was found.</returns>
public static VersionInfoResource? FromDirectory(IResourceDirectory rootDirectory)
{
var versionDirectory = (IResourceDirectory) rootDirectory.Entries[ResourceDirectoryHelper.IndexOfResourceDirectoryType(rootDirectory, ResourceType.Version)];
if (!rootDirectory.TryGetDirectory(ResourceType.Version, out var versionDirectory))
return null;

var categoryDirectory = versionDirectory
.Entries
Expand Down Expand Up @@ -188,14 +189,6 @@ protected override void WriteValue(IBinaryStreamWriter writer)
/// <inheritdoc />
public void WriteToDirectory(IResourceDirectory rootDirectory)
{
// Find and remove old version directory.
int index = ResourceDirectoryHelper.IndexOfResourceDirectoryType(rootDirectory, ResourceType.Version);

if (index == -1)
index = rootDirectory.Entries.Count;
else
rootDirectory.Entries.RemoveAt(index);

// Construct new directory.
var newVersionDirectory = new ResourceDirectory(ResourceType.Version)
{
Expand All @@ -209,7 +202,7 @@ public void WriteToDirectory(IResourceDirectory rootDirectory)
};

// Insert.
rootDirectory.Entries.Insert(index, newVersionDirectory);
rootDirectory.AddOrReplaceEntry(newVersionDirectory);
}
}
}
99 changes: 96 additions & 3 deletions src/AsmResolver.PE/Win32Resources/IResourceDirectory.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace AsmResolver.PE.Win32Resources
{
/// <summary>
/// Represents a single directory containing Win32 resources of a PE image.
/// Represents a single directory containing Win32 resources of a PE image.
/// </summary>
public interface IResourceDirectory : IResourceEntry
{
Expand All @@ -19,7 +20,7 @@ ResourceType Type
/// Gets or sets the flags of the directory.
/// </summary>
/// <remarks>
/// This field is reserved and is usually set to zero.
/// This field is reserved and is usually set to zero.
/// </remarks>
uint Characteristics
{
Expand Down Expand Up @@ -61,5 +62,97 @@ IList<IResourceEntry> Entries
{
get;
}

/// <summary>
/// Looks up an entry in the directory by its unique identifier.
/// </summary>
/// <param name="id">The identifier of the entry to lookup.</param>
/// <returns>The entry.</returns>
/// <exception cref="KeyNotFoundException">
/// Occurs when no entry with the provided identifier was found.
/// </exception>
IResourceEntry GetEntry(uint id);

/// <summary>
/// Looks up an directory by its unique identifier.
/// </summary>
/// <param name="id">The identifier of the directory to lookup.</param>
/// <returns>The directory.</returns>
/// <exception cref="KeyNotFoundException">
/// Occurs when no directory with the provided identifier was found.
/// </exception>
IResourceDirectory GetDirectory(uint id);

/// <summary>
/// Looks up an directory by its resource type.
/// </summary>
/// <param name="type">The type of resources to lookup.</param>
/// <returns>The directory.</returns>
/// <exception cref="KeyNotFoundException">
/// Occurs when no directory with the provided identifier was found.
/// </exception>
IResourceDirectory GetDirectory(ResourceType type);

/// <summary>
/// Looks up a data entry in the directory by its unique identifier.
/// </summary>
/// <param name="id">The id of the data entry to lookup.</param>
/// <returns>The data entry.</returns>
/// <exception cref="KeyNotFoundException">
/// Occurs when no data entry with the provided identifier was found.
/// </exception>
IResourceData GetData(uint id);

/// <summary>
/// Attempts to looks up an entry in the directory by its unique identifier.
/// </summary>
/// <param name="id">The identifier of the entry to lookup.</param>
/// <param name="entry">The found entry, or <c>null</c> if none was found.</param>
/// <returns><c>true</c> if the entry was found, <c>false</c> otherwise.</returns>
bool TryGetEntry(uint id, [NotNullWhen(true)] out IResourceEntry? entry);

/// <summary>
/// Attempts to looks up a directory by its unique identifier.
/// </summary>
/// <param name="id">The identifier of the directory to lookup.</param>
/// <param name="directory">The found directory, or <c>null</c> if none was found.</param>
/// <returns><c>true</c> if the directory was found, <c>false</c> otherwise.</returns>
bool TryGetDirectory(uint id, [NotNullWhen(true)] out IResourceDirectory? directory);

/// <summary>
/// Attempts to looks up a directory by its resource type.
/// </summary>
/// <param name="type">The type of resources to lookup.</param>
/// <param name="directory">The found directory, or <c>null</c> if none was found.</param>
/// <returns><c>true</c> if the directory was found, <c>false</c> otherwise.</returns>
bool TryGetDirectory(ResourceType type, [NotNullWhen(true)] out IResourceDirectory? directory);

/// <summary>
/// Attempts to looks up a data entry in the directory by its unique identifier.
/// </summary>
/// <param name="id">The identifier of the data entry to lookup.</param>
/// <param name="data">The found data entry, or <c>null</c> if none was found.</param>
/// <returns><c>true</c> if the data entry was found, <c>false</c> otherwise.</returns>
bool TryGetData(uint id, [NotNullWhen(true)] out IResourceData? data);

/// <summary>
/// Replaces an existing entry with the same ID with the provided entry, or adds the new entry to the directory.
/// </summary>
/// <param name="entry">The entry to store in the directory.</param>
void AddOrReplaceEntry(IResourceEntry entry);

/// <summary>
/// Removes an entry in the directory by its unique identifier.
/// </summary>
/// <param name="id">The identifier of the entry to remove.</param>
/// <returns><c>true</c> if the data entry was found and removed, <c>false</c> otherwise.</returns>
bool RemoveEntry(uint id);

/// <summary>
/// Removes a directory in the directory by its resource type.
/// </summary>
/// <param name="type">The type of resources to remove.</param>
/// <returns><c>true</c> if the directory was found and removed, <c>false</c> otherwise.</returns>
bool RemoveEntry(ResourceType type);
}
}
}
Loading

0 comments on commit 42d6727

Please sign in to comment.