From 939a2bab418729907052d6604a4afee68ec6fb7d Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Thu, 12 Feb 2026 13:44:41 -0500 Subject: [PATCH] Move some extension parsers to more common location --- .../Extensions/PortableExecutable.cs | 745 +--------------- .../Readers/PortableExecutable.cs | 803 ++++++++++++++++++ 2 files changed, 808 insertions(+), 740 deletions(-) diff --git a/SabreTools.Serialization/Extensions/PortableExecutable.cs b/SabreTools.Serialization/Extensions/PortableExecutable.cs index dd71dd66..bd26f5f4 100644 --- a/SabreTools.Serialization/Extensions/PortableExecutable.cs +++ b/SabreTools.Serialization/Extensions/PortableExecutable.cs @@ -163,23 +163,7 @@ namespace SabreTools.Data.Extensions if (entry?.Data is 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 AcceleratorTable(); - table.Entries = new AcceleratorTableEntry[count]; - - // Read in the table - for (int i = 0; i < count; i++) - { - table.Entries[i] = ParseAcceleratorTableEntry(entry.Data, ref offset); - } - - return table; + return Serialization.Readers.PortableExecutable.ParseAcceleratorTable(entry.Data); } /// @@ -215,470 +199,7 @@ namespace SabreTools.Data.Extensions if (entry?.Data is 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) ?? string.Empty; - - // 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) ?? string.Empty; - - // 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) ?? string.Empty; - - // 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) ?? string.Empty; - } - - // 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) ?? string.Empty; - - // 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) ?? string.Empty; - - // 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) ?? string.Empty; - - // 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) ?? string.Empty; - - // 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) ?? string.Empty; - - // 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) ?? string.Empty; - } - - // 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) ?? string.Empty; - - // 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) ?? string.Empty; - - // 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; + return Serialization.Readers.PortableExecutable.ParseDialogBoxResource(entry.Data); } /// @@ -761,89 +282,7 @@ namespace SabreTools.Data.Extensions if (entry?.Data is 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(); - - while (offset < entry.Data.Length) - { - // Determine if this is a popup - int flagsOffset = offset; - var initialFlags = (MenuFlags)entry.Data.ReadUInt16LittleEndian(ref flagsOffset); - - MenuItem? menuItem; -#if NET20 || NET35 - if ((initialFlags & MenuFlags.MF_POPUP) != 0) -#else - if (initialFlags.HasFlag(MenuFlags.MF_POPUP)) -#endif - menuItem = ParsePopupMenuItem(entry.Data, ref offset); - else - menuItem = ParseNormalMenuItem(entry.Data, ref offset); - - // Align to the DWORD boundary if we're not at the end - entry.Data.AlignToBoundary(ref offset, 4); - - if (menuItem is null) - return null; - - menuItems.Add(menuItem); - } - - menuResource.MenuItems = [.. menuItems]; - - #endregion - } - - return menuResource; + return Serialization.Readers.PortableExecutable.ParseMenuResource(entry.Data); } /// @@ -857,59 +296,7 @@ namespace SabreTools.Data.Extensions if (entry?.Data is null) return null; - // Initialize the iterator - int offset = 0; - - // Create the output object - var messageResourceData = new MessageResourceData(); - - // Message resource blocks - messageResourceData.NumberOfBlocks = entry.Data.ReadUInt32LittleEndian(ref offset); - if (messageResourceData.NumberOfBlocks > 0) - { - var messageResourceBlocks = new List(); - - for (int i = 0; i < messageResourceData.NumberOfBlocks; i++) - { - var messageResourceBlock = ParseMessageResourceBlock(entry.Data, ref offset); - messageResourceBlocks.Add(messageResourceBlock); - } - - messageResourceData.Blocks = [.. messageResourceBlocks]; - } - - // Message resource entries - if (messageResourceData.Blocks.Length != 0) - { - var messageResourceEntries = new Dictionary(); - - for (int i = 0; i < messageResourceData.Blocks.Length; i++) - { - var messageResourceBlock = messageResourceData.Blocks[i]; - if (messageResourceBlock is null) - continue; - - offset = (int)messageResourceBlock.OffsetToEntries; - - for (uint j = messageResourceBlock.LowId; j <= messageResourceBlock.HighId; j++) - { - var messageResourceEntry = new MessageResourceEntry(); - - messageResourceEntry.Length = entry.Data.ReadUInt16LittleEndian(ref offset); - messageResourceEntry.Flags = entry.Data.ReadUInt16LittleEndian(ref offset); - - Encoding textEncoding = messageResourceEntry.Flags == 0x0001 ? Encoding.Unicode : Encoding.ASCII; - byte[] textArray = entry.Data.ReadBytes(ref offset, messageResourceEntry.Length - 4); - messageResourceEntry.Text = textEncoding.GetString(textArray); - - messageResourceEntries[j] = messageResourceEntry; - } - } - - messageResourceData.Entries = messageResourceEntries; - } - - return messageResourceData; + return Serialization.Readers.PortableExecutable.ParseMessageResourceData(entry.Data); } /// @@ -1130,24 +517,6 @@ namespace SabreTools.Data.Extensions return versionInfo; } - /// - /// Parse a byte array into a AcceleratorTableEntry - /// - /// Data to parse - /// Offset into the byte array - /// A filled AcceleratorTableEntry on success, null on error - 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; - } - /// /// Parse a byte array into a FixedFileInfo /// @@ -1175,110 +544,6 @@ namespace SabreTools.Data.Extensions return obj; } - /// - /// Parse a byte array into a MenuHeaderExtended - /// - /// Data to parse - /// Offset into the byte array - /// A filled MenuHeaderExtended on success, null on error - 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; - } - - /// - /// Parse a byte array into a MenuItemExtended - /// - /// Data to parse - /// Offset into the byte array - /// A filled MenuItemExtended on success, null on error - 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) ?? string.Empty; - - return obj; - } - - /// - /// Parse a byte array into a MessageResourceBlock - /// - /// Data to parse - /// Offset into the byte array - /// A filled MessageResourceBlock on success, null on error - 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; - } - - /// - /// Parse a byte array into a NormalMenuHeader - /// - /// Data to parse - /// Offset into the byte array - /// A filled NormalMenuHeader on success, null on error - 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; - } - - /// - /// Parse a byte array into a NormalMenuItem - /// - /// Data to parse - /// Offset into the byte array - /// A filled NormalMenuItem on success, null on error - 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) ?? string.Empty; - - return obj; - } - - /// - /// Parse a byte array into a PopupMenuItem - /// - /// Data to parse - /// Offset into the byte array - /// A filled PopupMenuItem on success, null on error - 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) ?? string.Empty; - - return obj; - } - /// /// Parse a byte array into a ResourceHeader /// @@ -1346,7 +611,7 @@ namespace SabreTools.Data.Extensions /// Offset into the byte array /// Number of bytes to align on /// True if the array could be aligned, false otherwise - private static bool AlignToBoundary(this byte[]? input, ref int offset, byte alignment) + internal static bool AlignToBoundary(this byte[]? input, ref int offset, byte alignment) { // If the array is invalid if (input is null || input.Length == 0) diff --git a/SabreTools.Serialization/Readers/PortableExecutable.cs b/SabreTools.Serialization/Readers/PortableExecutable.cs index 84106814..90f5abad 100644 --- a/SabreTools.Serialization/Readers/PortableExecutable.cs +++ b/SabreTools.Serialization/Readers/PortableExecutable.cs @@ -475,6 +475,52 @@ namespace SabreTools.Serialization.Readers } } + /// + /// Parse a byte array into an AcceleratorTable + /// + /// Byte array to parse + /// Filled AcceleratorTable on success, null on error + public static Data.Models.PortableExecutable.Resource.Entries.AcceleratorTable? ParseAcceleratorTable(byte[] data) + { + // If we have data that's invalid for this resource type, we can't do anything + if (data.Length % 8 != 0) + return null; + + // Get the number of entries + int count = data.Length / 8; + + // Create the output object + var obj = new Data.Models.PortableExecutable.Resource.Entries.AcceleratorTable(); + obj.Entries = new Data.Models.PortableExecutable.Resource.Entries.AcceleratorTableEntry[count]; + + // Read in the table + int offset = 0; + for (int i = 0; i < count; i++) + { + obj.Entries[i] = ParseAcceleratorTableEntry(data, ref offset); + } + + return obj; + } + + /// + /// Parse a byte array into a AcceleratorTableEntry + /// + /// Byte array to parse + /// Offset into the byte array + /// A filled AcceleratorTableEntry on success, null on error + public static Data.Models.PortableExecutable.Resource.Entries.AcceleratorTableEntry ParseAcceleratorTableEntry(byte[] data, ref int offset) + { + var obj = new Data.Models.PortableExecutable.Resource.Entries.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; + } + /// /// Parse a byte array into an attribute certificate table /// @@ -740,6 +786,507 @@ namespace SabreTools.Serialization.Readers return obj; } + /// + /// Parse a byte array into a DialogBoxResource + /// + /// Byte array to parse + /// A filled DialogBoxResource on success, null on error + public static Data.Models.PortableExecutable.Resource.Entries.DialogBoxResource? ParseDialogBoxResource(byte[] data) + { + // Initialize the iterator + int offset = 0; + + // Create the output object + var obj = new Data.Models.PortableExecutable.Resource.Entries.DialogBoxResource(); + + // Try to read the signature for an extended dialog box template + int signatureOffset = sizeof(ushort); + int possibleSignature = data.ReadUInt16LittleEndian(ref signatureOffset); + if (possibleSignature == 0xFFFF) + { + obj.ExtendedDialogTemplate = ParseDialogTemplateExtended(data, ref offset); + + var dialogItemExtendedTemplates = new List(); + for (int i = 0; i < obj.ExtendedDialogTemplate.DialogItems; i++) + { + var dialogItemTemplate = ParseDialogItemTemplateExtended(data, ref offset); + dialogItemExtendedTemplates.Add(dialogItemTemplate); + + // If we have an invalid item count + if (offset >= data.Length) + break; + } + + obj.ExtendedDialogItemTemplates = [.. dialogItemExtendedTemplates]; + } + else + { + obj.DialogTemplate = ParseDialogTemplate(data, ref offset); + + var dialogItemTemplates = new List(); + for (int i = 0; i < obj.DialogTemplate.ItemCount; i++) + { + var dialogItemTemplate = ParseDialogItemTemplate(data, ref offset); + dialogItemTemplates.Add(dialogItemTemplate); + + // If we have an invalid item count + if (offset >= data.Length) + break; + } + + obj.DialogItemTemplates = [.. dialogItemTemplates]; + } + + return obj; + } + + /// + /// Parse a byte array into a DialogItemTemplate + /// + /// Byte array to parse + /// Offset into the byte array + /// Filled DialogItemTemplate on success, null on error + public static Data.Models.PortableExecutable.Resource.Entries.DialogItemTemplate ParseDialogItemTemplate(byte[] data, ref int offset) + { + var obj = new Data.Models.PortableExecutable.Resource.Entries.DialogItemTemplate(); + + obj.Style = (WindowStyles)data.ReadUInt32LittleEndian(ref offset); + obj.ExtendedStyle = (ExtendedWindowStyles)data.ReadUInt32LittleEndian(ref offset); + obj.PositionX = data.ReadInt16LittleEndian(ref offset); + obj.PositionY = data.ReadInt16LittleEndian(ref offset); + obj.WidthX = data.ReadInt16LittleEndian(ref offset); + obj.HeightY = data.ReadInt16LittleEndian(ref offset); + obj.ID = data.ReadUInt16LittleEndian(ref offset); + + #region Class resource + + int currentOffset = offset; + ushort itemClassResourceIdentifier = data.ReadUInt16LittleEndian(ref offset); + offset = currentOffset; + + // 0xFFFF means ordinal only + if (itemClassResourceIdentifier == 0xFFFF) + { + // Increment the pointer + _ = data.ReadUInt16LittleEndian(ref offset); + + // Read the ordinal + obj.ClassResourceOrdinal = (DialogItemTemplateOrdinal)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 + obj.ClassResource = data.ReadNullTerminatedUnicodeString(ref offset) ?? string.Empty; + + // Align to the WORD boundary if we're not at the end + data.AlignToBoundary(ref offset, 2); + } + + #endregion + + #region Title resource + + currentOffset = offset; + ushort itemTitleResourceIdentifier = data.ReadUInt16LittleEndian(ref offset); + offset = currentOffset; + + // 0xFFFF means ordinal only + if (itemTitleResourceIdentifier == 0xFFFF) + { + // Increment the pointer + _ = data.ReadUInt16LittleEndian(ref offset); + + // Read the ordinal + obj.TitleResourceOrdinal = data.ReadUInt16LittleEndian(ref offset); + } + else + { + // Read the title resource as a string + obj.TitleResource = data.ReadNullTerminatedUnicodeString(ref offset) ?? string.Empty; + + // Align to the WORD boundary if we're not at the end + data.AlignToBoundary(ref offset, 2); + } + + #endregion + + #region Creation data + + obj.CreationDataSize = data.ReadUInt16LittleEndian(ref offset); + if (obj.CreationDataSize != 0 && obj.CreationDataSize + offset < data.Length) + obj.CreationData = data.ReadBytes(ref offset, obj.CreationDataSize); + + #endregion + + // Align to the DWORD boundary if we're not at the end + data.AlignToBoundary(ref offset, 4); + + return obj; + } + + /// + /// Parse a byte array into a DialogItemTemplateExtended + /// + /// Byte array to parse + /// Offset into the byte array + /// Filled DialogItemTemplateExtended on success, null on error + public static Data.Models.PortableExecutable.Resource.Entries.DialogItemTemplateExtended ParseDialogItemTemplateExtended(byte[] data, ref int offset) + { + var obj = new Data.Models.PortableExecutable.Resource.Entries.DialogItemTemplateExtended(); + + obj.HelpID = data.ReadUInt32LittleEndian(ref offset); + obj.ExtendedStyle = (ExtendedWindowStyles)data.ReadUInt32LittleEndian(ref offset); + obj.Style = (WindowStyles)data.ReadUInt32LittleEndian(ref offset); + obj.PositionX = data.ReadInt16LittleEndian(ref offset); + obj.PositionY = data.ReadInt16LittleEndian(ref offset); + obj.WidthX = data.ReadInt16LittleEndian(ref offset); + obj.HeightY = data.ReadInt16LittleEndian(ref offset); + obj.ID = data.ReadUInt32LittleEndian(ref offset); + + #region Class resource + + int currentOffset = offset; + ushort itemClassResourceIdentifier = data.ReadUInt16LittleEndian(ref offset); + offset = currentOffset; + + // 0xFFFF means ordinal only + if (itemClassResourceIdentifier == 0xFFFF) + { + // Increment the pointer + _ = data.ReadUInt16LittleEndian(ref offset); + + // Read the ordinal + obj.ClassResourceOrdinal = (DialogItemTemplateOrdinal)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 + obj.ClassResource = data.ReadNullTerminatedUnicodeString(ref offset) ?? string.Empty; + + // Align to the WORD boundary if we're not at the end + data.AlignToBoundary(ref offset, 2); + } + + #endregion + + #region Title resource + + currentOffset = offset; + ushort itemTitleResourceIdentifier = data.ReadUInt16LittleEndian(ref offset); + offset = currentOffset; + + // 0xFFFF means ordinal only + if (itemTitleResourceIdentifier == 0xFFFF) + { + // Increment the pointer + _ = data.ReadUInt16LittleEndian(ref offset); + + // Read the ordinal + obj.TitleResourceOrdinal = data.ReadUInt16LittleEndian(ref offset); + } + else + { + // Read the title resource as a string + obj.TitleResource = data.ReadNullTerminatedUnicodeString(ref offset) ?? string.Empty; + + // Align to the WORD boundary if we're not at the end + data.AlignToBoundary(ref offset, 2); + } + + #endregion + + #region Creation data + + obj.CreationDataSize = data.ReadUInt16LittleEndian(ref offset); + if (obj.CreationDataSize != 0) + obj.CreationData = data.ReadBytes(ref offset, obj.CreationDataSize); + + #endregion + + // Align to the DWORD boundary if we're not at the end + data.AlignToBoundary(ref offset, 4); + + return obj; + } + + /// + /// Parse a byte array into a DialogTemplate + /// + /// Byte array to parse + /// Offset into the byte array + /// Filled DialogTemplate on success, null on error + public static Data.Models.PortableExecutable.Resource.Entries.DialogTemplate ParseDialogTemplate(byte[] data, ref int offset) + { + var obj = new Data.Models.PortableExecutable.Resource.Entries.DialogTemplate(); + + obj.Style = (WindowStyles)data.ReadUInt32LittleEndian(ref offset); + obj.ExtendedStyle = (ExtendedWindowStyles)data.ReadUInt32LittleEndian(ref offset); + obj.ItemCount = data.ReadUInt16LittleEndian(ref offset); + obj.PositionX = data.ReadInt16LittleEndian(ref offset); + obj.PositionY = data.ReadInt16LittleEndian(ref offset); + obj.WidthX = data.ReadInt16LittleEndian(ref offset); + obj.HeightY = data.ReadInt16LittleEndian(ref offset); + + #region Menu resource + + int currentOffset = offset; + ushort menuResourceIdentifier = 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 + obj.MenuResource = data.ReadNullTerminatedUnicodeString(ref offset) ?? string.Empty; + + // Align to the WORD boundary if we're not at the end + data.AlignToBoundary(ref offset, 2); + + // Read the ordinal if we have the flag set + if (menuResourceHasOrdinal) + obj.MenuResourceOrdinal = data.ReadUInt16LittleEndian(ref offset); + } + + #endregion + + #region Class resource + + currentOffset = offset; + ushort classResourceIdentifier; + if (offset >= data.Length) + classResourceIdentifier = 0x0000; + else + classResourceIdentifier = 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 + obj.ClassResource = data.ReadNullTerminatedUnicodeString(ref offset) ?? string.Empty; + + // Align to the WORD boundary if we're not at the end + data.AlignToBoundary(ref offset, 2); + + // Read the ordinal if we have the flag set + if (classResourcehasOrdinal) + obj.ClassResourceOrdinal = data.ReadUInt16LittleEndian(ref offset); + } + + #endregion + + #region Title resource + + currentOffset = offset; + ushort titleResourceIdentifier; + if (offset >= data.Length) + titleResourceIdentifier = 0x0000; + else + titleResourceIdentifier = 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 + obj.TitleResource = data.ReadNullTerminatedUnicodeString(ref offset) ?? string.Empty; + + // Align to the WORD boundary if we're not at the end + 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 ((obj.Style & WindowStyles.DS_SETFONT) != 0) +#else + if (obj.Style.HasFlag(WindowStyles.DS_SETFONT)) +#endif + { + obj.PointSizeValue = data.ReadUInt16LittleEndian(ref offset); + + // Read the font name as a string + obj.Typeface = data.ReadNullTerminatedUnicodeString(ref offset) ?? string.Empty; + } + + // Align to the DWORD boundary if we're not at the end + data.AlignToBoundary(ref offset, 4); + + #endregion + + return obj; + } + + /// + /// Parse a byte array into a DialogTemplateExtended + /// + /// Byte array to parse + /// Offset into the byte array + /// Filled DialogTemplateExtended on success, null on error + public static Data.Models.PortableExecutable.Resource.Entries.DialogTemplateExtended ParseDialogTemplateExtended(byte[] data, ref int offset) + { + var obj = new Data.Models.PortableExecutable.Resource.Entries.DialogTemplateExtended(); + + obj.Version = data.ReadUInt16LittleEndian(ref offset); + obj.Signature = data.ReadUInt16LittleEndian(ref offset); + obj.HelpID = data.ReadUInt32LittleEndian(ref offset); + obj.ExtendedStyle = (ExtendedWindowStyles)data.ReadUInt32LittleEndian(ref offset); + obj.Style = (WindowStyles)data.ReadUInt32LittleEndian(ref offset); + obj.DialogItems = data.ReadUInt16LittleEndian(ref offset); + obj.PositionX = data.ReadInt16LittleEndian(ref offset); + obj.PositionY = data.ReadInt16LittleEndian(ref offset); + obj.WidthX = data.ReadInt16LittleEndian(ref offset); + obj.HeightY = data.ReadInt16LittleEndian(ref offset); + + #region Menu resource + + int currentOffset = offset; + ushort menuResourceIdentifier = 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 + obj.MenuResource = data.ReadNullTerminatedUnicodeString(ref offset) ?? string.Empty; + + // Align to the WORD boundary if we're not at the end + data.AlignToBoundary(ref offset, 2); + + // Read the ordinal if we have the flag set + if (menuResourceHasOrdinal) + obj.MenuResourceOrdinal = data.ReadUInt16LittleEndian(ref offset); + } + + #endregion + + #region Class resource + + currentOffset = offset; + ushort classResourceIdentifier = 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 + obj.ClassResource = data.ReadNullTerminatedUnicodeString(ref offset) ?? string.Empty; + + // Align to the WORD boundary if we're not at the end + data.AlignToBoundary(ref offset, 2); + + // Read the ordinal if we have the flag set + if (classResourcehasOrdinal) + obj.ClassResourceOrdinal = data.ReadUInt16LittleEndian(ref offset); + } + + #endregion + + #region Title resource + + currentOffset = offset; + ushort titleResourceIdentifier = 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 + obj.TitleResource = data.ReadNullTerminatedUnicodeString(ref offset) ?? string.Empty; + + // Align to the WORD boundary if we're not at the end + 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 ((obj.Style & WindowStyles.DS_SETFONT) != 0) +#else + if (obj.Style.HasFlag(WindowStyles.DS_SETFONT)) +#endif + { + obj.PointSize = data.ReadUInt16LittleEndian(ref offset); + obj.Weight = data.ReadUInt16LittleEndian(ref offset); + obj.Italic = data.ReadByte(ref offset); + obj.CharSet = data.ReadByte(ref offset); + obj.Typeface = data.ReadNullTerminatedUnicodeString(ref offset) ?? string.Empty; + } + + // Align to the DWORD boundary if we're not at the end + data.AlignToBoundary(ref offset, 4); + + #endregion + + return obj; + } + /// /// Parse a Stream into a ExportAddressTable /// @@ -1280,6 +1827,243 @@ namespace SabreTools.Serialization.Readers return obj; } + /// + /// Parse a byte array into a MenuHeaderExtended + /// + /// Data to parse + /// Offset into the byte array + /// A filled MenuHeaderExtended on success, null on error + public static Data.Models.PortableExecutable.Resource.Entries.MenuHeaderExtended ParseMenuHeaderExtended(byte[] data, ref int offset) + { + var obj = new Data.Models.PortableExecutable.Resource.Entries.MenuHeaderExtended(); + + obj.Version = data.ReadUInt16LittleEndian(ref offset); + obj.Offset = data.ReadUInt16LittleEndian(ref offset); + obj.HelpID = data.ReadUInt32LittleEndian(ref offset); + + return obj; + } + + /// + /// Parse a byte array into a MenuItemExtended + /// + /// Data to parse + /// Offset into the byte array + /// A filled MenuItemExtended on success, null on error + public static Data.Models.PortableExecutable.Resource.Entries.MenuItemExtended ParseMenuItemExtended(byte[] data, ref int offset) + { + var obj = new Data.Models.PortableExecutable.Resource.Entries.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) ?? string.Empty; + + return obj; + } + + /// + /// Parse a byte array into a MenuResource + /// + /// Byte array to parse + /// Filled MenuResource on success, null on error + public static Data.Models.PortableExecutable.Resource.Entries.MenuResource? ParseMenuResource(byte[] data) + { + // Initialize the iterator + int offset = 0; + + // Create the output object + var obj = new Data.Models.PortableExecutable.Resource.Entries.MenuResource(); + + // Try to read the version for an extended header + int versionOffset = 0; + int possibleVersion = data.ReadUInt16LittleEndian(ref versionOffset); + if (possibleVersion == 0x0001) + { + var menuHeaderExtended = ParseMenuHeaderExtended(data, ref offset); + obj.MenuHeader = menuHeaderExtended; + + var extendedMenuItems = new List(); + if (offset != 0) + { + offset = menuHeaderExtended.Offset; + + while (offset < data.Length) + { + var extendedMenuItem = ParseMenuItemExtended(data, ref offset); + extendedMenuItems.Add(extendedMenuItem); + + // Align to the DWORD boundary if we're not at the end + data.AlignToBoundary(ref offset, 4); + } + } + + obj.MenuItems = [.. extendedMenuItems]; + } + else + { + obj.MenuHeader = ParseNormalMenuHeader(data, ref offset); + + #region Menu items + + var menuItems = new List(); + + while (offset < data.Length) + { + // Determine if this is a popup + int flagsOffset = offset; + var initialFlags = (MenuFlags)data.ReadUInt16LittleEndian(ref flagsOffset); + + Data.Models.PortableExecutable.Resource.Entries.MenuItem? menuItem; +#if NET20 || NET35 + if ((initialFlags & MenuFlags.MF_POPUP) != 0) +#else + if (initialFlags.HasFlag(MenuFlags.MF_POPUP)) +#endif + menuItem = ParsePopupMenuItem(data, ref offset); + else + menuItem = ParseNormalMenuItem(data, ref offset); + + // Align to the DWORD boundary if we're not at the end + data.AlignToBoundary(ref offset, 4); + + if (menuItem is null) + return null; + + menuItems.Add(menuItem); + } + + obj.MenuItems = [.. menuItems]; + + #endregion + } + + return obj; + } + + /// + /// Parse a byte array into a MessageResourceBlock + /// + /// Data to parse + /// Offset into the byte array + /// A filled MessageResourceBlock on success, null on error + public static Data.Models.PortableExecutable.Resource.Entries.MessageResourceBlock ParseMessageResourceBlock(byte[] data, ref int offset) + { + var obj = new Data.Models.PortableExecutable.Resource.Entries.MessageResourceBlock(); + + obj.LowId = data.ReadUInt32LittleEndian(ref offset); + obj.HighId = data.ReadUInt32LittleEndian(ref offset); + obj.OffsetToEntries = data.ReadUInt32LittleEndian(ref offset); + + return obj; + } + + /// + /// Read resource data as a MessageResourceData + /// + /// Byte array to parse + /// A filled MessageResourceData on success, null on error + public static Data.Models.PortableExecutable.Resource.Entries.MessageResourceData? ParseMessageResourceData(byte[] data) + { + // Initialize the iterator + int offset = 0; + + // Create the output object + var obj = new Data.Models.PortableExecutable.Resource.Entries.MessageResourceData(); + + // Message resource blocks + obj.NumberOfBlocks = data.ReadUInt32LittleEndian(ref offset); + if (obj.NumberOfBlocks > 0) + { + var messageResourceBlocks = new List(); + + for (int i = 0; i < obj.NumberOfBlocks; i++) + { + var messageResourceBlock = ParseMessageResourceBlock(data, ref offset); + messageResourceBlocks.Add(messageResourceBlock); + } + + obj.Blocks = [.. messageResourceBlocks]; + } + + // Message resource entries + if (obj.Blocks.Length != 0) + { + var messageResourceEntries = new Dictionary(); + + for (int i = 0; i < obj.Blocks.Length; i++) + { + var messageResourceBlock = obj.Blocks[i]; + if (messageResourceBlock is null) + continue; + + offset = (int)messageResourceBlock.OffsetToEntries; + + for (uint j = messageResourceBlock.LowId; j <= messageResourceBlock.HighId; j++) + { + messageResourceEntries[j] = ParseMessageResourceEntry(data, ref offset); + } + } + + obj.Entries = messageResourceEntries; + } + + return obj; + } + + /// + /// Parse a byte array into a MessageResourceEntry + /// + /// Data to parse + /// Offset into the byte array + /// A filled MessageResourceEntry on success, null on error + public static Data.Models.PortableExecutable.Resource.Entries.MessageResourceEntry ParseMessageResourceEntry(byte[] data, ref int offset) + { + var obj = new Data.Models.PortableExecutable.Resource.Entries.MessageResourceEntry(); + + obj.Length = data.ReadUInt16LittleEndian(ref offset); + obj.Flags = data.ReadUInt16LittleEndian(ref offset); + + Encoding textEncoding = obj.Flags == 0x0001 ? Encoding.Unicode : Encoding.ASCII; + byte[] textArray = data.ReadBytes(ref offset, obj.Length - 4); + obj.Text = textEncoding.GetString(textArray); + + return obj; + } + + /// + /// Parse a byte array into a NormalMenuHeader + /// + /// Data to parse + /// Offset into the byte array + /// A filled NormalMenuHeader on success, null on error + public static Data.Models.PortableExecutable.Resource.Entries.NormalMenuHeader ParseNormalMenuHeader(byte[] data, ref int offset) + { + var obj = new Data.Models.PortableExecutable.Resource.Entries.NormalMenuHeader(); + + obj.Version = data.ReadUInt16LittleEndian(ref offset); + obj.HeaderSize = data.ReadUInt16LittleEndian(ref offset); + + return obj; + } + + /// + /// Parse a byte array into a NormalMenuItem + /// + /// Data to parse + /// Offset into the byte array + /// A filled NormalMenuItem on success, null on error + public static Data.Models.PortableExecutable.Resource.Entries.NormalMenuItem ParseNormalMenuItem(byte[] data, ref int offset) + { + var obj = new Data.Models.PortableExecutable.Resource.Entries.NormalMenuItem(); + + obj.NormalResInfo = (MenuFlags)data.ReadUInt32LittleEndian(ref offset); + obj.NormalMenuText = data.ReadNullTerminatedUnicodeString(ref offset) ?? string.Empty; + + return obj; + } + /// /// Parse a Stream into an OptionalHeader /// @@ -1408,6 +2192,25 @@ namespace SabreTools.Serialization.Readers return obj; } + /// + /// Parse a byte array into a PopupMenuItem + /// + /// Data to parse + /// Offset into the byte array + /// A filled PopupMenuItem on success, null on error + public static Data.Models.PortableExecutable.Resource.Entries.PopupMenuItem ParsePopupMenuItem(byte[] data, ref int offset) + { + var obj = new Data.Models.PortableExecutable.Resource.Entries.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) ?? string.Empty; + + return obj; + } + /// /// Parse a Stream into a Relocation ///