Files
SabreTools.Serialization/SabreTools.Serialization/Extensions.PortableExecutable.cs

1444 lines
60 KiB
C#
Raw Normal View History

2023-09-08 22:55:47 -04:00
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml.Serialization;
2024-04-17 11:52:22 -04:00
using SabreTools.IO.Extensions;
2023-09-15 22:38:47 -04:00
using SabreTools.Models.PortableExecutable;
2023-09-08 22:55:47 -04:00
namespace SabreTools.Serialization
{
2023-09-15 22:38:47 -04:00
public static partial class Extensions
2023-09-08 22:55:47 -04:00
{
/// <summary>
/// Convert a relative virtual address to a physical one
/// </summary>
/// <param name="rva">Relative virtual address to convert</param>
/// <param name="sections">Array of sections to check against</param>
/// <returns>Physical address, 0 on error</returns>
2024-11-29 20:48:50 -05:00
public static uint ConvertVirtualAddress(this uint rva, SectionHeader[]? sections)
2023-09-08 22:55:47 -04:00
{
// 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
2024-11-30 02:49:10 -05:00
var matchingSection = Array.Find(sections, s => s.VirtualAddress == rva);
2023-09-08 22:55:47 -04:00
if (matchingSection != null)
return rva - matchingSection.VirtualAddress + matchingSection.PointerToRawData;
// Loop through all of the sections
for (int i = 0; i < sections.Length; i++)
{
2024-11-30 02:49:10 -05:00
// If the section "starts" at 0, just skip it
var section = sections[i];
if (section.PointerToRawData == 0)
2023-09-08 22:55:47 -04:00
continue;
2024-11-30 02:49:10 -05:00
// If the virtual address is greater than the RVA
if (rva < section.VirtualAddress)
2023-09-08 22:55:47 -04:00
continue;
// Attempt to derive the physical address from the current section
2024-11-30 02:49:10 -05:00
if (section.VirtualSize != 0 && rva <= section.VirtualAddress + section.VirtualSize)
2023-09-08 22:55:47 -04:00
return rva - section.VirtualAddress + section.PointerToRawData;
2024-11-30 02:49:10 -05:00
else if (section.SizeOfRawData != 0 && rva <= section.VirtualAddress + section.SizeOfRawData)
2023-09-08 22:55:47 -04:00
return rva - section.VirtualAddress + section.PointerToRawData;
}
return 0;
}
/// <summary>
/// Find the section a revlative virtual address lives in
/// </summary>
/// <param name="rva">Relative virtual address to convert</param>
/// <param name="sections">Array of sections to check against</param>
/// <returns>Section index, null on error</returns>
2024-11-29 20:48:50 -05:00
public static int ContainingSectionIndex(this uint rva, SectionHeader[]? sections)
2023-09-08 22:55:47 -04:00
{
// 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++)
{
2024-11-30 02:49:10 -05:00
// If the section "starts" at 0, just skip it
var section = sections[i];
2024-11-30 02:49:10 -05:00
if (section.PointerToRawData == 0)
2023-09-08 22:55:47 -04:00
continue;
2024-11-30 02:49:10 -05:00
// If the virtual address is greater than the RVA
if (rva < section.VirtualAddress)
2023-09-08 22:55:47 -04:00
continue;
// Attempt to derive the physical address from the current section
2024-11-30 02:49:10 -05:00
if (section.VirtualSize != 0 && rva <= section.VirtualAddress + section.VirtualSize)
2023-09-08 22:55:47 -04:00
return i;
2024-11-30 02:49:10 -05:00
else if (section.SizeOfRawData != 0 && rva <= section.VirtualAddress + section.SizeOfRawData)
2023-09-08 22:55:47 -04:00
return i;
}
return -1;
}
2024-04-03 22:31:24 -04:00
#region Debug
/// <summary>
2024-12-17 00:00:28 -05:00
/// Parse a byte array into a NB10ProgramDatabase
2024-04-03 22:31:24 -04:00
/// </summary>
2024-12-17 00:00:28 -05:00
/// <param name="data">Data to parse</param>
2024-04-03 22:31:24 -04:00
/// <param name="offset">Offset into the byte array</param>
2024-12-17 00:00:28 -05:00
/// <returns>A filled NB10ProgramDatabase on success, null on error</returns>
public static NB10ProgramDatabase? ParseNB10ProgramDatabase(this byte[] data, ref int offset)
2024-04-03 22:31:24 -04:00
{
2024-12-17 00:00:28 -05:00
var obj = new NB10ProgramDatabase();
obj.Signature = data.ReadUInt32LittleEndian(ref offset);
if (obj.Signature != 0x3031424E)
2024-04-03 22:31:24 -04:00
return null;
2024-12-17 00:00:28 -05:00
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;
2024-04-03 22:31:24 -04:00
}
/// <summary>
2024-12-17 00:00:28 -05:00
/// Parse a byte array into a RSDSProgramDatabase
2024-04-03 22:31:24 -04:00
/// </summary>
2024-12-17 00:00:28 -05:00
/// <param name="data">Data to parse</param>
2024-04-03 22:31:24 -04:00
/// <param name="offset">Offset into the byte array</param>
2024-12-17 00:00:28 -05:00
/// <returns>A filled RSDSProgramDatabase on success, null on error</returns>
public static RSDSProgramDatabase? ParseRSDSProgramDatabase(this byte[] data, ref int offset)
2024-04-03 22:31:24 -04:00
{
2024-12-17 00:00:28 -05:00
var obj = new RSDSProgramDatabase();
obj.Signature = data.ReadUInt32LittleEndian(ref offset);
if (obj.Signature != 0x53445352)
2024-04-03 22:31:24 -04:00
return null;
2024-12-17 00:00:28 -05:00
obj.GUID = data.ReadGuid(ref offset);
obj.Age = data.ReadUInt32LittleEndian(ref offset);
obj.PathAndFileName = data.ReadNullTerminatedUTF8String(ref offset);
2024-05-07 08:55:54 -04:00
2024-12-17 00:00:28 -05:00
return obj;
2024-04-03 22:31:24 -04:00
}
#endregion
#region Overlay
2023-09-08 22:55:47 -04:00
/// <summary>
2024-12-17 00:00:28 -05:00
/// Parse a byte array into a SecuROMAddD
2023-09-08 22:55:47 -04:00
/// </summary>
/// <param name="data">Data to parse into overlay data</param>
/// <param name="offset">Offset into the byte array</param>
2024-12-17 00:00:28 -05:00
/// <returns>A filled SecuROMAddD on success, null on error</returns>
public static SecuROMAddD? ParseSecuROMAddD(this byte[] data, ref int offset)
2023-09-08 22:55:47 -04:00
{
// Read in the table
2024-12-17 00:00:28 -05:00
var obj = new SecuROMAddD();
2023-09-08 22:55:47 -04:00
2024-12-17 00:00:28 -05:00
obj.Signature = data.ReadUInt32LittleEndian(ref offset);
if (obj.Signature != 0x44646441)
2023-09-08 22:55:47 -04:00
return null;
int originalOffset = offset;
2024-12-17 00:00:28 -05:00
obj.EntryCount = data.ReadUInt32LittleEndian(ref offset);
obj.Version = data.ReadNullTerminatedAnsiString(ref offset);
if (string.IsNullOrEmpty(obj.Version))
2023-09-08 22:55:47 -04:00
offset = originalOffset + 0x10;
2024-11-12 13:03:06 -05:00
var buildBytes = data.ReadBytes(ref offset, 4);
var buildChars = Array.ConvertAll(buildBytes, b => (char)b);
2024-12-17 00:00:28 -05:00
obj.Build = buildChars;
2023-09-08 22:55:47 -04:00
// Distinguish between v1 and v2
int bytesToRead = 112; // v2
2024-12-17 00:00:28 -05:00
if (string.IsNullOrEmpty(obj.Version)
|| obj.Version!.StartsWith("3")
|| obj.Version.StartsWith("4.47"))
2023-09-08 22:55:47 -04:00
{
bytesToRead = 44;
}
2024-12-17 00:00:28 -05:00
obj.Unknown14h = data.ReadBytes(ref offset, bytesToRead);
2023-09-08 22:55:47 -04:00
2024-12-17 00:00:28 -05:00
obj.Entries = new SecuROMAddDEntry[obj.EntryCount];
for (int i = 0; i < obj.EntryCount; i++)
2023-09-08 22:55:47 -04:00
{
2024-12-17 00:00:28 -05:00
obj.Entries[i] = ParseSecuROMAddDEntry(data, ref offset);
2023-09-08 22:55:47 -04:00
}
2024-12-17 00:00:28 -05:00
return obj;
2023-09-08 22:55:47 -04:00
}
/// <summary>
2024-12-17 00:00:28 -05:00
/// Parse a byte array into a SecuROMAddDEntry
2023-09-08 22:55:47 -04:00
/// </summary>
2024-12-17 00:00:28 -05:00
/// <param name="data">Data to parse</param>
2023-09-08 22:55:47 -04:00
/// <param name="offset">Offset into the byte array</param>
2024-12-17 00:00:28 -05:00
/// <returns>Filled SecuROMAddDEntry on success, null on error</returns>
public static SecuROMAddDEntry ParseSecuROMAddDEntry(this byte[] data, ref int offset)
2023-09-08 22:55:47 -04:00
{
2024-12-17 00:00:28 -05:00
var obj = new SecuROMAddDEntry();
obj.PhysicalOffset = data.ReadUInt32LittleEndian(ref offset);
obj.Length = data.ReadUInt32LittleEndian(ref offset);
obj.Unknown08h = data.ReadUInt32LittleEndian(ref offset);
obj.Unknown0Ch = data.ReadUInt32LittleEndian(ref offset);
obj.Unknown10h = data.ReadUInt32LittleEndian(ref offset);
obj.Unknown14h = data.ReadUInt32LittleEndian(ref offset);
obj.Unknown18h = data.ReadUInt32LittleEndian(ref offset);
obj.Unknown1Ch = data.ReadUInt32LittleEndian(ref offset);
obj.FileName = data.ReadNullTerminatedAnsiString(ref offset);
obj.Unknown2Ch = data.ReadUInt32LittleEndian(ref offset);
return obj;
2023-09-08 22:55:47 -04:00
}
2024-12-17 00:00:28 -05:00
#endregion
// TODO: Implement other resource types from https://learn.microsoft.com/en-us/windows/win32/menurc/resource-file-formats
#region Resources
2023-09-08 22:55:47 -04:00
/// <summary>
/// Read resource data as an accelerator table resource
/// </summary>
/// <param name="entry">Resource data entry to parse into an accelerator table resource</param>
/// <returns>A filled accelerator table resource on success, null on error</returns>
2023-09-15 22:38:47 -04:00
public static AcceleratorTableEntry[]? AsAcceleratorTableResource(this ResourceDataEntry? entry)
2023-09-08 22:55:47 -04:00
{
// 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
2023-09-15 22:38:47 -04:00
var table = new AcceleratorTableEntry[count];
2023-09-08 22:55:47 -04:00
// Read in the table
for (int i = 0; i < count; i++)
{
2024-12-17 00:00:28 -05:00
table[i] = ParseAcceleratorTableEntry(entry.Data, ref offset);
2023-09-08 22:55:47 -04:00
}
return table;
}
/// <summary>
/// Read resource data as a side-by-side assembly manifest
/// </summary>
/// <param name="entry">Resource data entry to parse into a side-by-side assembly manifest</param>
/// <returns>A filled side-by-side assembly manifest on success, null on error</returns>
2023-09-15 22:38:47 -04:00
public static AssemblyManifest? AsAssemblyManifest(this ResourceDataEntry? entry)
2023-09-08 22:55:47 -04:00
{
// If we have an invalid entry, just skip
if (entry?.Data == null)
return null;
try
{
var serializer = new XmlSerializer(typeof(AssemblyManifest));
2023-09-15 22:38:47 -04:00
return serializer.Deserialize(new MemoryStream(entry.Data)) as AssemblyManifest;
2023-09-08 22:55:47 -04:00
}
catch
{
return null;
}
}
/// <summary>
/// Read resource data as a dialog box
/// </summary>
/// <param name="entry">Resource data entry to parse into a dialog box</param>
/// <returns>A filled dialog box on success, null on error</returns>
2023-09-15 22:38:47 -04:00
public static DialogBoxResource? AsDialogBox(this ResourceDataEntry? entry)
2023-09-08 22:55:47 -04:00
{
// If we have an invalid entry, just skip
if (entry?.Data == null)
return null;
// Initialize the iterator
int offset = 0;
// Create the output object
2023-09-15 22:38:47 -04:00
var dialogBoxResource = new DialogBoxResource();
2023-09-08 22:55:47 -04:00
// Try to read the signature for an extended dialog box template
int signatureOffset = sizeof(ushort);
2024-12-17 00:00:28 -05:00
int possibleSignature = entry.Data.ReadUInt16LittleEndian(ref signatureOffset);
2023-09-08 22:55:47 -04:00
if (possibleSignature == 0xFFFF)
{
#region Extended dialog template
2023-09-15 22:38:47 -04:00
var dialogTemplateExtended = new DialogTemplateExtended();
2023-09-08 22:55:47 -04:00
2024-12-17 00:00:28 -05:00
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);
2023-09-08 22:55:47 -04:00
#region Menu resource
int currentOffset = offset;
2024-12-17 00:00:28 -05:00
ushort menuResourceIdentifier = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
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
2024-04-23 15:18:18 -04:00
dialogTemplateExtended.MenuResource = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
2023-09-08 22:55:47 -04:00
// Align to the WORD boundary if we're not at the end
2025-07-24 10:55:52 -04:00
entry.Data.AlignToBoundary(ref offset, 2);
2023-09-08 22:55:47 -04:00
// Read the ordinal if we have the flag set
if (menuResourceHasOrdinal)
2024-12-17 00:00:28 -05:00
dialogTemplateExtended.MenuResourceOrdinal = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
}
#endregion
#region Class resource
currentOffset = offset;
2024-12-17 00:00:28 -05:00
ushort classResourceIdentifier = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
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
2024-04-23 15:18:18 -04:00
dialogTemplateExtended.ClassResource = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
2023-09-08 22:55:47 -04:00
// Align to the WORD boundary if we're not at the end
2025-07-24 10:55:52 -04:00
entry.Data.AlignToBoundary(ref offset, 2);
2023-09-08 22:55:47 -04:00
// Read the ordinal if we have the flag set
if (classResourcehasOrdinal)
2024-12-17 00:00:28 -05:00
dialogTemplateExtended.ClassResourceOrdinal = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
}
#endregion
#region Title resource
currentOffset = offset;
2024-12-17 00:00:28 -05:00
ushort titleResourceIdentifier = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
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
2024-04-23 15:18:18 -04:00
dialogTemplateExtended.TitleResource = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
2023-09-08 22:55:47 -04:00
// Align to the WORD boundary if we're not at the end
2025-07-24 10:55:52 -04:00
entry.Data.AlignToBoundary(ref offset, 2);
2023-09-08 22:55:47 -04:00
}
#endregion
#region Point size and typeface
// Only if DS_SETFONT is set are the values here used
2023-11-21 20:59:20 -05:00
#if NET20 || NET35
if ((dialogTemplateExtended.Style & WindowStyles.DS_SETFONT) != 0)
#else
2023-09-15 22:38:47 -04:00
if (dialogTemplateExtended.Style.HasFlag(WindowStyles.DS_SETFONT))
2023-11-21 20:59:20 -05:00
#endif
2023-09-08 22:55:47 -04:00
{
2024-12-17 00:00:28 -05:00
dialogTemplateExtended.PointSize = entry.Data.ReadUInt16LittleEndian(ref offset);
dialogTemplateExtended.Weight = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
dialogTemplateExtended.Italic = entry.Data.ReadByte(ref offset);
dialogTemplateExtended.CharSet = entry.Data.ReadByte(ref offset);
2024-04-23 15:18:18 -04:00
dialogTemplateExtended.Typeface = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
2023-09-08 22:55:47 -04:00
}
// Align to the DWORD boundary if we're not at the end
2025-07-24 10:55:52 -04:00
entry.Data.AlignToBoundary(ref offset, 4);
2023-09-08 22:55:47 -04:00
#endregion
dialogBoxResource.ExtendedDialogTemplate = dialogTemplateExtended;
#endregion
#region Extended dialog item templates
2023-09-15 22:38:47 -04:00
var dialogItemExtendedTemplates = new List<DialogItemTemplateExtended>();
2023-09-08 22:55:47 -04:00
for (int i = 0; i < dialogTemplateExtended.DialogItems; i++)
{
2023-09-15 22:38:47 -04:00
var dialogItemTemplate = new DialogItemTemplateExtended();
2023-09-08 22:55:47 -04:00
2024-12-17 00:00:28 -05:00
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);
2024-12-17 00:00:28 -05:00
dialogItemTemplate.ID = entry.Data.ReadUInt32LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
#region Class resource
currentOffset = offset;
2024-12-17 00:00:28 -05:00
ushort itemClassResourceIdentifier = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
offset = currentOffset;
// 0xFFFF means ordinal only
if (itemClassResourceIdentifier == 0xFFFF)
{
// Increment the pointer
2024-12-17 00:00:28 -05:00
_ = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
// Read the ordinal
2024-12-17 00:00:28 -05:00
dialogItemTemplate.ClassResourceOrdinal = (DialogItemTemplateOrdinal)entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
}
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
2024-04-23 15:18:18 -04:00
dialogItemTemplate.ClassResource = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
2023-09-08 22:55:47 -04:00
// Align to the WORD boundary if we're not at the end
2025-07-24 10:55:52 -04:00
entry.Data.AlignToBoundary(ref offset, 2);
2023-09-08 22:55:47 -04:00
}
#endregion
#region Title resource
currentOffset = offset;
2024-12-17 00:00:28 -05:00
ushort itemTitleResourceIdentifier = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
offset = currentOffset;
// 0xFFFF means ordinal only
if (itemTitleResourceIdentifier == 0xFFFF)
{
// Increment the pointer
2024-12-17 00:00:28 -05:00
_ = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
// Read the ordinal
2024-12-17 00:00:28 -05:00
dialogItemTemplate.TitleResourceOrdinal = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
}
else
{
// Read the title resource as a string
2024-04-23 15:18:18 -04:00
dialogItemTemplate.TitleResource = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
2023-09-08 22:55:47 -04:00
// Align to the WORD boundary if we're not at the end
2025-07-24 10:55:52 -04:00
entry.Data.AlignToBoundary(ref offset, 2);
2023-09-08 22:55:47 -04:00
}
#endregion
#region Creation data
2024-12-17 00:00:28 -05:00
dialogItemTemplate.CreationDataSize = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
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
2025-07-24 10:55:52 -04:00
entry.Data.AlignToBoundary(ref offset, 4);
2023-09-08 22:55:47 -04:00
dialogItemExtendedTemplates.Add(dialogItemTemplate);
// If we have an invalid item count
if (offset >= entry.Data.Length)
break;
2023-09-08 22:55:47 -04:00
}
dialogBoxResource.ExtendedDialogItemTemplates = [.. dialogItemExtendedTemplates];
2023-09-08 22:55:47 -04:00
#endregion
}
else
{
#region Dialog template
2023-09-15 22:38:47 -04:00
var dialogTemplate = new DialogTemplate();
2023-09-08 22:55:47 -04:00
2024-12-17 00:00:28 -05:00
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);
2023-09-08 22:55:47 -04:00
#region Menu resource
int currentOffset = offset;
2024-12-17 00:00:28 -05:00
ushort menuResourceIdentifier = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
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
2024-04-23 15:18:18 -04:00
dialogTemplate.MenuResource = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
2023-09-08 22:55:47 -04:00
// Align to the WORD boundary if we're not at the end
2025-07-24 10:55:52 -04:00
entry.Data.AlignToBoundary(ref offset, 2);
2023-09-08 22:55:47 -04:00
// Read the ordinal if we have the flag set
if (menuResourceHasOrdinal)
2024-12-17 00:00:28 -05:00
dialogTemplate.MenuResourceOrdinal = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
}
#endregion
#region Class resource
currentOffset = offset;
2024-05-15 14:59:55 -04:00
ushort classResourceIdentifier;
if (offset >= entry.Data.Length)
classResourceIdentifier = 0x0000;
else
2024-12-17 00:00:28 -05:00
classResourceIdentifier = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
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
2024-04-23 15:18:18 -04:00
dialogTemplate.ClassResource = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
2023-09-08 22:55:47 -04:00
// Align to the WORD boundary if we're not at the end
2025-07-24 10:55:52 -04:00
entry.Data.AlignToBoundary(ref offset, 2);
2023-09-08 22:55:47 -04:00
// Read the ordinal if we have the flag set
if (classResourcehasOrdinal)
2024-12-17 00:00:28 -05:00
dialogTemplate.ClassResourceOrdinal = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
}
#endregion
#region Title resource
currentOffset = offset;
2024-05-15 14:59:55 -04:00
ushort titleResourceIdentifier;
if (offset >= entry.Data.Length)
titleResourceIdentifier = 0x0000;
else
2024-12-17 00:00:28 -05:00
titleResourceIdentifier = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
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
2024-04-23 15:18:18 -04:00
dialogTemplate.TitleResource = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
2023-09-08 22:55:47 -04:00
// Align to the WORD boundary if we're not at the end
2025-07-24 10:55:52 -04:00
entry.Data.AlignToBoundary(ref offset, 2);
2023-09-08 22:55:47 -04:00
}
#endregion
#region Point size and typeface
// Only if DS_SETFONT is set are the values here used
2023-11-21 20:59:20 -05:00
#if NET20 || NET35
if ((dialogTemplate.Style & WindowStyles.DS_SETFONT) != 0)
#else
2023-09-15 22:38:47 -04:00
if (dialogTemplate.Style.HasFlag(WindowStyles.DS_SETFONT))
2023-11-21 20:59:20 -05:00
#endif
2023-09-08 22:55:47 -04:00
{
2024-12-17 00:00:28 -05:00
dialogTemplate.PointSizeValue = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
// Read the font name as a string
2024-04-23 15:18:18 -04:00
dialogTemplate.Typeface = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
2023-09-08 22:55:47 -04:00
}
// Align to the DWORD boundary if we're not at the end
2025-07-24 10:55:52 -04:00
entry.Data.AlignToBoundary(ref offset, 4);
2023-09-08 22:55:47 -04:00
#endregion
dialogBoxResource.DialogTemplate = dialogTemplate;
#endregion
#region Dialog item templates
2023-09-15 22:38:47 -04:00
var dialogItemTemplates = new List<DialogItemTemplate>();
2023-09-08 22:55:47 -04:00
for (int i = 0; i < dialogTemplate.ItemCount; i++)
{
2023-09-15 22:38:47 -04:00
var dialogItemTemplate = new DialogItemTemplate();
2023-09-08 22:55:47 -04:00
2024-12-17 00:00:28 -05:00
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);
2024-12-17 00:00:28 -05:00
dialogItemTemplate.ID = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
#region Class resource
currentOffset = offset;
2024-12-17 00:00:28 -05:00
ushort itemClassResourceIdentifier = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
offset = currentOffset;
// 0xFFFF means ordinal only
if (itemClassResourceIdentifier == 0xFFFF)
{
// Increment the pointer
2024-12-17 00:00:28 -05:00
_ = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
// Read the ordinal
2024-12-17 00:00:28 -05:00
dialogItemTemplate.ClassResourceOrdinal = (DialogItemTemplateOrdinal)entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
}
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
2024-04-23 15:18:18 -04:00
dialogItemTemplate.ClassResource = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
2023-09-08 22:55:47 -04:00
// Align to the WORD boundary if we're not at the end
2025-07-24 10:55:52 -04:00
entry.Data.AlignToBoundary(ref offset, 2);
2023-09-08 22:55:47 -04:00
}
#endregion
#region Title resource
currentOffset = offset;
2024-12-17 00:00:28 -05:00
ushort itemTitleResourceIdentifier = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
offset = currentOffset;
// 0xFFFF means ordinal only
if (itemTitleResourceIdentifier == 0xFFFF)
{
// Increment the pointer
2024-12-17 00:00:28 -05:00
_ = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
// Read the ordinal
2024-12-17 00:00:28 -05:00
dialogItemTemplate.TitleResourceOrdinal = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
}
else
{
// Read the title resource as a string
2024-04-23 15:18:18 -04:00
dialogItemTemplate.TitleResource = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
2023-09-08 22:55:47 -04:00
// Align to the WORD boundary if we're not at the end
2025-07-24 10:55:52 -04:00
entry.Data.AlignToBoundary(ref offset, 2);
2023-09-08 22:55:47 -04:00
}
#endregion
#region Creation data
2024-12-17 00:00:28 -05:00
dialogItemTemplate.CreationDataSize = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
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
2025-07-24 10:55:52 -04:00
entry.Data.AlignToBoundary(ref offset, 4);
2023-09-08 22:55:47 -04:00
dialogItemTemplates.Add(dialogItemTemplate);
// If we have an invalid item count
if (offset >= entry.Data.Length)
break;
2023-09-08 22:55:47 -04:00
}
dialogBoxResource.DialogItemTemplates = [.. dialogItemTemplates];
2023-09-08 22:55:47 -04:00
#endregion
}
return dialogBoxResource;
}
/// <summary>
/// Read resource data as a font group
/// </summary>
/// <param name="entry">Resource data entry to parse into a font group</param>
/// <returns>A filled font group on success, null on error</returns>
2023-09-15 22:38:47 -04:00
public static FontGroupHeader? AsFontGroup(this ResourceDataEntry? entry)
2023-09-08 22:55:47 -04:00
{
// If we have an invalid entry, just skip
if (entry?.Data == null)
return null;
// Initialize the iterator
int offset = 0;
// Create the output object
2023-09-15 22:38:47 -04:00
var fontGroupHeader = new FontGroupHeader();
2023-09-08 22:55:47 -04:00
2024-12-17 00:00:28 -05:00
fontGroupHeader.NumberOfFonts = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
if (fontGroupHeader.NumberOfFonts > 0)
{
2023-09-15 22:38:47 -04:00
fontGroupHeader.DE = new DirEntry[fontGroupHeader.NumberOfFonts];
2023-09-08 22:55:47 -04:00
for (int i = 0; i < fontGroupHeader.NumberOfFonts; i++)
{
2023-09-15 22:38:47 -04:00
var dirEntry = new DirEntry();
2023-09-08 22:55:47 -04:00
2024-12-17 00:00:28 -05:00
dirEntry.FontOrdinal = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
2023-09-15 22:38:47 -04:00
dirEntry.Entry = new FontDirEntry();
2024-12-17 00:00:28 -05:00
dirEntry.Entry.Version = entry.Data.ReadUInt16LittleEndian(ref offset);
dirEntry.Entry.Size = entry.Data.ReadUInt32LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
dirEntry.Entry.Copyright = entry.Data.ReadBytes(ref offset, 60);
2024-12-17 00:00:28 -05:00
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);
2023-09-08 22:55:47 -04:00
dirEntry.Entry.Italic = entry.Data.ReadByte(ref offset);
dirEntry.Entry.Underline = entry.Data.ReadByte(ref offset);
dirEntry.Entry.StrikeOut = entry.Data.ReadByte(ref offset);
2024-12-17 00:00:28 -05:00
dirEntry.Entry.Weight = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
dirEntry.Entry.CharSet = entry.Data.ReadByte(ref offset);
2024-12-17 00:00:28 -05:00
dirEntry.Entry.PixWidth = entry.Data.ReadUInt16LittleEndian(ref offset);
dirEntry.Entry.PixHeight = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
dirEntry.Entry.PitchAndFamily = entry.Data.ReadByte(ref offset);
2024-12-17 00:00:28 -05:00
dirEntry.Entry.AvgWidth = entry.Data.ReadUInt16LittleEndian(ref offset);
dirEntry.Entry.MaxWidth = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
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);
2024-12-17 00:00:28 -05:00
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);
2023-09-10 17:32:03 -04:00
2023-09-08 22:55:47 -04:00
// TODO: Determine how to read these two? Immediately after?
2024-04-23 15:18:18 -04:00
dirEntry.Entry.DeviceName = entry.Data.ReadNullTerminatedAnsiString(ref offset);
dirEntry.Entry.FaceName = entry.Data.ReadNullTerminatedAnsiString(ref offset);
2023-09-08 22:55:47 -04:00
fontGroupHeader.DE[i] = dirEntry;
}
}
// TODO: Implement entry parsing
return fontGroupHeader;
}
/// <summary>
/// Read resource data as a menu
/// </summary>
/// <param name="entry">Resource data entry to parse into a menu</param>
/// <returns>A filled menu on success, null on error</returns>
2023-09-15 22:38:47 -04:00
public static MenuResource? AsMenu(this ResourceDataEntry? entry)
2023-09-08 22:55:47 -04:00
{
// If we have an invalid entry, just skip
if (entry?.Data == null)
return null;
// Initialize the iterator
int offset = 0;
// Create the output object
2023-09-15 22:38:47 -04:00
var menuResource = new MenuResource();
2023-09-08 22:55:47 -04:00
// Try to read the version for an extended header
int versionOffset = 0;
2024-12-17 00:00:28 -05:00
int possibleVersion = entry.Data.ReadUInt16LittleEndian(ref versionOffset);
2023-09-08 22:55:47 -04:00
if (possibleVersion == 0x0001)
{
#region Extended menu header
2024-12-17 00:00:28 -05:00
var menuHeaderExtended = ParseMenuHeaderExtended(entry.Data, ref offset);
2024-05-15 14:59:55 -04:00
menuResource.MenuHeader = menuHeaderExtended;
2023-09-08 22:55:47 -04:00
#endregion
#region Extended dialog item templates
2023-09-15 22:38:47 -04:00
var extendedMenuItems = new List<MenuItemExtended>();
2023-09-08 22:55:47 -04:00
if (offset != 0)
{
offset = menuHeaderExtended.Offset;
while (offset < entry.Data.Length)
{
2024-12-17 00:00:28 -05:00
var extendedMenuItem = ParseMenuItemExtended(entry.Data, ref offset);
extendedMenuItems.Add(extendedMenuItem);
2023-09-08 22:55:47 -04:00
// Align to the DWORD boundary if we're not at the end
2025-07-24 10:55:52 -04:00
entry.Data.AlignToBoundary(ref offset, 4);
2023-09-08 22:55:47 -04:00
}
}
2024-05-15 14:59:55 -04:00
menuResource.MenuItems = [.. extendedMenuItems];
2023-09-08 22:55:47 -04:00
#endregion
}
else
{
#region Menu header
2024-12-17 00:00:28 -05:00
menuResource.MenuHeader = ParseNormalMenuHeader(entry.Data, ref offset);
2023-09-08 22:55:47 -04:00
#endregion
#region Menu items
2023-09-15 22:38:47 -04:00
var menuItems = new List<MenuItem>();
2023-09-08 22:55:47 -04:00
while (offset < entry.Data.Length)
{
// Determine if this is a popup
int flagsOffset = offset;
2024-12-17 00:00:28 -05:00
var initialFlags = (MenuFlags)entry.Data.ReadUInt16LittleEndian(ref flagsOffset);
2024-04-23 21:45:54 -04:00
MenuItem? menuItem;
2023-11-21 20:59:20 -05:00
#if NET20 || NET35
if ((initialFlags & MenuFlags.MF_POPUP) != 0)
#else
2023-09-15 22:38:47 -04:00
if (initialFlags.HasFlag(MenuFlags.MF_POPUP))
2023-11-21 20:59:20 -05:00
#endif
2024-12-17 00:00:28 -05:00
menuItem = ParsePopupMenuItem(entry.Data, ref offset);
2023-09-08 22:55:47 -04:00
else
2024-12-17 00:00:28 -05:00
menuItem = ParseNormalMenuItem(entry.Data, ref offset);
2023-09-08 22:55:47 -04:00
// Align to the DWORD boundary if we're not at the end
2025-07-24 10:55:52 -04:00
entry.Data.AlignToBoundary(ref offset, 4);
2023-09-08 22:55:47 -04:00
2024-04-23 21:45:54 -04:00
if (menuItem == null)
return null;
2023-09-08 22:55:47 -04:00
menuItems.Add(menuItem);
}
menuResource.MenuItems = [.. menuItems];
2023-09-08 22:55:47 -04:00
#endregion
}
return menuResource;
}
/// <summary>
/// Read resource data as a message table resource
/// </summary>
/// <param name="entry">Resource data entry to parse into a message table resource</param>
/// <returns>A filled message table resource on success, null on error</returns>
2023-09-15 22:38:47 -04:00
public static MessageResourceData? AsMessageResourceData(this ResourceDataEntry? entry)
2023-09-08 22:55:47 -04:00
{
// If we have an invalid entry, just skip
if (entry?.Data == null)
return null;
// Initialize the iterator
int offset = 0;
// Create the output object
2023-09-15 22:38:47 -04:00
var messageResourceData = new MessageResourceData();
2023-09-08 22:55:47 -04:00
// Message resource blocks
2024-12-17 00:00:28 -05:00
messageResourceData.NumberOfBlocks = entry.Data.ReadUInt32LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
if (messageResourceData.NumberOfBlocks > 0)
{
2023-09-15 22:38:47 -04:00
var messageResourceBlocks = new List<MessageResourceBlock>();
2023-09-08 22:55:47 -04:00
for (int i = 0; i < messageResourceData.NumberOfBlocks; i++)
{
2024-12-17 00:00:28 -05:00
var messageResourceBlock = ParseMessageResourceBlock(entry.Data, ref offset);
2023-09-08 22:55:47 -04:00
messageResourceBlocks.Add(messageResourceBlock);
}
messageResourceData.Blocks = [.. messageResourceBlocks];
2023-09-08 22:55:47 -04:00
}
// Message resource entries
if (messageResourceData.Blocks != null && messageResourceData.Blocks.Length != 0)
{
2023-09-15 22:38:47 -04:00
var messageResourceEntries = new Dictionary<uint, MessageResourceEntry?>();
2023-09-08 22:55:47 -04:00
for (int i = 0; i < messageResourceData.Blocks.Length; i++)
{
var messageResourceBlock = messageResourceData.Blocks[i];
2023-09-10 23:01:45 -04:00
if (messageResourceBlock == null)
continue;
2023-09-08 22:55:47 -04:00
offset = (int)messageResourceBlock.OffsetToEntries;
for (uint j = messageResourceBlock.LowId; j <= messageResourceBlock.HighId; j++)
{
2023-09-15 22:38:47 -04:00
var messageResourceEntry = new MessageResourceEntry();
2023-09-08 22:55:47 -04:00
2024-12-17 00:00:28 -05:00
messageResourceEntry.Length = entry.Data.ReadUInt16LittleEndian(ref offset);
messageResourceEntry.Flags = entry.Data.ReadUInt16LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
Encoding textEncoding = messageResourceEntry.Flags == 0x0001 ? Encoding.Unicode : Encoding.ASCII;
2023-09-10 17:32:03 -04:00
byte[]? textArray = entry.Data.ReadBytes(ref offset, messageResourceEntry.Length - 4);
if (textArray != null)
messageResourceEntry.Text = textEncoding.GetString(textArray);
2023-09-08 22:55:47 -04:00
messageResourceEntries[j] = messageResourceEntry;
}
}
messageResourceData.Entries = messageResourceEntries;
}
return messageResourceData;
}
/// <summary>
/// Read byte data as a string file info resource
/// </summary>
/// <param name="data">Data to parse into a string file info</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>A filled string file info resource on success, null on error</returns>
2024-04-23 21:00:10 -04:00
public static StringFileInfo? AsStringFileInfo(byte[] data, ref int offset)
2023-09-08 22:55:47 -04:00
{
2023-09-15 22:38:47 -04:00
var stringFileInfo = new StringFileInfo();
2023-09-08 22:55:47 -04:00
// Cache the initial offset
int currentOffset = offset;
2024-12-17 00:00:28 -05:00
stringFileInfo.Length = data.ReadUInt16LittleEndian(ref offset);
stringFileInfo.ValueLength = data.ReadUInt16LittleEndian(ref offset);
stringFileInfo.ResourceType = (VersionResourceType)data.ReadUInt16LittleEndian(ref offset);
2024-04-23 15:18:18 -04:00
stringFileInfo.Key = data.ReadNullTerminatedUnicodeString(ref offset);
2023-09-08 22:55:47 -04:00
if (stringFileInfo.Key != "StringFileInfo")
{
2023-09-10 17:32:03 -04:00
offset -= 6 + ((stringFileInfo.Key?.Length ?? 0 + 1) * 2);
2023-09-08 22:55:47 -04:00
return null;
}
// Align to the DWORD boundary if we're not at the end
2025-07-24 10:55:52 -04:00
data.AlignToBoundary(ref offset, 4);
2023-09-08 22:55:47 -04:00
2023-09-15 22:38:47 -04:00
var stringFileInfoChildren = new List<StringTable>();
2023-09-08 22:55:47 -04:00
while ((offset - currentOffset) < stringFileInfo.Length)
{
2023-09-15 22:38:47 -04:00
var stringTable = new StringTable();
2023-09-08 22:55:47 -04:00
2024-12-17 00:00:28 -05:00
stringTable.Length = data.ReadUInt16LittleEndian(ref offset);
stringTable.ValueLength = data.ReadUInt16LittleEndian(ref offset);
stringTable.ResourceType = (VersionResourceType)data.ReadUInt16LittleEndian(ref offset);
2024-04-23 15:18:18 -04:00
stringTable.Key = data.ReadNullTerminatedUnicodeString(ref offset);
2023-09-08 22:55:47 -04:00
// Align to the DWORD boundary if we're not at the end
2025-07-24 10:55:52 -04:00
data.AlignToBoundary(ref offset, 4);
2023-09-08 22:55:47 -04:00
2023-09-15 22:38:47 -04:00
var stringTableChildren = new List<StringData>();
2023-09-08 22:55:47 -04:00
while ((offset - currentOffset) < stringTable.Length)
{
2023-09-15 22:38:47 -04:00
var stringData = new StringData();
2023-09-08 22:55:47 -04:00
2024-01-26 01:23:33 -05:00
int dataStartOffset = offset;
2024-12-17 00:00:28 -05:00
stringData.Length = data.ReadUInt16LittleEndian(ref offset);
stringData.ValueLength = data.ReadUInt16LittleEndian(ref offset);
stringData.ResourceType = (VersionResourceType)data.ReadUInt16LittleEndian(ref offset);
2024-04-23 15:18:18 -04:00
stringData.Key = data.ReadNullTerminatedUnicodeString(ref offset);
2023-09-08 22:55:47 -04:00
// Align to the DWORD boundary if we're not at the end
2025-07-24 10:55:52 -04:00
data.AlignToBoundary(ref offset, 4);
2023-09-08 22:55:47 -04:00
if (stringData.ValueLength > 0)
{
2024-01-26 01:23:33 -05:00
int bytesReadable = Math.Min(stringData.ValueLength * sizeof(ushort), stringData.Length - (offset - dataStartOffset));
2024-11-27 13:47:07 -05:00
byte[] valueBytes = data.ReadBytes(ref offset, bytesReadable);
stringData.Value = Encoding.Unicode.GetString(valueBytes);
2023-09-08 22:55:47 -04:00
}
// Align to the DWORD boundary if we're not at the end
2025-07-24 10:55:52 -04:00
data.AlignToBoundary(ref offset, 4);
2023-09-08 22:55:47 -04:00
stringTableChildren.Add(stringData);
if (stringData.Length == 0 && stringData.ValueLength == 0)
break;
2023-09-08 22:55:47 -04:00
}
stringTable.Children = [.. stringTableChildren];
2023-09-08 22:55:47 -04:00
stringFileInfoChildren.Add(stringTable);
}
stringFileInfo.Children = [.. stringFileInfoChildren];
2023-09-08 22:55:47 -04:00
return stringFileInfo;
}
2024-12-17 00:00:28 -05:00
/// <summary>
/// Read resource data as a string table resource
/// </summary>
/// <param name="entry">Resource data entry to parse into a string table resource</param>
/// <returns>A filled string table resource on success, null on error</returns>
public static Dictionary<int, string?>? AsStringTable(this ResourceDataEntry? entry)
{
// If we have an invalid entry, just skip
if (entry?.Data == null)
return null;
// Initialize the iterators
int offset = 0, stringIndex = 0;
// Create the output table
var stringTable = new Dictionary<int, string?>();
// Loop through and add
while (offset < entry.Data.Length)
{
string? stringValue = entry.Data.ReadPrefixedUnicodeString(ref offset);
if (stringValue != null)
{
stringValue = stringValue.Replace("\n", "\\n").Replace("\r", newValue: "\\r");
stringTable[stringIndex++] = stringValue;
}
}
return stringTable;
}
2023-09-08 22:55:47 -04:00
/// <summary>
/// Read byte data as a var file info resource
/// </summary>
/// <param name="data">Data to parse into a var file info</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>A filled var file info resource on success, null on error</returns>
2024-04-23 21:00:10 -04:00
public static VarFileInfo? AsVarFileInfo(byte[] data, ref int offset)
2023-09-08 22:55:47 -04:00
{
2023-09-15 22:38:47 -04:00
var varFileInfo = new VarFileInfo();
2023-09-08 22:55:47 -04:00
// Cache the initial offset
int initialOffset = offset;
2024-12-17 00:00:28 -05:00
varFileInfo.Length = data.ReadUInt16LittleEndian(ref offset);
varFileInfo.ValueLength = data.ReadUInt16LittleEndian(ref offset);
varFileInfo.ResourceType = (VersionResourceType)data.ReadUInt16LittleEndian(ref offset);
2024-04-23 15:18:18 -04:00
varFileInfo.Key = data.ReadNullTerminatedUnicodeString(ref offset);
2023-09-08 22:55:47 -04:00
if (varFileInfo.Key != "VarFileInfo")
return null;
// Align to the DWORD boundary if we're not at the end
2025-07-24 10:55:52 -04:00
data.AlignToBoundary(ref offset, 4);
2023-09-08 22:55:47 -04:00
2023-09-15 22:38:47 -04:00
var varFileInfoChildren = new List<VarData>();
2023-09-08 22:55:47 -04:00
while ((offset - initialOffset) < varFileInfo.Length)
{
2023-09-15 22:38:47 -04:00
var varData = new VarData();
2023-09-08 22:55:47 -04:00
2024-12-17 00:00:28 -05:00
varData.Length = data.ReadUInt16LittleEndian(ref offset);
varData.ValueLength = data.ReadUInt16LittleEndian(ref offset);
varData.ResourceType = (VersionResourceType)data.ReadUInt16LittleEndian(ref offset);
2024-04-23 15:18:18 -04:00
varData.Key = data.ReadNullTerminatedUnicodeString(ref offset);
2023-09-08 22:55:47 -04:00
if (varData.Key != "Translation")
{
2023-09-10 17:32:03 -04:00
offset -= 6 + ((varData.Key?.Length ?? 0 + 1) * 2);
2023-09-08 22:55:47 -04:00
return null;
}
// Align to the DWORD boundary if we're not at the end
2025-07-24 10:55:52 -04:00
data.AlignToBoundary(ref offset, 4);
2023-09-08 22:55:47 -04:00
// Cache the current offset
int currentOffset = offset;
var varDataValue = new List<uint>();
while ((offset - currentOffset) < varData.ValueLength)
{
2024-12-17 00:00:28 -05:00
uint languageAndCodeIdentifierPair = data.ReadUInt32LittleEndian(ref offset);
2023-09-08 22:55:47 -04:00
varDataValue.Add(languageAndCodeIdentifierPair);
}
varData.Value = [.. varDataValue];
2023-09-08 22:55:47 -04:00
varFileInfoChildren.Add(varData);
}
varFileInfo.Children = [.. varFileInfoChildren];
2023-09-08 22:55:47 -04:00
return varFileInfo;
}
2024-12-17 00:00:28 -05:00
/// <summary>
/// Read resource data as a version info resource
/// </summary>
/// <param name="entry">Resource data entry to parse into a version info resource</param>
/// <returns>A filled version info resource on success, null on error</returns>
public static VersionInfo? AsVersionInfo(this ResourceDataEntry? 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 versionInfo = new VersionInfo();
versionInfo.Length = entry.Data.ReadUInt16LittleEndian(ref offset);
versionInfo.ValueLength = entry.Data.ReadUInt16LittleEndian(ref offset);
versionInfo.ResourceType = (VersionResourceType)entry.Data.ReadUInt16LittleEndian(ref offset);
versionInfo.Key = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
if (versionInfo.Key != "VS_VERSION_INFO")
return null;
while (offset < entry.Data.Length && (offset % 4) != 0)
versionInfo.Padding1 = entry.Data.ReadUInt16LittleEndian(ref offset);
// Read fixed file info
if (versionInfo.ValueLength > 0 && offset + versionInfo.ValueLength <= entry.Data.Length)
{
var fixedFileInfo = ParseFixedFileInfo(entry.Data, ref offset);
if (fixedFileInfo?.Signature != 0xFEEF04BD)
return null;
versionInfo.Value = fixedFileInfo;
}
while (offset < entry.Data.Length && (offset % 4) != 0)
versionInfo.Padding2 = entry.Data.ReadUInt16LittleEndian(ref offset);
// TODO: Make the following block a private helper method
// Determine if we have a StringFileInfo or VarFileInfo next
if (offset < versionInfo.Length)
{
// Cache the current offset for reading
int currentOffset = offset;
offset += 6;
string? nextKey = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
offset = currentOffset;
if (nextKey == "StringFileInfo")
{
var stringFileInfo = AsStringFileInfo(entry.Data, ref offset);
versionInfo.StringFileInfo = stringFileInfo;
}
else if (nextKey == "VarFileInfo")
{
var varFileInfo = AsVarFileInfo(entry.Data, ref offset);
versionInfo.VarFileInfo = varFileInfo;
}
}
// And again
if (offset < versionInfo.Length)
{
// Cache the current offset for reading
int currentOffset = offset;
offset += 6;
string? nextKey = entry.Data.ReadNullTerminatedUnicodeString(ref offset);
offset = currentOffset;
if (nextKey == "StringFileInfo")
{
var stringFileInfo = AsStringFileInfo(entry.Data, ref offset);
versionInfo.StringFileInfo = stringFileInfo;
}
else if (nextKey == "VarFileInfo")
{
var varFileInfo = AsVarFileInfo(entry.Data, ref offset);
versionInfo.VarFileInfo = varFileInfo;
}
}
return versionInfo;
}
/// <summary>
/// Parse a byte array into a AcceleratorTableEntry
/// </summary>
/// <param name="data">Data to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>A filled AcceleratorTableEntry on success, null on error</returns>
public static AcceleratorTableEntry ParseAcceleratorTableEntry(this byte[] data, ref int offset)
{
var obj = new AcceleratorTableEntry();
obj.Flags = (AcceleratorTableFlags)data.ReadUInt16LittleEndian(ref offset);
obj.Ansi = data.ReadUInt16LittleEndian(ref offset);
obj.Id = data.ReadUInt16LittleEndian(ref offset);
obj.Padding = data.ReadUInt16LittleEndian(ref offset);
return obj;
}
/// <summary>
/// Parse a byte array into a FixedFileInfo
/// </summary>
/// <param name="data">Data to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>A filled FixedFileInfo on success, null on error</returns>
public static FixedFileInfo ParseFixedFileInfo(this byte[] data, ref int offset)
{
var obj = new FixedFileInfo();
obj.Signature = data.ReadUInt32LittleEndian(ref offset);
obj.StrucVersion = data.ReadUInt32LittleEndian(ref offset);
obj.FileVersionMS = data.ReadUInt32LittleEndian(ref offset);
obj.FileVersionLS = data.ReadUInt32LittleEndian(ref offset);
obj.ProductVersionMS = data.ReadUInt32LittleEndian(ref offset);
obj.ProductVersionLS = data.ReadUInt32LittleEndian(ref offset);
obj.FileFlagsMask = data.ReadUInt32LittleEndian(ref offset);
obj.FileFlags = (FixedFileInfoFlags)data.ReadUInt32LittleEndian(ref offset);
obj.FileOS = (FixedFileInfoOS)data.ReadUInt32LittleEndian(ref offset);
obj.FileType = (FixedFileInfoFileType)data.ReadUInt32LittleEndian(ref offset);
obj.FileSubtype = (FixedFileInfoFileSubtype)data.ReadUInt32LittleEndian(ref offset);
obj.FileDateMS = data.ReadUInt32LittleEndian(ref offset);
obj.FileDateLS = data.ReadUInt32LittleEndian(ref offset);
return obj;
}
/// <summary>
/// Parse a byte array into a MenuHeaderExtended
/// </summary>
/// <param name="data">Data to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>A filled MenuHeaderExtended on success, null on error</returns>
public static MenuHeaderExtended ParseMenuHeaderExtended(this byte[] data, ref int offset)
{
var obj = new MenuHeaderExtended();
obj.Version = data.ReadUInt16LittleEndian(ref offset);
obj.Offset = data.ReadUInt16LittleEndian(ref offset);
obj.HelpID = data.ReadUInt32LittleEndian(ref offset);
return obj;
}
/// <summary>
/// Parse a byte array into a MenuItemExtended
/// </summary>
/// <param name="data">Data to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>A filled MenuItemExtended on success, null on error</returns>
public static MenuItemExtended ParseMenuItemExtended(this byte[] data, ref int offset)
{
var obj = new MenuItemExtended();
obj.ItemType = (MenuFlags)data.ReadUInt32LittleEndian(ref offset);
obj.State = (MenuFlags)data.ReadUInt32LittleEndian(ref offset);
obj.ID = data.ReadUInt32LittleEndian(ref offset);
obj.Flags = (MenuFlags)data.ReadUInt32LittleEndian(ref offset);
obj.MenuText = data.ReadNullTerminatedUnicodeString(ref offset);
return obj;
}
/// <summary>
/// Parse a byte array into a MessageResourceBlock
/// </summary>
/// <param name="data">Data to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>A filled MessageResourceBlock on success, null on error</returns>
public static MessageResourceBlock ParseMessageResourceBlock(this byte[] data, ref int offset)
{
var obj = new MessageResourceBlock();
obj.LowId = data.ReadUInt32LittleEndian(ref offset);
obj.HighId = data.ReadUInt32LittleEndian(ref offset);
obj.OffsetToEntries = data.ReadUInt32LittleEndian(ref offset);
return obj;
}
/// <summary>
/// Parse a byte array into a NormalMenuHeader
/// </summary>
/// <param name="data">Data to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>A filled NormalMenuHeader on success, null on error</returns>
public static NormalMenuHeader ParseNormalMenuHeader(this byte[] data, ref int offset)
{
var obj = new NormalMenuHeader();
obj.Version = data.ReadUInt16LittleEndian(ref offset);
obj.HeaderSize = data.ReadUInt16LittleEndian(ref offset);
return obj;
}
/// <summary>
/// Parse a byte array into a NormalMenuItem
/// </summary>
/// <param name="data">Data to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>A filled NormalMenuItem on success, null on error</returns>
public static NormalMenuItem ParseNormalMenuItem(this byte[] data, ref int offset)
{
var obj = new NormalMenuItem();
obj.NormalResInfo = (MenuFlags)data.ReadUInt32LittleEndian(ref offset);
obj.NormalMenuText = data.ReadNullTerminatedUnicodeString(ref offset);
return obj;
}
/// <summary>
/// Parse a byte array into a PopupMenuItem
/// </summary>
/// <param name="data">Data to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>A filled PopupMenuItem on success, null on error</returns>
public static PopupMenuItem ParsePopupMenuItem(this byte[] data, ref int offset)
{
var obj = new PopupMenuItem();
obj.PopupItemType = (MenuFlags)data.ReadUInt32LittleEndian(ref offset);
obj.PopupState = (MenuFlags)data.ReadUInt32LittleEndian(ref offset);
obj.PopupID = data.ReadUInt32LittleEndian(ref offset);
obj.PopupResInfo = (MenuFlags)data.ReadUInt32LittleEndian(ref offset);
obj.PopupMenuText = data.ReadNullTerminatedUnicodeString(ref offset);
return obj;
}
/// <summary>
/// Parse a byte array into a ResourceHeader
/// </summary>
/// <param name="data">Data to parse</param>
/// <param name="offset">Offset into the byte array</param>
/// <returns>A filled ResourceHeader on success, null on error</returns>
public static ResourceHeader ParseResourceHeader(this byte[] data, ref int offset)
{
// Read in the table
var obj = new ResourceHeader();
obj.DataSize = data.ReadUInt32LittleEndian(ref offset);
obj.HeaderSize = data.ReadUInt32LittleEndian(ref offset);
obj.ResourceType = (ResourceType)data.ReadUInt32LittleEndian(ref offset); // TODO: Could be a string too
obj.Name = data.ReadUInt32LittleEndian(ref offset); // TODO: Could be a string too
obj.DataVersion = data.ReadUInt32LittleEndian(ref offset);
obj.MemoryFlags = (MemoryFlags)data.ReadUInt16LittleEndian(ref offset);
obj.LanguageId = data.ReadUInt16LittleEndian(ref offset);
obj.Version = data.ReadUInt32LittleEndian(ref offset);
obj.Characteristics = data.ReadUInt32LittleEndian(ref offset);
return obj;
}
2023-09-08 22:55:47 -04:00
#endregion
2025-07-24 10:55:52 -04:00
#region Helpers
/// <summary>
/// Align the array position to a byte-size boundary
/// </summary>
/// <param name="input">Input array to try aligning</param>
/// <param name="offset">Offset into the byte array</param>
/// <param name="alignment">Number of bytes to align on</param>
2025-07-24 10:56:31 -04:00
/// <returns>True if the array could be aligned, false otherwise</returns>
private static bool AlignToBoundary(this byte[]? input, ref int offset, byte alignment)
2025-07-24 10:55:52 -04:00
{
// If the array is invalid
if (input == null || input.Length == 0)
return false;
// If already at the end of the array
if (offset >= input.Length)
return false;
// Align the stream position
while (offset % alignment != 0 && offset < input.Length)
{
_ = input.ReadByteValue(ref offset);
}
// Return if the alignment completed
return offset % alignment == 0;
}
#endregion
2023-09-08 22:55:47 -04:00
}
}