mirror of
https://github.com/SabreTools/BinaryObjectScanner.git
synced 2026-09-22 06:45:03 +00:00
Add PE menu resource reading and writing
This commit is contained in:
@@ -465,7 +465,7 @@ namespace BurnOutSharp.Builder
|
||||
/// <summary>
|
||||
/// Read resource data as a dialog box
|
||||
/// </summary>
|
||||
/// <param name="entry">Resource data entry to parse into a font group</param>
|
||||
/// <param name="entry">Resource data entry to parse into a dialog box</param>
|
||||
/// <returns>A filled dialog box on success, null on error</returns>
|
||||
public static Models.PortableExecutable.DialogBoxResource AsDialogBox(this Models.PortableExecutable.ResourceDataEntry entry)
|
||||
{
|
||||
@@ -1004,6 +1004,130 @@ namespace BurnOutSharp.Builder
|
||||
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>
|
||||
public static Models.PortableExecutable.MenuResource AsMenu(this Models.PortableExecutable.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 menuResource = new Models.PortableExecutable.MenuResource();
|
||||
|
||||
// Try to read the version for an extended header
|
||||
int versionOffset = 0;
|
||||
int possibleVersion = entry.Data.ReadUInt16(ref versionOffset);
|
||||
if (possibleVersion == 0x0001)
|
||||
{
|
||||
#region Extended menu header
|
||||
|
||||
var menuHeaderExtended = new Models.PortableExecutable.MenuHeaderExtended();
|
||||
|
||||
menuHeaderExtended.Version = entry.Data.ReadUInt16(ref offset);
|
||||
menuHeaderExtended.Offset = entry.Data.ReadUInt16(ref offset);
|
||||
menuHeaderExtended.HelpID = entry.Data.ReadUInt32(ref offset);
|
||||
|
||||
menuResource.ExtendedMenuHeader = menuHeaderExtended;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extended dialog item templates
|
||||
|
||||
var extendedMenuItems = new List<Models.PortableExecutable.MenuItemExtended>();
|
||||
|
||||
if (offset != 0)
|
||||
{
|
||||
offset = menuHeaderExtended.Offset;
|
||||
|
||||
while (offset < entry.Data.Length)
|
||||
{
|
||||
var extendedMenuItem = new Models.PortableExecutable.MenuItemExtended();
|
||||
|
||||
extendedMenuItem.ItemType = (Models.PortableExecutable.MenuFlags)entry.Data.ReadUInt32(ref offset);
|
||||
extendedMenuItem.State = (Models.PortableExecutable.MenuFlags)entry.Data.ReadUInt32(ref offset);
|
||||
extendedMenuItem.ID = entry.Data.ReadUInt32(ref offset);
|
||||
extendedMenuItem.Flags = (Models.PortableExecutable.MenuFlags)entry.Data.ReadUInt32(ref offset);
|
||||
extendedMenuItem.MenuText = entry.Data.ReadString(ref offset, Encoding.Unicode);
|
||||
|
||||
// Align to the DWORD boundary if we're not at the end
|
||||
if (offset != entry.Data.Length)
|
||||
{
|
||||
while ((offset % 4) != 0)
|
||||
_ = entry.Data.ReadByte(ref offset);
|
||||
}
|
||||
|
||||
extendedMenuItems.Add(extendedMenuItem);
|
||||
}
|
||||
}
|
||||
|
||||
menuResource.ExtendedMenuItems = extendedMenuItems.ToArray();
|
||||
|
||||
#endregion
|
||||
}
|
||||
else
|
||||
{
|
||||
#region Menu header
|
||||
|
||||
var menuHeader = new Models.PortableExecutable.MenuHeader();
|
||||
|
||||
menuHeader.Version = entry.Data.ReadUInt16(ref offset);
|
||||
menuHeader.HeaderSize = entry.Data.ReadUInt16(ref offset);
|
||||
|
||||
menuResource.MenuHeader = menuHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Menu items
|
||||
|
||||
var menuItems = new List<Models.PortableExecutable.MenuItem>();
|
||||
|
||||
while (offset < entry.Data.Length)
|
||||
{
|
||||
var menuItem = new Models.PortableExecutable.MenuItem();
|
||||
|
||||
// Determine if this is a popup
|
||||
int flagsOffset = offset;
|
||||
var initialFlags = (Models.PortableExecutable.MenuFlags)entry.Data.ReadUInt16(ref flagsOffset);
|
||||
if (initialFlags.HasFlag(Models.PortableExecutable.MenuFlags.MF_POPUP))
|
||||
{
|
||||
menuItem.PopupItemType = (Models.PortableExecutable.MenuFlags)entry.Data.ReadUInt32(ref offset);
|
||||
menuItem.PopupState = (Models.PortableExecutable.MenuFlags)entry.Data.ReadUInt32(ref offset);
|
||||
menuItem.PopupID = entry.Data.ReadUInt32(ref offset);
|
||||
menuItem.PopupResInfo = (Models.PortableExecutable.MenuFlags)entry.Data.ReadUInt32(ref offset);
|
||||
menuItem.PopupMenuText = entry.Data.ReadString(ref offset, Encoding.Unicode);
|
||||
}
|
||||
else
|
||||
{
|
||||
menuItem.NormalResInfo = (Models.PortableExecutable.MenuFlags)entry.Data.ReadUInt16(ref offset);
|
||||
menuItem.NormalMenuText = entry.Data.ReadString(ref offset, Encoding.Unicode);
|
||||
}
|
||||
|
||||
// Align to the DWORD boundary if we're not at the end
|
||||
if (offset != entry.Data.Length)
|
||||
{
|
||||
while ((offset % 4) != 0)
|
||||
_ = entry.Data.ReadByte(ref offset);
|
||||
}
|
||||
|
||||
menuItems.Add(menuItem);
|
||||
}
|
||||
|
||||
menuResource.MenuItems = menuItems.ToArray();
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
return menuResource;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read resource data as a string table resource
|
||||
/// </summary>
|
||||
|
||||
@@ -1036,22 +1036,6 @@ namespace BurnOutSharp.Models.PortableExecutable
|
||||
IMPORT_NAME_UNDECORATE = 3,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum MemoryFlags : ushort
|
||||
{
|
||||
// TODO: Validate the ~ statements
|
||||
MOVEABLE = 0x0010,
|
||||
FIXED = 0xFFEF, // ~MOVEABLE
|
||||
|
||||
PURE = 0x0020,
|
||||
IMPURE = 0xFFDF, // ~PURE
|
||||
|
||||
PRELOAD = 0x0040,
|
||||
LOADONCALL = 0xFFBF, // ~PRELOAD
|
||||
|
||||
DISCARDABLE = 0x1000,
|
||||
}
|
||||
|
||||
public enum MachineType : ushort
|
||||
{
|
||||
/// <summary>
|
||||
@@ -1190,6 +1174,83 @@ namespace BurnOutSharp.Models.PortableExecutable
|
||||
IMAGE_FILE_MACHINE_WCEMIPSV2 = 0x0169,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum MemoryFlags : ushort
|
||||
{
|
||||
// TODO: Validate the ~ statements
|
||||
MOVEABLE = 0x0010,
|
||||
FIXED = 0xFFEF, // ~MOVEABLE
|
||||
|
||||
PURE = 0x0020,
|
||||
IMPURE = 0xFFDF, // ~PURE
|
||||
|
||||
PRELOAD = 0x0040,
|
||||
LOADONCALL = 0xFFBF, // ~PRELOAD
|
||||
|
||||
DISCARDABLE = 0x1000,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum MenuFlags : uint
|
||||
{
|
||||
MF_INSERT = 0x00000000,
|
||||
MF_CHANGE = 0x00000080,
|
||||
MF_APPEND = 0x00000100,
|
||||
MF_DELETE = 0x00000200,
|
||||
MF_REMOVE = 0x00001000,
|
||||
|
||||
MF_BYCOMMAND = 0x00000000,
|
||||
MF_BYPOSITION = 0x00000400,
|
||||
|
||||
MF_SEPARATOR = 0x00000800,
|
||||
|
||||
MF_ENABLED = 0x00000000,
|
||||
MF_GRAYED = 0x00000001,
|
||||
MF_DISABLED = 0x00000002,
|
||||
|
||||
MF_UNCHECKED = 0x00000000,
|
||||
MF_CHECKED = 0x00000008,
|
||||
MF_USECHECKBITMAPS = 0x00000200,
|
||||
|
||||
MF_STRING = 0x00000000,
|
||||
MF_BITMAP = 0x00000004,
|
||||
MF_OWNERDRAW = 0x00000100,
|
||||
|
||||
MF_POPUP = 0x00000010,
|
||||
MF_MENUBARBREAK = 0x00000020,
|
||||
MF_MENUBREAK = 0x00000040,
|
||||
|
||||
MF_UNHILITE = 0x00000000,
|
||||
MF_HILITE = 0x00000080,
|
||||
|
||||
MF_DEFAULT = 0x00001000,
|
||||
MF_SYSMENU = 0x00002000,
|
||||
MF_HELP = 0x00004000,
|
||||
MF_RIGHTJUSTIFY = 0x00004000,
|
||||
|
||||
MF_MOUSESELECT = 0x00008000,
|
||||
MF_END = 0x00000080,
|
||||
|
||||
MFT_STRING = MF_STRING,
|
||||
MFT_BITMAP = MF_BITMAP,
|
||||
MFT_MENUBARBREAK = MF_MENUBARBREAK,
|
||||
MFT_MENUBREAK = MF_MENUBREAK,
|
||||
MFT_OWNERDRAW = MF_OWNERDRAW,
|
||||
MFT_RADIOCHECK = 0x00000200,
|
||||
MFT_SEPARATOR = MF_SEPARATOR,
|
||||
MFT_RIGHTORDER = 0x00002000,
|
||||
MFT_RIGHTJUSTIFY = MF_RIGHTJUSTIFY,
|
||||
|
||||
MFS_GRAYED = 0x00000003,
|
||||
MFS_DISABLED = MFS_GRAYED,
|
||||
MFS_CHECKED = MF_CHECKED,
|
||||
MFS_HILITE = MF_HILITE,
|
||||
MFS_ENABLED = MF_ENABLED,
|
||||
MFS_UNCHECKED = MF_UNCHECKED,
|
||||
MFS_UNHILITE = MF_UNHILITE,
|
||||
MFS_DEFAULT = MF_DEFAULT,
|
||||
}
|
||||
|
||||
public enum OptionalHeaderMagicNumber : ushort
|
||||
{
|
||||
ROMImage = 0x0107,
|
||||
|
||||
25
BurnOutSharp.Models/PortableExecutable/MenuHeader.cs
Normal file
25
BurnOutSharp.Models/PortableExecutable/MenuHeader.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BurnOutSharp.Models.PortableExecutable
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains version information for the menu resource. The structure definition provided
|
||||
/// here is for explanation only; it is not present in any standard header file.
|
||||
/// </summary>
|
||||
/// <see href="https://learn.microsoft.com/en-us/windows/win32/menurc/menuheader"/>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class MenuHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// The version number of the menu template. This member must be equal to zero to indicate
|
||||
/// that this is an RT_MENU created with a standard menu template.
|
||||
/// </summary>
|
||||
public ushort Version;
|
||||
|
||||
/// <summary>
|
||||
/// The size of the menu template header. This value is zero for menus you create with a
|
||||
/// standard menu template.
|
||||
/// </summary>
|
||||
public ushort HeaderSize;
|
||||
}
|
||||
}
|
||||
30
BurnOutSharp.Models/PortableExecutable/MenuHeaderExtended.cs
Normal file
30
BurnOutSharp.Models/PortableExecutable/MenuHeaderExtended.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BurnOutSharp.Models.PortableExecutable
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the header for an extended menu template. This structure definition is for
|
||||
/// explanation only; it is not present in any standard header file.
|
||||
/// </summary>
|
||||
/// <see href="https://learn.microsoft.com/en-us/windows/win32/menurc/menuex-template-header"/>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class MenuHeaderExtended
|
||||
{
|
||||
/// <summary>
|
||||
/// The template version number. This member must be 1 for extended menu templates.
|
||||
/// </summary>
|
||||
public ushort Version;
|
||||
|
||||
/// <summary>
|
||||
/// The offset to the first MENUEX_TEMPLATE_ITEM structure, relative to the end of
|
||||
/// this structure member. If the first item definition immediately follows the
|
||||
/// dwHelpId member, this member should be 4.
|
||||
/// </summary>
|
||||
public ushort Offset;
|
||||
|
||||
/// <summary>
|
||||
/// The help identifier of menu bar.
|
||||
/// </summary>
|
||||
public uint HelpID;
|
||||
}
|
||||
}
|
||||
62
BurnOutSharp.Models/PortableExecutable/MenuItem.cs
Normal file
62
BurnOutSharp.Models/PortableExecutable/MenuItem.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
namespace BurnOutSharp.Models.PortableExecutable
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information about each item in a menu resource that does not open a menu
|
||||
/// or a submenu. The structure definition provided here is for explanation only; it
|
||||
/// is not present in any standard header file.
|
||||
///
|
||||
/// Contains information about the menu items in a menu resource that open a menu
|
||||
/// or a submenu. The structure definition provided here is for explanation only;
|
||||
/// it is not present in any standard header file.
|
||||
/// </summary>
|
||||
/// <see href="https://learn.microsoft.com/en-us/windows/win32/menurc/normalmenuitem"/>
|
||||
/// <see href="https://learn.microsoft.com/en-us/windows/win32/menurc/popupmenuitem"/>
|
||||
public class MenuItem
|
||||
{
|
||||
#region NORMALMENUITEM
|
||||
|
||||
/// <summary>
|
||||
/// The type of menu item.
|
||||
/// </summary>
|
||||
public MenuFlags NormalResInfo;
|
||||
|
||||
/// <summary>
|
||||
/// A null-terminated Unicode string that contains the text for this menu item.
|
||||
/// There is no fixed limit on the size of this string.
|
||||
/// </summary>
|
||||
public string NormalMenuText;
|
||||
|
||||
#endregion
|
||||
|
||||
#region POPUPMENUITEM
|
||||
|
||||
/// <summary>
|
||||
/// Describes the menu item.
|
||||
/// </summary>
|
||||
public MenuFlags PopupItemType;
|
||||
|
||||
/// <summary>
|
||||
/// Describes the menu item.
|
||||
/// </summary>
|
||||
public MenuFlags PopupState;
|
||||
|
||||
/// <summary>
|
||||
/// A numeric expression that identifies the menu item that is passed in the
|
||||
/// WM_COMMAND message.
|
||||
/// </summary>
|
||||
public uint PopupID;
|
||||
|
||||
/// <summary>
|
||||
/// A set of bit flags that specify the type of menu item.
|
||||
/// </summary>
|
||||
public MenuFlags PopupResInfo;
|
||||
|
||||
/// <summary>
|
||||
/// A null-terminated Unicode string that contains the text for this menu item.
|
||||
/// There is no fixed limit on the size of this string.
|
||||
/// </summary>
|
||||
public string PopupMenuText;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
37
BurnOutSharp.Models/PortableExecutable/MenuItemExtended.cs
Normal file
37
BurnOutSharp.Models/PortableExecutable/MenuItemExtended.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
namespace BurnOutSharp.Models.PortableExecutable
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a menu item in an extended menu template. This structure definition is for
|
||||
/// explanation only; it is not present in any standard header file.
|
||||
/// </summary>
|
||||
/// <see href="https://learn.microsoft.com/en-us/windows/win32/menurc/menuex-template-item"/>
|
||||
public class MenuItemExtended
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the menu item.
|
||||
/// </summary>
|
||||
public MenuFlags ItemType;
|
||||
|
||||
/// <summary>
|
||||
/// Describes the menu item.
|
||||
/// </summary>
|
||||
public MenuFlags State;
|
||||
|
||||
/// <summary>
|
||||
/// A numeric expression that identifies the menu item that is passed in the
|
||||
/// WM_COMMAND message.
|
||||
/// </summary>
|
||||
public uint ID;
|
||||
|
||||
/// <summary>
|
||||
/// A set of bit flags that specify the type of menu item.
|
||||
/// </summary>
|
||||
public MenuFlags Flags;
|
||||
|
||||
/// <summary>
|
||||
/// A null-terminated Unicode string that contains the text for this menu item.
|
||||
/// There is no fixed limit on the size of this string.
|
||||
/// </summary>
|
||||
public string MenuText;
|
||||
}
|
||||
}
|
||||
40
BurnOutSharp.Models/PortableExecutable/MenuResource.cs
Normal file
40
BurnOutSharp.Models/PortableExecutable/MenuResource.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
namespace BurnOutSharp.Models.PortableExecutable
|
||||
{
|
||||
/// <summary>
|
||||
/// A menu resource consists of a MENUHEADER structure followed by one or more
|
||||
/// NORMALMENUITEM or POPUPMENUITEM structures, one for each menu item in the menu
|
||||
/// template. The MENUEX_TEMPLATE_HEADER and the MENUEX_TEMPLATE_ITEM structures
|
||||
/// describe the format of extended menu resources.
|
||||
/// </summary>
|
||||
/// <see href="https://learn.microsoft.com/en-us/windows/win32/menurc/resource-file-formats"/>
|
||||
public class MenuResource
|
||||
{
|
||||
#region Menu header
|
||||
|
||||
/// <summary>
|
||||
/// Menu header structure
|
||||
/// </summary>
|
||||
public MenuHeader MenuHeader;
|
||||
|
||||
/// <summary>
|
||||
/// Menu extended header structure
|
||||
/// </summary>
|
||||
public MenuHeaderExtended ExtendedMenuHeader;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Menu items
|
||||
|
||||
/// <summary>
|
||||
/// Menu items
|
||||
/// </summary>
|
||||
public MenuItem[] MenuItems;
|
||||
|
||||
/// <summary>
|
||||
/// Extended menu items
|
||||
/// </summary>
|
||||
public MenuItemExtended[] ExtendedMenuItems;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1187,7 +1187,82 @@ namespace ExecutableTest
|
||||
Console.WriteLine($"{padding}Hardware-dependent icon resource found, not parsed yet");
|
||||
break;
|
||||
case BurnOutSharp.Models.PortableExecutable.ResourceType.RT_MENU:
|
||||
Console.WriteLine($"{padding}Menu resource found, not parsed yet");
|
||||
var menu = entry.AsMenu();
|
||||
if (menu == null)
|
||||
{
|
||||
Console.WriteLine($"{padding}Menu resource found, but malformed");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (menu.MenuHeader != null)
|
||||
{
|
||||
Console.WriteLine($"{padding}Version: {menu.MenuHeader.Version}");
|
||||
Console.WriteLine($"{padding}Header size: {menu.MenuHeader.HeaderSize}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"{padding}Menu items");
|
||||
Console.WriteLine($"{padding}-------------------------");
|
||||
if (menu.MenuItems == null
|
||||
|| menu.MenuItems.Length == 0)
|
||||
{
|
||||
Console.WriteLine($"{padding}No menu items");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < menu.MenuItems.Length; i++)
|
||||
{
|
||||
var menuItem = menu.MenuItems[i];
|
||||
|
||||
Console.WriteLine($"{padding}Menu item {i}");
|
||||
if (menuItem.NormalMenuText != null)
|
||||
{
|
||||
Console.WriteLine($"{padding} Resource info: {menuItem.NormalResInfo}");
|
||||
Console.WriteLine($"{padding} Menu text: {menuItem.NormalMenuText}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"{padding} Item type: {menuItem.PopupItemType}");
|
||||
Console.WriteLine($"{padding} State: {menuItem.PopupState}");
|
||||
Console.WriteLine($"{padding} ID: {menuItem.PopupID}");
|
||||
Console.WriteLine($"{padding} Resource info: {menuItem.PopupResInfo}");
|
||||
Console.WriteLine($"{padding} Menu text: {menuItem.PopupMenuText}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (menu.ExtendedMenuHeader != null)
|
||||
{
|
||||
Console.WriteLine($"{padding}Version: {menu.ExtendedMenuHeader.Version}");
|
||||
Console.WriteLine($"{padding}Offset: {menu.ExtendedMenuHeader.Offset}");
|
||||
Console.WriteLine($"{padding}Help ID: {menu.ExtendedMenuHeader.HelpID}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"{padding}Menu items");
|
||||
Console.WriteLine($"{padding}-------------------------");
|
||||
if (menu.ExtendedMenuHeader.Offset == 0
|
||||
|| menu.ExtendedMenuItems == null
|
||||
|| menu.ExtendedMenuItems.Length == 0)
|
||||
{
|
||||
Console.WriteLine($"{padding}No menu items");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < menu.ExtendedMenuItems.Length; i++)
|
||||
{
|
||||
var menuItem = menu.ExtendedMenuItems[i];
|
||||
|
||||
Console.WriteLine($"{padding}Dialog item template {i}");
|
||||
Console.WriteLine($"{padding} Item type: {menuItem.ItemType}");
|
||||
Console.WriteLine($"{padding} State: {menuItem.State}");
|
||||
Console.WriteLine($"{padding} ID: {menuItem.ID}");
|
||||
Console.WriteLine($"{padding} Flags: {menuItem.Flags}");
|
||||
Console.WriteLine($"{padding} Menu text: {menuItem.MenuText}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"{padding}Menu resource found, but malformed");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case BurnOutSharp.Models.PortableExecutable.ResourceType.RT_DIALOG:
|
||||
var dialogBox = entry.AsDialogBox();
|
||||
|
||||
Reference in New Issue
Block a user