diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index 22977662f..f361b7e61 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -612,6 +612,7 @@
+
diff --git a/ICSharpCode.Decompiler/Util/Win32Resources.cs b/ICSharpCode.Decompiler/Util/Win32Resources.cs
new file mode 100644
index 000000000..9cbc99951
--- /dev/null
+++ b/ICSharpCode.Decompiler/Util/Win32Resources.cs
@@ -0,0 +1,283 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Reflection.PortableExecutable;
+
+namespace ICSharpCode.Decompiler.Util
+{
+ ///
+ /// Represents win32 resources
+ ///
+ public static class Win32Resources
+ {
+ ///
+ /// Reads win32 resource root directory
+ ///
+ ///
+ ///
+ public static unsafe Win32ResourceDirectory ReadWin32Resources(this PEReader pe)
+ {
+ if (pe == null)
+ {
+ throw new ArgumentNullException(nameof(pe));
+ }
+
+ int rva = pe.PEHeaders.PEHeader.ResourceTableDirectory.RelativeVirtualAddress;
+ if (rva == 0)
+ return null;
+ byte* pRoot = pe.GetSectionData(rva).Pointer;
+ return new Win32ResourceDirectory(pe, pRoot, 0, new Win32ResourceName("Root"));
+ }
+
+ public static Win32ResourceDirectory Find(this Win32ResourceDirectory root, Win32ResourceName type)
+ {
+ if (root is null)
+ throw new ArgumentNullException(nameof(root));
+ if (!root.Name.HasName || root.Name.Name != "Root")
+ throw new ArgumentOutOfRangeException(nameof(root));
+ if (type is null)
+ throw new ArgumentNullException(nameof(type));
+
+ return root.FindDirectory(type);
+ }
+
+ public static Win32ResourceDirectory Find(this Win32ResourceDirectory root, Win32ResourceName type, Win32ResourceName name)
+ {
+ if (root is null)
+ throw new ArgumentNullException(nameof(root));
+ if (!root.Name.HasName || root.Name.Name != "Root")
+ throw new ArgumentOutOfRangeException(nameof(root));
+ if (type is null)
+ throw new ArgumentNullException(nameof(type));
+ if (name is null)
+ throw new ArgumentNullException(nameof(name));
+
+ return root.FindDirectory(type)?.FindDirectory(name);
+ }
+
+ public static Win32ResourceData Find(this Win32ResourceDirectory root, Win32ResourceName type, Win32ResourceName name, Win32ResourceName langId)
+ {
+ if (root is null)
+ throw new ArgumentNullException(nameof(root));
+ if (!root.Name.HasName || root.Name.Name != "Root")
+ throw new ArgumentOutOfRangeException(nameof(root));
+ if (type is null)
+ throw new ArgumentNullException(nameof(type));
+ if (name is null)
+ throw new ArgumentNullException(nameof(name));
+ if (langId is null)
+ throw new ArgumentNullException(nameof(langId));
+
+ return root.FindDirectory(type)?.FindDirectory(name)?.FindData(langId);
+ }
+ }
+
+ [DebuggerDisplay("Directory: {Name}")]
+ public sealed class Win32ResourceDirectory
+ {
+ #region Structure
+ public uint Characteristics { get; }
+ public uint TimeDateStamp { get; }
+ public ushort MajorVersion { get; }
+ public ushort MinorVersion { get; }
+ public ushort NumberOfNamedEntries { get; }
+ public ushort NumberOfIdEntries { get; }
+ #endregion
+
+ public Win32ResourceName Name { get; }
+
+ public IList Directories { get; }
+
+ public IList Datas { get; }
+
+ internal unsafe Win32ResourceDirectory(PEReader pe, byte* pRoot, int offset, Win32ResourceName name)
+ {
+ var p = (IMAGE_RESOURCE_DIRECTORY*)(pRoot + offset);
+ Characteristics = p->Characteristics;
+ TimeDateStamp = p->TimeDateStamp;
+ MajorVersion = p->MajorVersion;
+ MinorVersion = p->MinorVersion;
+ NumberOfNamedEntries = p->NumberOfNamedEntries;
+ NumberOfIdEntries = p->NumberOfIdEntries;
+
+ Name = name;
+ Directories = new List();
+ Datas = new List();
+ var pEntries = (IMAGE_RESOURCE_DIRECTORY_ENTRY*)(p + 1);
+ int total = NumberOfNamedEntries + NumberOfIdEntries;
+ for (int i = 0; i < total; i++)
+ {
+ var pEntry = pEntries + i;
+ name = new Win32ResourceName(pRoot, pEntry);
+ if ((pEntry->OffsetToData & 0x80000000) == 0)
+ Datas.Add(new Win32ResourceData(pe, pRoot, (int)pEntry->OffsetToData, name));
+ else
+ Directories.Add(new Win32ResourceDirectory(pe, pRoot, (int)(pEntry->OffsetToData & 0x7FFFFFFF), name));
+ }
+ }
+
+ static unsafe string ReadString(byte* pRoot, int offset)
+ {
+ var pString = (IMAGE_RESOURCE_DIRECTORY_STRING*)(pRoot + offset);
+ return new string(pString->NameString, 0, pString->Length);
+ }
+
+ public Win32ResourceDirectory FindDirectory(Win32ResourceName name)
+ {
+ foreach (var directory in Directories)
+ {
+ if (directory.Name == name)
+ return directory;
+ }
+ return null;
+ }
+
+ public Win32ResourceData FindData(Win32ResourceName name)
+ {
+ foreach (var data in Datas)
+ {
+ if (data.Name == name)
+ return data;
+ }
+ return null;
+ }
+ }
+
+ [DebuggerDisplay("Data: {Name}")]
+ public sealed unsafe class Win32ResourceData
+ {
+ #region Structure
+ public uint OffsetToData { get; }
+ public uint Size { get; }
+ public uint CodePage { get; }
+ public uint Reserved { get; }
+ #endregion
+
+ private readonly void* _pointer;
+
+ public Win32ResourceName Name { get; }
+
+ public byte[] Data {
+ get {
+ byte[] data = new byte[Size];
+ fixed (void* pData = data)
+ Buffer.MemoryCopy(_pointer, pData, Size, Size);
+ return data;
+ }
+ }
+
+ internal Win32ResourceData(PEReader pe, byte* pRoot, int offset, Win32ResourceName name)
+ {
+ var p = (IMAGE_RESOURCE_DATA_ENTRY*)(pRoot + offset);
+ OffsetToData = p->OffsetToData;
+ Size = p->Size;
+ CodePage = p->CodePage;
+ Reserved = p->Reserved;
+
+ _pointer = pe.GetSectionData((int)OffsetToData).Pointer;
+ Name = name;
+ }
+ }
+
+ public sealed class Win32ResourceName
+ {
+ private readonly object _name;
+
+ public bool HasName => _name is string;
+
+ public bool HasId => _name is ushort;
+
+ public string Name => (string)_name;
+
+ public ushort Id => (ushort)_name;
+
+ public Win32ResourceName(string name)
+ {
+ _name = name ?? throw new ArgumentNullException(nameof(name));
+ }
+
+ public Win32ResourceName(int id) : this(checked((ushort)id))
+ {
+ }
+
+ public Win32ResourceName(ushort id)
+ {
+ _name = id;
+ }
+
+ internal unsafe Win32ResourceName(byte* pRoot, IMAGE_RESOURCE_DIRECTORY_ENTRY* pEntry)
+ {
+ _name = (pEntry->Name & 0x80000000) == 0 ? (object)(ushort)pEntry->Name : ReadString(pRoot, (int)(pEntry->Name & 0x7FFFFFFF));
+
+ static string ReadString(byte* pRoot, int offset)
+ {
+ var pString = (IMAGE_RESOURCE_DIRECTORY_STRING*)(pRoot + offset);
+ return new string(pString->NameString, 0, pString->Length);
+ }
+ }
+
+ public static bool operator ==(Win32ResourceName x, Win32ResourceName y)
+ {
+ if (x.HasName)
+ {
+ return y.HasName ? string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase) == 0 : false;
+ }
+ else
+ {
+ return y.HasId ? x.Id == y.Id : false;
+ }
+ }
+
+ public static bool operator !=(Win32ResourceName x, Win32ResourceName y)
+ {
+ return !(x == y);
+ }
+
+ public override int GetHashCode()
+ {
+ return _name.GetHashCode();
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (!(obj is Win32ResourceName name))
+ return false;
+ return this == name;
+ }
+
+ public override string ToString()
+ {
+ return HasName ? $"Name: {Name}" : $"Id: {Id}";
+ }
+ }
+
+ internal struct IMAGE_RESOURCE_DIRECTORY
+ {
+ public uint Characteristics;
+ public uint TimeDateStamp;
+ public ushort MajorVersion;
+ public ushort MinorVersion;
+ public ushort NumberOfNamedEntries;
+ public ushort NumberOfIdEntries;
+ }
+
+ internal struct IMAGE_RESOURCE_DIRECTORY_ENTRY
+ {
+ public uint Name;
+ public uint OffsetToData;
+ }
+
+ internal unsafe struct IMAGE_RESOURCE_DIRECTORY_STRING
+ {
+ public ushort Length;
+ public fixed char NameString[1];
+ }
+
+ internal struct IMAGE_RESOURCE_DATA_ENTRY
+ {
+ public uint OffsetToData;
+ public uint Size;
+ public uint CodePage;
+ public uint Reserved;
+ }
+}