diff --git a/BurnOutSharp.Builder/Extensions.cs b/BurnOutSharp.Builder/Extensions.cs
index 97fd823c..3c41afbc 100644
--- a/BurnOutSharp.Builder/Extensions.cs
+++ b/BurnOutSharp.Builder/Extensions.cs
@@ -330,15 +330,16 @@ namespace BurnOutSharp.Builder
#endregion
+ // TODO: Write extension to parse resource data
#region Portable Executable
///
- /// Convert a virtual address to a physical one
+ /// Convert a relative virtual address to a physical one
///
- /// Virtual address to convert
+ /// Relative virtual address to convert
/// Array of sections to check against
/// Physical address, 0 on error
- public static uint ConvertVirtualAddress(this uint virtualAddress, Models.PortableExecutable.SectionHeader[] sections)
+ public static uint ConvertVirtualAddress(this uint rva, Models.PortableExecutable.SectionHeader[] sections)
{
// Loop through all of the sections
for (int i = 0; i < sections.Length; i++)
@@ -353,8 +354,8 @@ namespace BurnOutSharp.Builder
// Attempt to derive the physical address from the current section
var section = sections[i];
- if (virtualAddress >= section.VirtualAddress && virtualAddress <= section.VirtualAddress + section.VirtualSize)
- return section.PointerToRawData + virtualAddress - section.VirtualAddress;
+ if (rva >= section.VirtualAddress && rva <= section.VirtualAddress + section.VirtualSize)
+ return rva - section.VirtualAddress + section.PointerToRawData;
}
return 0;
diff --git a/BurnOutSharp.Builder/PortableExecutable.cs b/BurnOutSharp.Builder/PortableExecutable.cs
index cabadba5..37ccf6e6 100644
--- a/BurnOutSharp.Builder/PortableExecutable.cs
+++ b/BurnOutSharp.Builder/PortableExecutable.cs
@@ -114,6 +114,33 @@ namespace BurnOutSharp.Builder
#endregion
+ // TODO: COFFStringTable (Only if COFFSymbolTable?)
+ // TODO: AttributeCertificateTable
+ // TODO: DelayLoadDirectoryTable
+
+ // TODO: Port to byte once done
+ #region Resource Directory Table
+
+ // Should also be in the '.rsrc' section
+ if (optionalHeader.ResourceTable != null && optionalHeader.ResourceTable.VirtualAddress != 0)
+ {
+ // If the offset for the resource directory table doesn't exist
+ int tableAddress = initialOffset
+ + (int)optionalHeader.ResourceTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable);
+ if (tableAddress >= data.Length)
+ return executable;
+
+ // Try to parse the resource directory table
+ var resourceDirectoryTable = ParseResourceDirectoryTable(data, tableAddress, tableAddress, executable.SectionTable);
+ if (resourceDirectoryTable == null)
+ return null;
+
+ // Set the resource directory table
+ executable.ResourceDirectoryTable = resourceDirectoryTable;
+ }
+
+ #endregion
+
// TODO: Finish implementing PE parsing
return executable;
}
@@ -507,6 +534,146 @@ namespace BurnOutSharp.Builder
return coffSymbolTable;
}
+ ///
+ /// Parse a byte array into a resource directory table
+ ///
+ /// Byte array to parse
+ /// Offset into the byte array
+ /// Initial offset to use in address comparisons
+ /// Section table to use for virtual address translation
+ /// Filled resource directory table on success, null on error
+ private static ResourceDirectoryTable ParseResourceDirectoryTable(byte[] data, int offset, long initialOffset, SectionHeader[] sections)
+ {
+ // TODO: Use marshalling here instead of building
+ var resourceDirectoryTable = new ResourceDirectoryTable();
+
+ resourceDirectoryTable.Characteristics = data.ReadUInt32(ref offset);
+ resourceDirectoryTable.TimeDateStamp = data.ReadUInt32(ref offset);
+ resourceDirectoryTable.MajorVersion = data.ReadUInt16(ref offset);
+ resourceDirectoryTable.MinorVersion = data.ReadUInt16(ref offset);
+ resourceDirectoryTable.NumberOfNameEntries = data.ReadUInt16(ref offset);
+ resourceDirectoryTable.NumberOfIDEntries = data.ReadUInt16(ref offset);
+
+ // Perform top-level pass of data
+ if (resourceDirectoryTable.NumberOfNameEntries > 0)
+ {
+ resourceDirectoryTable.NameEntries = new ResourceDirectoryEntry[resourceDirectoryTable.NumberOfNameEntries];
+ for (int i = 0; i < resourceDirectoryTable.NumberOfNameEntries; i++)
+ {
+ var entry = new ResourceDirectoryEntry();
+ entry.NameOffset = data.ReadUInt32(ref offset);
+
+ uint newOffset = data.ReadUInt32(ref offset);
+ if ((newOffset & 0xF0000000) != 0)
+ entry.SubdirectoryOffset = newOffset & ~0xF0000000;
+ else
+ entry.DataEntryOffset = newOffset;
+
+ // Read the name from the offset
+ int currentOffset = offset;
+ offset = (int)(entry.NameOffset + initialOffset);
+ var resourceDirectoryString = new ResourceDirectoryString();
+ resourceDirectoryString.Length = data.ReadUInt16(ref offset);
+ resourceDirectoryString.UnicodeString = data.ReadBytes(ref offset, resourceDirectoryString.Length);
+ entry.Name = resourceDirectoryString;
+ offset = currentOffset;
+
+ resourceDirectoryTable.NameEntries[i] = entry;
+ }
+ }
+ if (resourceDirectoryTable.NumberOfIDEntries > 0)
+ {
+ resourceDirectoryTable.IDEntries = new ResourceDirectoryEntry[resourceDirectoryTable.NumberOfIDEntries];
+ for (int i = 0; i < resourceDirectoryTable.NumberOfIDEntries; i++)
+ {
+ var entry = new ResourceDirectoryEntry();
+ entry.IntegerID = data.ReadUInt32(ref offset);
+
+ uint newOffset = data.ReadUInt32(ref offset);
+ if ((newOffset & 0xF0000000) != 0)
+ entry.SubdirectoryOffset = newOffset & ~0xF0000000;
+ else
+ entry.DataEntryOffset = newOffset;
+
+ resourceDirectoryTable.IDEntries[i] = entry;
+ }
+ }
+
+ // Read all leaves at this level
+ if (resourceDirectoryTable.NumberOfNameEntries > 0)
+ {
+ foreach (var entry in resourceDirectoryTable.NameEntries)
+ {
+ if (entry.SubdirectoryOffset != 0)
+ continue;
+
+ int newOffset = (int)(entry.DataEntryOffset + initialOffset);
+
+ var resourceDataEntry = new ResourceDataEntry();
+ resourceDataEntry.DataRVA = data.ReadUInt32(ref newOffset);
+ resourceDataEntry.Size = data.ReadUInt32(ref newOffset);
+ resourceDataEntry.Codepage = data.ReadUInt32(ref offset);
+ resourceDataEntry.Reserved = data.ReadUInt32(ref offset);
+
+ // Read the data from the offset
+ newOffset = (int)resourceDataEntry.DataRVA.ConvertVirtualAddress(sections);
+ if (newOffset > 0)
+ resourceDataEntry.Data = data.ReadBytes(ref newOffset, (int)resourceDataEntry.Size);
+
+ entry.DataEntry = resourceDataEntry;
+ }
+ }
+ if (resourceDirectoryTable.NumberOfIDEntries > 0)
+ {
+ foreach (var entry in resourceDirectoryTable.IDEntries)
+ {
+ if (entry.SubdirectoryOffset != 0)
+ continue;
+
+ int newOffset = (int)(entry.DataEntryOffset + initialOffset);
+
+ var resourceDataEntry = new ResourceDataEntry();
+ resourceDataEntry.DataRVA = data.ReadUInt32(ref newOffset);
+ resourceDataEntry.Size = data.ReadUInt32(ref newOffset);
+ resourceDataEntry.Codepage = data.ReadUInt32(ref newOffset);
+ resourceDataEntry.Reserved = data.ReadUInt32(ref newOffset);
+
+ // Read the data from the offset
+ newOffset = (int)resourceDataEntry.DataRVA.ConvertVirtualAddress(sections);
+ if (newOffset > 0)
+ resourceDataEntry.Data = data.ReadBytes(ref newOffset, (int)resourceDataEntry.Size);
+
+ entry.DataEntry = resourceDataEntry;
+ }
+ }
+
+ // Now go one level lower
+ if (resourceDirectoryTable.NumberOfNameEntries > 0)
+ {
+ foreach (var entry in resourceDirectoryTable.NameEntries)
+ {
+ if (entry.DataEntryOffset != 0)
+ continue;
+
+ int newOffset = (int)(entry.SubdirectoryOffset + initialOffset);
+ entry.Subdirectory = ParseResourceDirectoryTable(data, newOffset, initialOffset, sections);
+ }
+ }
+ if (resourceDirectoryTable.NumberOfIDEntries > 0)
+ {
+ foreach (var entry in resourceDirectoryTable.IDEntries)
+ {
+ if (entry.DataEntryOffset != 0)
+ continue;
+
+ int newOffset = (int)(entry.SubdirectoryOffset + initialOffset);
+ entry.Subdirectory = ParseResourceDirectoryTable(data, newOffset, initialOffset, sections);
+ }
+ }
+
+ return resourceDirectoryTable;
+ }
+
#endregion
#region Stream Data
@@ -616,6 +783,34 @@ namespace BurnOutSharp.Builder
#endregion
+ // TODO: COFFStringTable (Only if COFFSymbolTable?)
+ // TODO: AttributeCertificateTable
+ // TODO: DelayLoadDirectoryTable
+
+ // TODO: Port to byte once done
+ #region Resource Directory Table
+
+ // Should also be in the '.rsrc' section
+ if (optionalHeader.ResourceTable != null && optionalHeader.ResourceTable.VirtualAddress != 0)
+ {
+ // If the offset for the resource directory table doesn't exist
+ int tableAddress = initialOffset
+ + (int)optionalHeader.ResourceTable.VirtualAddress.ConvertVirtualAddress(executable.SectionTable);
+ if (tableAddress >= data.Length)
+ return executable;
+
+ // Try to parse the resource directory table
+ data.Seek(tableAddress, SeekOrigin.Begin);
+ var resourceDirectoryTable = ParseResourceDirectoryTable(data, data.Position, executable.SectionTable);
+ if (resourceDirectoryTable == null)
+ return null;
+
+ // Set the resource directory table
+ executable.ResourceDirectoryTable = resourceDirectoryTable;
+ }
+
+ #endregion
+
// TODO: Finish implementing PE parsing
return executable;
}
@@ -1005,6 +1200,156 @@ namespace BurnOutSharp.Builder
return coffSymbolTable;
}
+ ///
+ /// Parse a Stream into a resource directory table
+ ///
+ /// Stream to parse
+ /// Initial offset to use in address comparisons
+ /// Section table to use for virtual address translation
+ /// Filled resource directory table on success, null on error
+ private static ResourceDirectoryTable ParseResourceDirectoryTable(Stream data, long initialOffset, SectionHeader[] sections)
+ {
+ // TODO: Use marshalling here instead of building
+ var resourceDirectoryTable = new ResourceDirectoryTable();
+
+ resourceDirectoryTable.Characteristics = data.ReadUInt32();
+ resourceDirectoryTable.TimeDateStamp = data.ReadUInt32();
+ resourceDirectoryTable.MajorVersion = data.ReadUInt16();
+ resourceDirectoryTable.MinorVersion = data.ReadUInt16();
+ resourceDirectoryTable.NumberOfNameEntries = data.ReadUInt16();
+ resourceDirectoryTable.NumberOfIDEntries = data.ReadUInt16();
+
+ // Perform top-level pass of data
+ if (resourceDirectoryTable.NumberOfNameEntries > 0)
+ {
+ resourceDirectoryTable.NameEntries = new ResourceDirectoryEntry[resourceDirectoryTable.NumberOfNameEntries];
+ for (int i = 0; i < resourceDirectoryTable.NumberOfNameEntries; i++)
+ {
+ var entry = new ResourceDirectoryEntry();
+ entry.NameOffset = data.ReadUInt32();
+
+ uint offset = data.ReadUInt32();
+ if ((offset & 0xF0000000) != 0)
+ entry.SubdirectoryOffset = offset & ~0xF0000000;
+ else
+ entry.DataEntryOffset = offset;
+
+ // Read the name from the offset
+ long currentOffset = data.Position;
+ offset = entry.NameOffset + (uint)initialOffset;
+ data.Seek(offset, SeekOrigin.Begin);
+ var resourceDirectoryString = new ResourceDirectoryString();
+ resourceDirectoryString.Length = data.ReadUInt16();
+ resourceDirectoryString.UnicodeString = data.ReadBytes(resourceDirectoryString.Length);
+ entry.Name = resourceDirectoryString;
+ data.Seek(currentOffset, SeekOrigin.Begin);
+
+ resourceDirectoryTable.NameEntries[i] = entry;
+ }
+ }
+ if (resourceDirectoryTable.NumberOfIDEntries > 0)
+ {
+ resourceDirectoryTable.IDEntries = new ResourceDirectoryEntry[resourceDirectoryTable.NumberOfIDEntries];
+ for (int i = 0; i < resourceDirectoryTable.NumberOfIDEntries; i++)
+ {
+ var entry = new ResourceDirectoryEntry();
+ entry.IntegerID = data.ReadUInt32();
+
+ uint offset = data.ReadUInt32();
+ if ((offset & 0xF0000000) != 0)
+ entry.SubdirectoryOffset = offset & ~0xF0000000;
+ else
+ entry.DataEntryOffset = offset;
+
+ resourceDirectoryTable.IDEntries[i] = entry;
+ }
+ }
+
+ // Read all leaves at this level
+ if (resourceDirectoryTable.NumberOfNameEntries > 0)
+ {
+ foreach (var entry in resourceDirectoryTable.NameEntries)
+ {
+ if (entry.SubdirectoryOffset != 0)
+ continue;
+
+ uint offset = entry.DataEntryOffset + (uint)initialOffset;
+ data.Seek(offset, SeekOrigin.Begin);
+
+ var resourceDataEntry = new ResourceDataEntry();
+ resourceDataEntry.DataRVA = data.ReadUInt32();
+ resourceDataEntry.Size = data.ReadUInt32();
+ resourceDataEntry.Codepage = data.ReadUInt32();
+ resourceDataEntry.Reserved = data.ReadUInt32();
+
+ // Read the data from the offset
+ offset = resourceDataEntry.DataRVA.ConvertVirtualAddress(sections);
+ if (offset > 0)
+ {
+ data.Seek(offset, SeekOrigin.Begin);
+ resourceDataEntry.Data = data.ReadBytes((int)resourceDataEntry.Size);
+ }
+
+ entry.DataEntry = resourceDataEntry;
+ }
+ }
+ if (resourceDirectoryTable.NumberOfIDEntries > 0)
+ {
+ foreach (var entry in resourceDirectoryTable.IDEntries)
+ {
+ if (entry.SubdirectoryOffset != 0)
+ continue;
+
+ uint offset = entry.DataEntryOffset + (uint)initialOffset;
+ data.Seek(offset, SeekOrigin.Begin);
+
+ var resourceDataEntry = new ResourceDataEntry();
+ resourceDataEntry.DataRVA = data.ReadUInt32();
+ resourceDataEntry.Size = data.ReadUInt32();
+ resourceDataEntry.Codepage = data.ReadUInt32();
+ resourceDataEntry.Reserved = data.ReadUInt32();
+
+ // Read the data from the offset
+ offset = resourceDataEntry.DataRVA.ConvertVirtualAddress(sections);
+ if (offset > 0)
+ {
+ data.Seek(offset, SeekOrigin.Begin);
+ resourceDataEntry.Data = data.ReadBytes((int)resourceDataEntry.Size);
+ }
+
+ entry.DataEntry = resourceDataEntry;
+ }
+ }
+
+ // Now go one level lower
+ if (resourceDirectoryTable.NumberOfNameEntries > 0)
+ {
+ foreach (var entry in resourceDirectoryTable.NameEntries)
+ {
+ if (entry.DataEntryOffset != 0)
+ continue;
+
+ uint offset = entry.SubdirectoryOffset + (uint)initialOffset;
+ data.Seek(offset, SeekOrigin.Begin);
+ entry.Subdirectory = ParseResourceDirectoryTable(data, initialOffset, sections);
+ }
+ }
+ if (resourceDirectoryTable.NumberOfIDEntries > 0)
+ {
+ foreach (var entry in resourceDirectoryTable.IDEntries)
+ {
+ if (entry.DataEntryOffset != 0)
+ continue;
+
+ uint offset = entry.SubdirectoryOffset + (uint)initialOffset;
+ data.Seek(offset, SeekOrigin.Begin);
+ entry.Subdirectory = ParseResourceDirectoryTable(data, initialOffset, sections);
+ }
+ }
+
+ return resourceDirectoryTable;
+ }
+
#endregion
}
}
\ No newline at end of file
diff --git a/BurnOutSharp.Models/PortableExecutable/Enums.cs b/BurnOutSharp.Models/PortableExecutable/Enums.cs
index cfa81fd6..42717c3a 100644
--- a/BurnOutSharp.Models/PortableExecutable/Enums.cs
+++ b/BurnOutSharp.Models/PortableExecutable/Enums.cs
@@ -1693,6 +1693,30 @@ namespace BurnOutSharp.Models.PortableExecutable
#endregion
}
+ public enum ResourceType : uint
+ {
+ RT_NEWRESOURCE = 0x2000,
+ RT_ERROR = 0x7FFF,
+
+ RT_CURSOR = 1,
+ RT_BITMAP = 2,
+ RT_ICON = 3,
+ RT_MENU = 4,
+ RT_DIALOG = 5,
+ RT_STRING = 6,
+ RT_FONTDIR = 7,
+ RT_FONT = 8,
+ RT_ACCELERATORS = 9,
+ RT_RCDATA = 10,
+ RT_MESSAGETABLE = 11,
+ RT_GROUP_CURSOR = 12,
+ RT_GROUP_ICON = 14,
+ RT_VERSION = 16,
+ RT_NEWBITMAP = (RT_BITMAP | RT_NEWRESOURCE),
+ RT_NEWMENU = (RT_MENU | RT_NEWRESOURCE),
+ RT_NEWDIALOG = (RT_DIALOG | RT_NEWRESOURCE),
+ }
+
[Flags]
public enum SectionFlags : uint
{
diff --git a/BurnOutSharp.Models/PortableExecutable/Executable.cs b/BurnOutSharp.Models/PortableExecutable/Executable.cs
index be6d31a5..a7182fff 100644
--- a/BurnOutSharp.Models/PortableExecutable/Executable.cs
+++ b/BurnOutSharp.Models/PortableExecutable/Executable.cs
@@ -56,7 +56,14 @@ namespace BurnOutSharp.Models.PortableExecutable
///
public DelayLoadDirectoryTableEntry[] DelayLoadDirectoryTable { get; set; }
- // TODO: Left off at "The .cormeta Section (Object Only)"
+ #region Named Sections
+
+ ///
+ /// Resource directory table (.rsrc)
+ ///
+ public ResourceDirectoryTable ResourceDirectoryTable { get; set; }
+
+ #endregion
// TODO: Implement and/or document the following non-modeled parts:
// - Grouped Sections (Object Only)
diff --git a/BurnOutSharp.Models/PortableExecutable/ResourceDataEntry.cs b/BurnOutSharp.Models/PortableExecutable/ResourceDataEntry.cs
index 789b3c86..984a8420 100644
--- a/BurnOutSharp.Models/PortableExecutable/ResourceDataEntry.cs
+++ b/BurnOutSharp.Models/PortableExecutable/ResourceDataEntry.cs
@@ -24,6 +24,11 @@ namespace BurnOutSharp.Models.PortableExecutable
///
public uint Size;
+ ///
+ /// The resource data that is pointed to by the Data RVA field.
+ ///
+ public byte[] Data;
+
///
/// The code page that is used to decode code point values within the
/// resource data. Typically, the code page would be the Unicode code page.
diff --git a/BurnOutSharp.Models/PortableExecutable/ResourceDirectoryEntry.cs b/BurnOutSharp.Models/PortableExecutable/ResourceDirectoryEntry.cs
index 7d2e27c8..7ca0129c 100644
--- a/BurnOutSharp.Models/PortableExecutable/ResourceDirectoryEntry.cs
+++ b/BurnOutSharp.Models/PortableExecutable/ResourceDirectoryEntry.cs
@@ -1,6 +1,4 @@
-using System.Runtime.InteropServices;
-
-namespace BurnOutSharp.Models.PortableExecutable
+namespace BurnOutSharp.Models.PortableExecutable
{
///
/// A leaf's Type, Name, and Language IDs are determined by the path that is
@@ -19,29 +17,52 @@ namespace BurnOutSharp.Models.PortableExecutable
/// IMAGE_DIRECTORY_ENTRY_RESOURCE DataDirectory.
///
///
- [StructLayout(LayoutKind.Explicit)]
public class ResourceDirectoryEntry
{
+ #region Offset 0x00
+
///
/// The offset of a string that gives the Type, Name, or Language ID entry,
/// depending on level of table.
///
- [FieldOffset(0)] public uint NameOffset;
+ public uint NameOffset;
+
+ ///
+ /// A string that gives the Type, Name, or Language ID entry, depending on
+ /// level of table.
+ ///
+ public ResourceDirectoryString Name;
///
/// A 32-bit integer that identifies the Type, Name, or Language ID entry.
///
- [FieldOffset(0)] public uint IntegerID;
+ public uint IntegerID;
+
+ #endregion
+
+ #region Offset 0x04
///
/// High bit 0. Address of a Resource Data entry (a leaf).
///
- [FieldOffset(4)] public uint DataEntryOffset;
+ public uint DataEntryOffset;
+
+ ///
+ /// Resource data entry (a leaf).
+ ///
+ public ResourceDataEntry DataEntry;
///
/// High bit 1. The lower 31 bits are the address of another resource
/// directory table (the next level down).
///
- [FieldOffset(4)] public uint SubdirectoryOffset;
+ public uint SubdirectoryOffset;
+
+ ///
+ /// Another resource directory table (the next level down).
+ ///
+ public ResourceDirectoryTable Subdirectory;
+
+ #endregion
}
}
diff --git a/BurnOutSharp.Models/PortableExecutable/ResourceDirectoryTable.cs b/BurnOutSharp.Models/PortableExecutable/ResourceDirectoryTable.cs
index d1351636..5e412e5c 100644
--- a/BurnOutSharp.Models/PortableExecutable/ResourceDirectoryTable.cs
+++ b/BurnOutSharp.Models/PortableExecutable/ResourceDirectoryTable.cs
@@ -51,5 +51,18 @@ namespace BurnOutSharp.Models.PortableExecutable
/// use numeric IDs for Type, Name, or Language entries.
///
public ushort NumberOfIDEntries;
+
+ ///
+ /// Directory entries immediately following the table that use
+ /// strings to identify Type, Name, or Language entries (depending on the
+ /// level of the table).
+ ///
+ public ResourceDirectoryEntry[] NameEntries;
+
+ ///
+ /// Directory entries immediately following the Name entries that
+ /// use numeric IDs for Type, Name, or Language entries.
+ ///
+ public ResourceDirectoryEntry[] IDEntries;
}
}