using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml.Serialization;
using SabreTools.Data.Models.COFF;
using SabreTools.Data.Models.PortableExecutable;
using SabreTools.Data.Models.PortableExecutable.Resource.Entries;
using SabreTools.Data.Models.SecuROM;
using SabreTools.IO.Extensions;
namespace SabreTools.Data.Extensions
{
public static class PortableExecutable
{
///
/// Convert a relative virtual address to a physical one
///
/// Relative virtual address to convert
/// Array of sections to check against
/// Physical address, 0 on error
public static uint ConvertVirtualAddress(this uint rva, SectionHeader[]? sections)
{
// If we have an invalid section table, we can't do anything
if (sections == null || sections.Length == 0)
return 0;
// If the RVA is 0, we just return 0 because it's invalid
if (rva == 0)
return 0;
// If the RVA matches a section start exactly, use that
var matchingSection = Array.Find(sections, s => s.VirtualAddress == rva);
if (matchingSection != null)
return rva - matchingSection.VirtualAddress + matchingSection.PointerToRawData;
// Loop through all of the sections
uint maxVirtualAddress = 0, maxRawPointer = 0;
for (int i = 0; i < sections.Length; i++)
{
// If the section "starts" at 0, just skip it
var section = sections[i];
if (section.PointerToRawData == 0)
continue;
// If the virtual address is greater than the RVA
if (rva < section.VirtualAddress)
continue;
// Cache the maximum matching section data, in case of a miss
if (rva >= section.VirtualAddress)
{
maxVirtualAddress = section.VirtualAddress;
maxRawPointer = section.PointerToRawData;
}
// Attempt to derive the physical address from the current section
if (section.VirtualSize != 0 && rva <= section.VirtualAddress + section.VirtualSize)
return rva - section.VirtualAddress + section.PointerToRawData;
else if (section.SizeOfRawData != 0 && rva <= section.VirtualAddress + section.SizeOfRawData)
return rva - section.VirtualAddress + section.PointerToRawData;
}
return maxRawPointer != 0 ? rva - maxVirtualAddress + maxRawPointer : 0;
}
///
/// Find the section a revlative virtual address lives in
///
/// Relative virtual address to convert
/// Array of sections to check against
/// Section index, null on error
public static int ContainingSectionIndex(this uint rva, SectionHeader[]? sections)
{
// If we have an invalid section table, we can't do anything
if (sections == null || sections.Length == 0)
return -1;
// If the RVA is 0, we just return -1 because it's invalid
if (rva == 0)
return -1;
// Loop through all of the sections
for (int i = 0; i < sections.Length; i++)
{
// If the section "starts" at 0, just skip it
var section = sections[i];
if (section.PointerToRawData == 0)
continue;
// If the virtual address is greater than the RVA
if (rva < section.VirtualAddress)
continue;
// Attempt to derive the physical address from the current section
if (section.VirtualSize != 0 && rva <= section.VirtualAddress + section.VirtualSize)
return i;
else if (section.SizeOfRawData != 0 && rva <= section.VirtualAddress + section.SizeOfRawData)
return i;
}
return -1;
}
#region Debug
///
/// Parse a byte array into a NB10ProgramDatabase
///
/// Data to parse
/// Offset into the byte array
/// A filled NB10ProgramDatabase on success, null on error
public static Data.Models.PortableExecutable.DebugData.NB10ProgramDatabase? ParseNB10ProgramDatabase(this byte[] data, ref int offset)
{
var obj = new Data.Models.PortableExecutable.DebugData.NB10ProgramDatabase();
obj.Signature = data.ReadUInt32LittleEndian(ref offset);
if (obj.Signature != 0x3031424E)
return null;
obj.Offset = data.ReadUInt32LittleEndian(ref offset);
obj.Timestamp = data.ReadUInt32LittleEndian(ref offset);
obj.Age = data.ReadUInt32LittleEndian(ref offset);
obj.PdbFileName = data.ReadNullTerminatedAnsiString(ref offset);
return obj;
}
///
/// Parse a byte array into a RSDSProgramDatabase
///
/// Data to parse
/// Offset into the byte array
/// A filled RSDSProgramDatabase on success, null on error
public static Data.Models.PortableExecutable.DebugData.RSDSProgramDatabase? ParseRSDSProgramDatabase(this byte[] data, ref int offset)
{
var obj = new Data.Models.PortableExecutable.DebugData.RSDSProgramDatabase();
obj.Signature = data.ReadUInt32LittleEndian(ref offset);
if (obj.Signature != 0x53445352)
return null;
obj.GUID = data.ReadGuid(ref offset);
obj.Age = data.ReadUInt32LittleEndian(ref offset);
obj.PathAndFileName = data.ReadNullTerminatedUTF8String(ref offset);
return obj;
}
#endregion
// TODO: Implement other resource types from https://learn.microsoft.com/en-us/windows/win32/menurc/resource-file-formats
#region Resources
///
/// Read resource data as an accelerator table resource
///
/// Resource data entry to parse into an accelerator table resource
/// A filled accelerator table resource on success, null on error
public static AcceleratorTableEntry[]? AsAcceleratorTableResource(this Data.Models.PortableExecutable.Resource.DataEntry? entry)
{
// If we have data that's invalid for this resource type, we can't do anything
if (entry?.Data == null || entry.Data.Length % 8 != 0)
return null;
// Get the number of entries
int count = entry.Data.Length / 8;
// Initialize the iterator
int offset = 0;
// Create the output object
var table = new AcceleratorTableEntry[count];
// Read in the table
for (int i = 0; i < count; i++)
{
table[i] = ParseAcceleratorTableEntry(entry.Data, ref offset);
}
return table;
}
///
/// Read resource data as a side-by-side assembly manifest
///
/// Resource data entry to parse into a side-by-side assembly manifest
/// A filled side-by-side assembly manifest on success, null on error
public static AssemblyManifest? AsAssemblyManifest(this Data.Models.PortableExecutable.Resource.DataEntry? entry)
{
// If we have an invalid entry, just skip
if (entry?.Data == null)
return null;
try
{
var serializer = new XmlSerializer(typeof(AssemblyManifest));
return serializer.Deserialize(new MemoryStream(entry.Data)) as AssemblyManifest;
}
catch
{
return null;
}
}
///
/// Read resource data as a dialog box
///
/// Resource data entry to parse into a dialog box
/// A filled dialog box on success, null on error
public static DialogBoxResource? AsDialogBox(this Data.Models.PortableExecutable.Resource.DataEntry? entry)
{
// If we have an invalid entry, just skip
if (entry?.Data == null)
return null;
// Initialize the iterator
int offset = 0;
// Create the output object
var dialogBoxResource = new DialogBoxResource();
// Try to read the signature for an extended dialog box template
int signatureOffset = sizeof(ushort);
int possibleSignature = entry.Data.ReadUInt16LittleEndian(ref signatureOffset);
if (possibleSignature == 0xFFFF)
{
#region Extended dialog template
var dialogTemplateExtended = new DialogTemplateExtended();
dialogTemplateExtended.Version = entry.Data.ReadUInt16LittleEndian(ref offset);
dialogTemplateExtended.Signature = entry.Data.ReadUInt16LittleEndian(ref offset);
dialogTemplateExtended.HelpID = entry.Data.ReadUInt32LittleEndian(ref offset);
dialogTemplateExtended.ExtendedStyle = (ExtendedWindowStyles)entry.Data.ReadUInt32LittleEndian(ref offset);
dialogTemplateExtended.Style = (WindowStyles)entry.Data.ReadUInt32LittleEndian(ref offset);
dialogTemplateExtended.DialogItems = entry.Data.ReadUInt16LittleEndian(ref offset);
dialogTemplateExtended.PositionX = entry.Data.ReadInt16LittleEndian(ref offset);
dialogTemplateExtended.PositionY = entry.Data.ReadInt16LittleEndian(ref offset);
dialogTemplateExtended.WidthX = entry.Data.ReadInt16LittleEndian(ref offset);
dialogTemplateExtended.HeightY = entry.Data.ReadInt16LittleEndian(ref offset);
#region Menu resource
int currentOffset = offset;
ushort menuResourceIdentifier = entry.Data.ReadUInt16LittleEndian(ref offset);
offset = currentOffset;
// 0x0000 means no elements
if (menuResourceIdentifier == 0x0000)
{
// Increment the pointer if it was empty
offset += sizeof(ushort);
}
else
{
// Flag if there's an ordinal at the end
bool menuResourceHasOrdinal = menuResourceIdentifier == 0xFFFF;
if (menuResourceHasOrdinal)
offset += sizeof(ushort);
// Read the menu resource as a string
dialogTemplateExtended.MenuResource = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
// Align to the WORD boundary if we're not at the end
entry.Data.AlignToBoundary(ref offset, 2);
// Read the ordinal if we have the flag set
if (menuResourceHasOrdinal)
dialogTemplateExtended.MenuResourceOrdinal = entry.Data.ReadUInt16LittleEndian(ref offset);
}
#endregion
#region Class resource
currentOffset = offset;
ushort classResourceIdentifier = entry.Data.ReadUInt16LittleEndian(ref offset);
offset = currentOffset;
// 0x0000 means no elements
if (classResourceIdentifier == 0x0000)
{
// Increment the pointer if it was empty
offset += sizeof(ushort);
}
else
{
// Flag if there's an ordinal at the end
bool classResourcehasOrdinal = classResourceIdentifier == 0xFFFF;
if (classResourcehasOrdinal)
offset += sizeof(ushort);
// Read the class resource as a string
dialogTemplateExtended.ClassResource = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
// Align to the WORD boundary if we're not at the end
entry.Data.AlignToBoundary(ref offset, 2);
// Read the ordinal if we have the flag set
if (classResourcehasOrdinal)
dialogTemplateExtended.ClassResourceOrdinal = entry.Data.ReadUInt16LittleEndian(ref offset);
}
#endregion
#region Title resource
currentOffset = offset;
ushort titleResourceIdentifier = entry.Data.ReadUInt16LittleEndian(ref offset);
offset = currentOffset;
// 0x0000 means no elements
if (titleResourceIdentifier == 0x0000)
{
// Increment the pointer if it was empty
offset += sizeof(ushort);
}
else
{
// Read the title resource as a string
dialogTemplateExtended.TitleResource = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
// Align to the WORD boundary if we're not at the end
entry.Data.AlignToBoundary(ref offset, 2);
}
#endregion
#region Point size and typeface
// Only if DS_SETFONT is set are the values here used
#if NET20 || NET35
if ((dialogTemplateExtended.Style & WindowStyles.DS_SETFONT) != 0)
#else
if (dialogTemplateExtended.Style.HasFlag(WindowStyles.DS_SETFONT))
#endif
{
dialogTemplateExtended.PointSize = entry.Data.ReadUInt16LittleEndian(ref offset);
dialogTemplateExtended.Weight = entry.Data.ReadUInt16LittleEndian(ref offset);
dialogTemplateExtended.Italic = entry.Data.ReadByte(ref offset);
dialogTemplateExtended.CharSet = entry.Data.ReadByte(ref offset);
dialogTemplateExtended.Typeface = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
}
// Align to the DWORD boundary if we're not at the end
entry.Data.AlignToBoundary(ref offset, 4);
#endregion
dialogBoxResource.ExtendedDialogTemplate = dialogTemplateExtended;
#endregion
#region Extended dialog item templates
var dialogItemExtendedTemplates = new List();
for (int i = 0; i < dialogTemplateExtended.DialogItems; i++)
{
var dialogItemTemplate = new DialogItemTemplateExtended();
dialogItemTemplate.HelpID = entry.Data.ReadUInt32LittleEndian(ref offset);
dialogItemTemplate.ExtendedStyle = (ExtendedWindowStyles)entry.Data.ReadUInt32LittleEndian(ref offset);
dialogItemTemplate.Style = (WindowStyles)entry.Data.ReadUInt32LittleEndian(ref offset);
dialogItemTemplate.PositionX = entry.Data.ReadInt16LittleEndian(ref offset);
dialogItemTemplate.PositionY = entry.Data.ReadInt16LittleEndian(ref offset);
dialogItemTemplate.WidthX = entry.Data.ReadInt16LittleEndian(ref offset);
dialogItemTemplate.HeightY = entry.Data.ReadInt16LittleEndian(ref offset);
dialogItemTemplate.ID = entry.Data.ReadUInt32LittleEndian(ref offset);
#region Class resource
currentOffset = offset;
ushort itemClassResourceIdentifier = entry.Data.ReadUInt16LittleEndian(ref offset);
offset = currentOffset;
// 0xFFFF means ordinal only
if (itemClassResourceIdentifier == 0xFFFF)
{
// Increment the pointer
_ = entry.Data.ReadUInt16LittleEndian(ref offset);
// Read the ordinal
dialogItemTemplate.ClassResourceOrdinal = (DialogItemTemplateOrdinal)entry.Data.ReadUInt16LittleEndian(ref offset);
}
else
{
// Flag if there's an ordinal at the end
bool classResourcehasOrdinal = itemClassResourceIdentifier == 0xFFFF;
if (classResourcehasOrdinal)
offset += sizeof(ushort);
// Read the class resource as a string
dialogItemTemplate.ClassResource = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
// Align to the WORD boundary if we're not at the end
entry.Data.AlignToBoundary(ref offset, 2);
}
#endregion
#region Title resource
currentOffset = offset;
ushort itemTitleResourceIdentifier = entry.Data.ReadUInt16LittleEndian(ref offset);
offset = currentOffset;
// 0xFFFF means ordinal only
if (itemTitleResourceIdentifier == 0xFFFF)
{
// Increment the pointer
_ = entry.Data.ReadUInt16LittleEndian(ref offset);
// Read the ordinal
dialogItemTemplate.TitleResourceOrdinal = entry.Data.ReadUInt16LittleEndian(ref offset);
}
else
{
// Read the title resource as a string
dialogItemTemplate.TitleResource = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
// Align to the WORD boundary if we're not at the end
entry.Data.AlignToBoundary(ref offset, 2);
}
#endregion
#region Creation data
dialogItemTemplate.CreationDataSize = entry.Data.ReadUInt16LittleEndian(ref offset);
if (dialogItemTemplate.CreationDataSize != 0)
dialogItemTemplate.CreationData = entry.Data.ReadBytes(ref offset, dialogItemTemplate.CreationDataSize);
#endregion
// Align to the DWORD boundary if we're not at the end
entry.Data.AlignToBoundary(ref offset, 4);
dialogItemExtendedTemplates.Add(dialogItemTemplate);
// If we have an invalid item count
if (offset >= entry.Data.Length)
break;
}
dialogBoxResource.ExtendedDialogItemTemplates = [.. dialogItemExtendedTemplates];
#endregion
}
else
{
#region Dialog template
var dialogTemplate = new DialogTemplate();
dialogTemplate.Style = (WindowStyles)entry.Data.ReadUInt32LittleEndian(ref offset);
dialogTemplate.ExtendedStyle = (ExtendedWindowStyles)entry.Data.ReadUInt32LittleEndian(ref offset);
dialogTemplate.ItemCount = entry.Data.ReadUInt16LittleEndian(ref offset);
dialogTemplate.PositionX = entry.Data.ReadInt16LittleEndian(ref offset);
dialogTemplate.PositionY = entry.Data.ReadInt16LittleEndian(ref offset);
dialogTemplate.WidthX = entry.Data.ReadInt16LittleEndian(ref offset);
dialogTemplate.HeightY = entry.Data.ReadInt16LittleEndian(ref offset);
#region Menu resource
int currentOffset = offset;
ushort menuResourceIdentifier = entry.Data.ReadUInt16LittleEndian(ref offset);
offset = currentOffset;
// 0x0000 means no elements
if (menuResourceIdentifier == 0x0000)
{
// Increment the pointer if it was empty
offset += sizeof(ushort);
}
else
{
// Flag if there's an ordinal at the end
bool menuResourceHasOrdinal = menuResourceIdentifier == 0xFFFF;
if (menuResourceHasOrdinal)
offset += sizeof(ushort);
// Read the menu resource as a string
dialogTemplate.MenuResource = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
// Align to the WORD boundary if we're not at the end
entry.Data.AlignToBoundary(ref offset, 2);
// Read the ordinal if we have the flag set
if (menuResourceHasOrdinal)
dialogTemplate.MenuResourceOrdinal = entry.Data.ReadUInt16LittleEndian(ref offset);
}
#endregion
#region Class resource
currentOffset = offset;
ushort classResourceIdentifier;
if (offset >= entry.Data.Length)
classResourceIdentifier = 0x0000;
else
classResourceIdentifier = entry.Data.ReadUInt16LittleEndian(ref offset);
offset = currentOffset;
// 0x0000 means no elements
if (classResourceIdentifier == 0x0000)
{
// Increment the pointer if it was empty
offset += sizeof(ushort);
}
else
{
// Flag if there's an ordinal at the end
bool classResourcehasOrdinal = classResourceIdentifier == 0xFFFF;
if (classResourcehasOrdinal)
offset += sizeof(ushort);
// Read the class resource as a string
dialogTemplate.ClassResource = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
// Align to the WORD boundary if we're not at the end
entry.Data.AlignToBoundary(ref offset, 2);
// Read the ordinal if we have the flag set
if (classResourcehasOrdinal)
dialogTemplate.ClassResourceOrdinal = entry.Data.ReadUInt16LittleEndian(ref offset);
}
#endregion
#region Title resource
currentOffset = offset;
ushort titleResourceIdentifier;
if (offset >= entry.Data.Length)
titleResourceIdentifier = 0x0000;
else
titleResourceIdentifier = entry.Data.ReadUInt16LittleEndian(ref offset);
offset = currentOffset;
// 0x0000 means no elements
if (titleResourceIdentifier == 0x0000)
{
// Increment the pointer if it was empty
offset += sizeof(ushort);
}
else
{
// Read the title resource as a string
dialogTemplate.TitleResource = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
// Align to the WORD boundary if we're not at the end
entry.Data.AlignToBoundary(ref offset, 2);
}
#endregion
#region Point size and typeface
// Only if DS_SETFONT is set are the values here used
#if NET20 || NET35
if ((dialogTemplate.Style & WindowStyles.DS_SETFONT) != 0)
#else
if (dialogTemplate.Style.HasFlag(WindowStyles.DS_SETFONT))
#endif
{
dialogTemplate.PointSizeValue = entry.Data.ReadUInt16LittleEndian(ref offset);
// Read the font name as a string
dialogTemplate.Typeface = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
}
// Align to the DWORD boundary if we're not at the end
entry.Data.AlignToBoundary(ref offset, 4);
#endregion
dialogBoxResource.DialogTemplate = dialogTemplate;
#endregion
#region Dialog item templates
var dialogItemTemplates = new List();
for (int i = 0; i < dialogTemplate.ItemCount; i++)
{
var dialogItemTemplate = new DialogItemTemplate();
dialogItemTemplate.Style = (WindowStyles)entry.Data.ReadUInt32LittleEndian(ref offset);
dialogItemTemplate.ExtendedStyle = (ExtendedWindowStyles)entry.Data.ReadUInt32LittleEndian(ref offset);
dialogItemTemplate.PositionX = entry.Data.ReadInt16LittleEndian(ref offset);
dialogItemTemplate.PositionY = entry.Data.ReadInt16LittleEndian(ref offset);
dialogItemTemplate.WidthX = entry.Data.ReadInt16LittleEndian(ref offset);
dialogItemTemplate.HeightY = entry.Data.ReadInt16LittleEndian(ref offset);
dialogItemTemplate.ID = entry.Data.ReadUInt16LittleEndian(ref offset);
#region Class resource
currentOffset = offset;
ushort itemClassResourceIdentifier = entry.Data.ReadUInt16LittleEndian(ref offset);
offset = currentOffset;
// 0xFFFF means ordinal only
if (itemClassResourceIdentifier == 0xFFFF)
{
// Increment the pointer
_ = entry.Data.ReadUInt16LittleEndian(ref offset);
// Read the ordinal
dialogItemTemplate.ClassResourceOrdinal = (DialogItemTemplateOrdinal)entry.Data.ReadUInt16LittleEndian(ref offset);
}
else
{
// Flag if there's an ordinal at the end
bool classResourcehasOrdinal = itemClassResourceIdentifier == 0xFFFF;
if (classResourcehasOrdinal)
offset += sizeof(ushort);
// Read the class resource as a string
dialogItemTemplate.ClassResource = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
// Align to the WORD boundary if we're not at the end
entry.Data.AlignToBoundary(ref offset, 2);
}
#endregion
#region Title resource
currentOffset = offset;
ushort itemTitleResourceIdentifier = entry.Data.ReadUInt16LittleEndian(ref offset);
offset = currentOffset;
// 0xFFFF means ordinal only
if (itemTitleResourceIdentifier == 0xFFFF)
{
// Increment the pointer
_ = entry.Data.ReadUInt16LittleEndian(ref offset);
// Read the ordinal
dialogItemTemplate.TitleResourceOrdinal = entry.Data.ReadUInt16LittleEndian(ref offset);
}
else
{
// Read the title resource as a string
dialogItemTemplate.TitleResource = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
// Align to the WORD boundary if we're not at the end
entry.Data.AlignToBoundary(ref offset, 2);
}
#endregion
#region Creation data
dialogItemTemplate.CreationDataSize = entry.Data.ReadUInt16LittleEndian(ref offset);
if (dialogItemTemplate.CreationDataSize != 0 && dialogItemTemplate.CreationDataSize + offset < entry.Data.Length)
dialogItemTemplate.CreationData = entry.Data.ReadBytes(ref offset, dialogItemTemplate.CreationDataSize);
#endregion
// Align to the DWORD boundary if we're not at the end
entry.Data.AlignToBoundary(ref offset, 4);
dialogItemTemplates.Add(dialogItemTemplate);
// If we have an invalid item count
if (offset >= entry.Data.Length)
break;
}
dialogBoxResource.DialogItemTemplates = [.. dialogItemTemplates];
#endregion
}
return dialogBoxResource;
}
///
/// Read resource data as a font group
///
/// Resource data entry to parse into a font group
/// A filled font group on success, null on error
public static FontGroupHeader? AsFontGroup(this Data.Models.PortableExecutable.Resource.DataEntry? entry)
{
// If we have an invalid entry, just skip
if (entry?.Data == null)
return null;
// Initialize the iterator
int offset = 0;
// Create the output object
var fontGroupHeader = new FontGroupHeader();
fontGroupHeader.NumberOfFonts = entry.Data.ReadUInt16LittleEndian(ref offset);
if (fontGroupHeader.NumberOfFonts > 0)
{
fontGroupHeader.DE = new DirEntry[fontGroupHeader.NumberOfFonts];
for (int i = 0; i < fontGroupHeader.NumberOfFonts; i++)
{
var dirEntry = new DirEntry();
dirEntry.FontOrdinal = entry.Data.ReadUInt16LittleEndian(ref offset);
dirEntry.Entry = new FontDirEntry();
dirEntry.Entry.Version = entry.Data.ReadUInt16LittleEndian(ref offset);
dirEntry.Entry.Size = entry.Data.ReadUInt32LittleEndian(ref offset);
dirEntry.Entry.Copyright = entry.Data.ReadBytes(ref offset, 60);
dirEntry.Entry.Type = entry.Data.ReadUInt16LittleEndian(ref offset);
dirEntry.Entry.Points = entry.Data.ReadUInt16LittleEndian(ref offset);
dirEntry.Entry.VertRes = entry.Data.ReadUInt16LittleEndian(ref offset);
dirEntry.Entry.HorizRes = entry.Data.ReadUInt16LittleEndian(ref offset);
dirEntry.Entry.Ascent = entry.Data.ReadUInt16LittleEndian(ref offset);
dirEntry.Entry.InternalLeading = entry.Data.ReadUInt16LittleEndian(ref offset);
dirEntry.Entry.ExternalLeading = entry.Data.ReadUInt16LittleEndian(ref offset);
dirEntry.Entry.Italic = entry.Data.ReadByte(ref offset);
dirEntry.Entry.Underline = entry.Data.ReadByte(ref offset);
dirEntry.Entry.StrikeOut = entry.Data.ReadByte(ref offset);
dirEntry.Entry.Weight = entry.Data.ReadUInt16LittleEndian(ref offset);
dirEntry.Entry.CharSet = entry.Data.ReadByte(ref offset);
dirEntry.Entry.PixWidth = entry.Data.ReadUInt16LittleEndian(ref offset);
dirEntry.Entry.PixHeight = entry.Data.ReadUInt16LittleEndian(ref offset);
dirEntry.Entry.PitchAndFamily = entry.Data.ReadByte(ref offset);
dirEntry.Entry.AvgWidth = entry.Data.ReadUInt16LittleEndian(ref offset);
dirEntry.Entry.MaxWidth = entry.Data.ReadUInt16LittleEndian(ref offset);
dirEntry.Entry.FirstChar = entry.Data.ReadByte(ref offset);
dirEntry.Entry.LastChar = entry.Data.ReadByte(ref offset);
dirEntry.Entry.DefaultChar = entry.Data.ReadByte(ref offset);
dirEntry.Entry.BreakChar = entry.Data.ReadByte(ref offset);
dirEntry.Entry.WidthBytes = entry.Data.ReadUInt16LittleEndian(ref offset);
dirEntry.Entry.Device = entry.Data.ReadUInt32LittleEndian(ref offset);
dirEntry.Entry.Face = entry.Data.ReadUInt32LittleEndian(ref offset);
dirEntry.Entry.Reserved = entry.Data.ReadUInt32LittleEndian(ref offset);
// TODO: Determine how to read these two? Immediately after?
dirEntry.Entry.DeviceName = entry.Data.ReadNullTerminatedAnsiString(ref offset);
dirEntry.Entry.FaceName = entry.Data.ReadNullTerminatedAnsiString(ref offset);
fontGroupHeader.DE[i] = dirEntry;
}
}
// TODO: Implement entry parsing
return fontGroupHeader;
}
///
/// Read resource data as a menu
///
/// Resource data entry to parse into a menu
/// A filled menu on success, null on error
public static MenuResource? AsMenu(this Data.Models.PortableExecutable.Resource.DataEntry? entry)
{
// If we have an invalid entry, just skip
if (entry?.Data == null)
return null;
// Initialize the iterator
int offset = 0;
// Create the output object
var menuResource = new MenuResource();
// Try to read the version for an extended header
int versionOffset = 0;
int possibleVersion = entry.Data.ReadUInt16LittleEndian(ref versionOffset);
if (possibleVersion == 0x0001)
{
#region Extended menu header
var menuHeaderExtended = ParseMenuHeaderExtended(entry.Data, ref offset);
menuResource.MenuHeader = menuHeaderExtended;
#endregion
#region Extended dialog item templates
var extendedMenuItems = new List();
if (offset != 0)
{
offset = menuHeaderExtended.Offset;
while (offset < entry.Data.Length)
{
var extendedMenuItem = ParseMenuItemExtended(entry.Data, ref offset);
extendedMenuItems.Add(extendedMenuItem);
// Align to the DWORD boundary if we're not at the end
entry.Data.AlignToBoundary(ref offset, 4);
}
}
menuResource.MenuItems = [.. extendedMenuItems];
#endregion
}
else
{
#region Menu header
menuResource.MenuHeader = ParseNormalMenuHeader(entry.Data, ref offset);
#endregion
#region Menu items
var menuItems = new List