diff --git a/llvm/test/tools/llvm-rc/Inputs/menuex.rc b/llvm/test/tools/llvm-rc/Inputs/menuex.rc new file mode 100644 index 0000000000000..766fffe03b87d --- /dev/null +++ b/llvm/test/tools/llvm-rc/Inputs/menuex.rc @@ -0,0 +1,14 @@ +101 MENUEX +BEGIN + POPUP "File", 40182 + BEGIN + MENUITEM "Load", 40011 + MENUITEM "", 0, 0x00000800L + + POPUP "Savestate Slot", 40187 + BEGIN + MENUITEM "&1", 40099 + MENUITEM "&2", 40100 + END + END +END diff --git a/llvm/test/tools/llvm-rc/menuex.test b/llvm/test/tools/llvm-rc/menuex.test new file mode 100644 index 0000000000000..2a51ffd110419 --- /dev/null +++ b/llvm/test/tools/llvm-rc/menuex.test @@ -0,0 +1,25 @@ +; RUN: llvm-rc -no-preprocess /FO %t -- %p/Inputs/menuex.rc +; RUN: llvm-readobj %t | FileCheck %s + +CHECK: Resource type (int): MENU (ID 4) +CHECK-NEXT: Resource name (int): 101 +CHECK-NEXT: Data version: 0 +CHECK-NEXT: Memory flags: 0x1030 +CHECK-NEXT: Language ID: 1033 +CHECK-NEXT: Version (major): 0 +CHECK-NEXT: Version (minor): 0 +CHECK-NEXT: Characteristics: 0 +CHECK-NEXT: Data size: 164 +CHECK-NEXT: Data: ( +CHECK-NEXT: 0000: 01000400 00000000 00000000 00000000 |................| +CHECK-NEXT: 0010: F69C0000 81004600 69006C00 65000000 |......F.i.l.e...| +CHECK-NEXT: 0020: 00000000 00000000 00000000 4B9C0000 |............K...| +CHECK-NEXT: 0030: 00004C00 6F006100 64000000 00080000 |..L.o.a.d.......| +CHECK-NEXT: 0040: 00000000 00000000 00000000 00000000 |................| +CHECK-NEXT: 0050: 00000000 FB9C0000 81005300 61007600 |..........S.a.v.| +CHECK-NEXT: 0060: 65007300 74006100 74006500 20005300 |e.s.t.a.t.e. .S.| +CHECK-NEXT: 0070: 6C006F00 74000000 00000000 00000000 |l.o.t...........| +CHECK-NEXT: 0080: 00000000 A39C0000 00002600 31000000 |..........&.1...| +CHECK-NEXT: 0090: 00000000 00000000 A49C0000 80002600 |..............&.| +CHECK-NEXT: 00A0: 32000000 |2...| +CHECK-NEXT: ) diff --git a/llvm/tools/llvm-rc/ResourceFileWriter.cpp b/llvm/tools/llvm-rc/ResourceFileWriter.cpp index 62eed50976d90..dd9db338ece25 100644 --- a/llvm/tools/llvm-rc/ResourceFileWriter.cpp +++ b/llvm/tools/llvm-rc/ResourceFileWriter.cpp @@ -471,6 +471,10 @@ Error ResourceFileWriter::visitMenuResource(const RCResource *Res) { return writeResource(Res, &ResourceFileWriter::writeMenuBody); } +Error ResourceFileWriter::visitMenuExResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeMenuExBody); +} + Error ResourceFileWriter::visitStringTableResource(const RCResource *Base) { const auto *Res = cast(Base); @@ -1176,6 +1180,7 @@ Error ResourceFileWriter::writeHTMLBody(const RCResource *Base) { Error ResourceFileWriter::writeMenuDefinition( const std::unique_ptr &Def, uint16_t Flags) { + // https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-menuitemtemplate assert(Def); const MenuDefinition *DefPtr = Def.get(); @@ -1202,6 +1207,34 @@ Error ResourceFileWriter::writeMenuDefinition( return writeMenuDefinitionList(PopupPtr->SubItems); } +Error ResourceFileWriter::writeMenuExDefinition( + const std::unique_ptr &Def, uint16_t Flags) { + // https://learn.microsoft.com/en-us/windows/win32/menurc/menuex-template-item + assert(Def); + const MenuDefinition *DefPtr = Def.get(); + + padStream(sizeof(uint32_t)); + if (auto *MenuItemPtr = dyn_cast(DefPtr)) { + writeInt(MenuItemPtr->Type); + writeInt(MenuItemPtr->State); + writeInt(MenuItemPtr->Id); + writeInt(Flags); + padStream(sizeof(uint16_t)); + RETURN_IF_ERROR(writeCString(MenuItemPtr->Name)); + return Error::success(); + } + + auto *PopupPtr = cast(DefPtr); + writeInt(PopupPtr->Type); + writeInt(PopupPtr->State); + writeInt(PopupPtr->Id); + writeInt(Flags); + padStream(sizeof(uint16_t)); + RETURN_IF_ERROR(writeCString(PopupPtr->Name)); + writeInt(PopupPtr->HelpId); + return writeMenuExDefinitionList(PopupPtr->SubItems); +} + Error ResourceFileWriter::writeMenuDefinitionList( const MenuDefinitionList &List) { for (auto &Def : List.Definitions) { @@ -1216,6 +1249,20 @@ Error ResourceFileWriter::writeMenuDefinitionList( return Error::success(); } +Error ResourceFileWriter::writeMenuExDefinitionList( + const MenuDefinitionList &List) { + for (auto &Def : List.Definitions) { + uint16_t Flags = Def->getResFlags(); + // Last element receives an additional 0x80 flag. + const uint16_t LastElementFlag = 0x0080; + if (&Def == &List.Definitions.back()) + Flags |= LastElementFlag; + + RETURN_IF_ERROR(writeMenuExDefinition(Def, Flags)); + } + return Error::success(); +} + Error ResourceFileWriter::writeMenuBody(const RCResource *Base) { // At first, MENUHEADER structure. In fact, these are two WORDs equal to 0. // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648018.aspx @@ -1224,6 +1271,17 @@ Error ResourceFileWriter::writeMenuBody(const RCResource *Base) { return writeMenuDefinitionList(cast(Base)->Elements); } +Error ResourceFileWriter::writeMenuExBody(const RCResource *Base) { + // At first, MENUEX_TEMPLATE_HEADER structure. + // Ref: + // https://learn.microsoft.com/en-us/windows/win32/menurc/menuex-template-header + writeInt(1); + writeInt(4); + writeInt(0); + + return writeMenuExDefinitionList(cast(Base)->Elements); +} + // --- StringTableResource helpers. --- // class BundleResource : public RCResource { diff --git a/llvm/tools/llvm-rc/ResourceFileWriter.h b/llvm/tools/llvm-rc/ResourceFileWriter.h index 7a92f84d5d84f..d809890ee8e82 100644 --- a/llvm/tools/llvm-rc/ResourceFileWriter.h +++ b/llvm/tools/llvm-rc/ResourceFileWriter.h @@ -55,6 +55,7 @@ class ResourceFileWriter : public Visitor { Error visitHTMLResource(const RCResource *) override; Error visitIconResource(const RCResource *) override; Error visitMenuResource(const RCResource *) override; + Error visitMenuExResource(const RCResource *) override; Error visitVersionInfoResource(const RCResource *) override; Error visitStringTableResource(const RCResource *) override; Error visitUserDefinedResource(const RCResource *) override; @@ -150,8 +151,12 @@ class ResourceFileWriter : public Visitor { // MenuResource Error writeMenuDefinition(const std::unique_ptr &, uint16_t Flags); + Error writeMenuExDefinition(const std::unique_ptr &, + uint16_t Flags); Error writeMenuDefinitionList(const MenuDefinitionList &List); + Error writeMenuExDefinitionList(const MenuDefinitionList &List); Error writeMenuBody(const RCResource *); + Error writeMenuExBody(const RCResource *); // StringTableResource Error visitStringTableBundle(const RCResource *); diff --git a/llvm/tools/llvm-rc/ResourceScriptParser.cpp b/llvm/tools/llvm-rc/ResourceScriptParser.cpp index 0037fec4c0247..9e1047448831b 100644 --- a/llvm/tools/llvm-rc/ResourceScriptParser.cpp +++ b/llvm/tools/llvm-rc/ResourceScriptParser.cpp @@ -80,6 +80,8 @@ RCParser::ParseType RCParser::parseSingleResource() { Result = parseIconResource(); else if (TypeToken->equalsLower("MENU")) Result = parseMenuResource(); + else if (TypeToken->equalsLower("MENUEX")) + Result = parseMenuExResource(); else if (TypeToken->equalsLower("RCDATA")) Result = parseUserDefinedResource(RkRcData); else if (TypeToken->equalsLower("VERSIONINFO")) @@ -499,7 +501,7 @@ RCParser::ParseType RCParser::parseUserDefinedResource(IntOrString Type) { case Kind::String: case Kind::Identifier: return std::make_unique(Type, read().value(), - MemoryFlags); + MemoryFlags); default: break; } @@ -518,7 +520,7 @@ RCParser::ParseType RCParser::parseUserDefinedResource(IntOrString Type) { } return std::make_unique(Type, std::move(Data), - MemoryFlags); + MemoryFlags); } RCParser::ParseType RCParser::parseVersionInfoResource() { @@ -557,7 +559,8 @@ Expected RCParser::parseControl() { IntOrString Class; std::optional Style; if (ClassUpper == "CONTROL") { - // CONTROL text, id, class, style, x, y, width, height [, exstyle] [, helpID] + // CONTROL text, id, class, style, x, y, width, height [, exstyle] [, + // helpID] ASSIGN_OR_RETURN(ClassStr, readString()); RETURN_IF_ERROR(consumeType(Kind::Comma)); Class = *ClassStr; @@ -589,8 +592,8 @@ Expected RCParser::parseControl() { HelpID = *Val; } - return Control(*ClassResult, Caption, *ID, (*Args)[0], (*Args)[1], - (*Args)[2], (*Args)[3], Style, ExStyle, HelpID, Class); + return Control(*ClassResult, Caption, *ID, (*Args)[0], (*Args)[1], (*Args)[2], + (*Args)[3], Style, ExStyle, HelpID, Class); } RCParser::ParseType RCParser::parseBitmapResource() { @@ -620,7 +623,14 @@ RCParser::ParseType RCParser::parseMenuResource() { ASSIGN_OR_RETURN(OptStatements, parseOptionalStatements()); ASSIGN_OR_RETURN(Items, parseMenuItemsList()); return std::make_unique(std::move(*OptStatements), - std::move(*Items), MemoryFlags); + std::move(*Items), MemoryFlags); +} + +RCParser::ParseType RCParser::parseMenuExResource() { + uint16_t MemoryFlags = + parseMemoryFlags(MenuExResource::getDefaultMemoryFlags()); + ASSIGN_OR_RETURN(Items, parseMenuExItemsList()); + return std::make_unique(std::move(*Items), MemoryFlags); } Expected RCParser::parseMenuItemsList() { @@ -682,6 +692,95 @@ Expected RCParser::parseMenuItemsList() { return std::move(List); } +Expected RCParser::parseMenuExItemsList() { + RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); + + MenuDefinitionList List; + + // Read a set of items. Each item is of one of two kinds: + // MENUITEM caption:String [,[id][, [type][, state]]]] + // POPUP caption:String [,[id][, [type][, [state][, helpID]]]] { popupBody } + while (!consumeOptionalType(Kind::BlockEnd)) { + ASSIGN_OR_RETURN(ItemTypeResult, readIdentifier()); + + bool IsMenuItem = ItemTypeResult->equals_insensitive("MENUITEM"); + bool IsPopup = ItemTypeResult->equals_insensitive("POPUP"); + if (!IsMenuItem && !IsPopup) + return getExpectedError("MENUITEM, POPUP, END or '}'", true); + + // Not a separator. Read the caption. + ASSIGN_OR_RETURN(CaptionResult, readString()); + + // If MENUITEM, expect [,[id][, [type][, state]]]] + if (IsMenuItem) { + uint32_t MenuId = 0; + uint32_t MenuType = 0; + uint32_t MenuState = 0; + + if (consumeOptionalType(Kind::Comma)) { + auto IntId = readInt(); + if (IntId) { + MenuId = *IntId; + } + if (consumeOptionalType(Kind::Comma)) { + auto IntType = readInt(); + if (IntType) { + MenuType = *IntType; + } + if (consumeOptionalType(Kind::Comma)) { + auto IntState = readInt(); + if (IntState) { + MenuState = *IntState; + } + } + } + } + List.addDefinition(std::make_unique(*CaptionResult, MenuId, + MenuType, MenuState)); + continue; + } + + assert(IsPopup); + + uint32_t PopupId = 0; + uint32_t PopupType = 0; + uint32_t PopupState = 0; + uint32_t PopupHelpID = 0; + + if (consumeOptionalType(Kind::Comma)) { + auto IntId = readInt(); + if (IntId) { + PopupId = *IntId; + } + if (consumeOptionalType(Kind::Comma)) { + auto IntType = readInt(); + if (IntType) { + PopupType = *IntType; + } + if (consumeOptionalType(Kind::Comma)) { + auto IntState = readInt(); + if (IntState) { + PopupState = *IntState; + } + if (consumeOptionalType(Kind::Comma)) { + auto IntHelpID = readInt(); + if (IntHelpID) { + PopupHelpID = *IntHelpID; + } + } + } + } + } + // If POPUP, read submenu items recursively. + ASSIGN_OR_RETURN(SubMenuResult, parseMenuExItemsList()); + List.addDefinition(std::make_unique( + *CaptionResult, PopupId, PopupType, PopupState, PopupHelpID, + std::move(*SubMenuResult))); + } + + return std::move(List); +} + RCParser::ParseType RCParser::parseStringTableResource() { uint16_t MemoryFlags = parseMemoryFlags(StringTableResource::getDefaultMemoryFlags()); @@ -689,7 +788,7 @@ RCParser::ParseType RCParser::parseStringTableResource() { RETURN_IF_ERROR(consumeType(Kind::BlockBegin)); auto Table = std::make_unique(std::move(*OptStatements), - MemoryFlags); + MemoryFlags); // Read strings until we reach the end of the block. while (!consumeOptionalType(Kind::BlockEnd)) { @@ -753,7 +852,7 @@ Expected> RCParser::parseVersionInfoStmt() { PrecedingCommas.push_back(HadComma); } return std::make_unique(*KeyResult, std::move(Values), - std::move(PrecedingCommas)); + std::move(PrecedingCommas)); } return getExpectedError("BLOCK or VALUE", true); @@ -835,7 +934,7 @@ RCParser::ParseOptionType RCParser::parseFontStmt(OptStmtType DialogType) { } } return std::make_unique(*SizeResult, *NameResult, FontWeight, - FontItalic, FontCharset); + FontItalic, FontCharset); } RCParser::ParseOptionType RCParser::parseStyleStmt() { diff --git a/llvm/tools/llvm-rc/ResourceScriptParser.h b/llvm/tools/llvm-rc/ResourceScriptParser.h index a7d3b595f7943..5c01cec0f151e 100644 --- a/llvm/tools/llvm-rc/ResourceScriptParser.h +++ b/llvm/tools/llvm-rc/ResourceScriptParser.h @@ -143,6 +143,7 @@ class RCParser { ParseType parseIconResource(); ParseType parseHTMLResource(); ParseType parseMenuResource(); + ParseType parseMenuExResource(); ParseType parseStringTableResource(); ParseType parseUserDefinedResource(IntOrString Type); ParseType parseVersionInfoResource(); @@ -153,6 +154,9 @@ class RCParser { // Helper MENU parser. Expected parseMenuItemsList(); + // Helper MENUEX parser. + Expected parseMenuExItemsList(); + // Helper VERSIONINFO parser - read the contents of a single BLOCK statement, // from BEGIN to END. Expected> diff --git a/llvm/tools/llvm-rc/ResourceScriptStmt.cpp b/llvm/tools/llvm-rc/ResourceScriptStmt.cpp index ef8c34541881a..62df7999252fe 100644 --- a/llvm/tools/llvm-rc/ResourceScriptStmt.cpp +++ b/llvm/tools/llvm-rc/ResourceScriptStmt.cpp @@ -102,6 +102,12 @@ raw_ostream &MenuSeparator::log(raw_ostream &OS) const { return OS << " Menu separator\n"; } +raw_ostream &MenuExItem::log(raw_ostream &OS) const { + OS << " MenuExItem (" << Name << "), ID = " << Id; + OS << ", type: " << Type << ", state: " << State; + return OS << "\n"; +} + raw_ostream &PopupItem::log(raw_ostream &OS) const { OS << " Popup (" << Name << ")"; logFlags(OS, Flags); @@ -109,12 +115,25 @@ raw_ostream &PopupItem::log(raw_ostream &OS) const { return SubItems.log(OS); } +raw_ostream &PopupExItem::log(raw_ostream &OS) const { + OS << " Popup (" << Name << ")"; + OS << ", type: " << Type << ", state: " << State << ", help ID: " << HelpId; + OS << ":\n"; + return SubItems.log(OS); +} + raw_ostream &MenuResource::log(raw_ostream &OS) const { OS << "Menu (" << ResName << "):\n"; OptStatements->log(OS); return Elements.log(OS); } +raw_ostream &MenuExResource::log(raw_ostream &OS) const { + OS << "MenuEx (" << ResName << "):\n"; + OptStatements->log(OS); + return Elements.log(OS); +} + raw_ostream &StringTableResource::log(raw_ostream &OS) const { OS << "StringTable:\n"; OptStatements->log(OS); diff --git a/llvm/tools/llvm-rc/ResourceScriptStmt.h b/llvm/tools/llvm-rc/ResourceScriptStmt.h index 71f6a9d212e46..09853da6c500a 100644 --- a/llvm/tools/llvm-rc/ResourceScriptStmt.h +++ b/llvm/tools/llvm-rc/ResourceScriptStmt.h @@ -536,6 +536,23 @@ class MenuItem : public MenuDefinition { } }; +class MenuExItem : public MenuDefinition { +public: + StringRef Name; + uint32_t Id; + uint32_t Type; + uint32_t State; + + MenuExItem(StringRef Caption, uint32_t ItemId, uint32_t Type, uint32_t State) + : Name(Caption), Id(ItemId), Type(Type), State(State) {} + raw_ostream &log(raw_ostream &) const override; + + MenuDefKind getKind() const override { return MkMenuItem; } + static bool classof(const MenuDefinition *D) { + return D->getKind() == MkMenuItem; + } +}; + // POPUP statement definition. // // Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381030(v=vs.85).aspx @@ -550,8 +567,7 @@ class PopupItem : public MenuDefinition { : Name(Caption), Flags(ItemFlags), SubItems(std::move(SubItemsList)) {} raw_ostream &log(raw_ostream &) const override; - // This has an additional (0x10) flag. It doesn't match with documented - // 0x01 flag, though. + // This has an additional MF_POPUP (0x10) flag. uint16_t getResFlags() const override { return Flags | 0x10; } MenuDefKind getKind() const override { return MkPopup; } static bool classof(const MenuDefinition *D) { @@ -559,6 +575,28 @@ class PopupItem : public MenuDefinition { } }; +class PopupExItem : public MenuDefinition { +public: + StringRef Name; + uint32_t Id; + uint32_t Type; + uint32_t State; + uint32_t HelpId; + MenuDefinitionList SubItems; + + PopupExItem(StringRef Caption, uint32_t Id, uint32_t Type, uint32_t State, + uint32_t HelpId, MenuDefinitionList &&SubItemsList) + : Name(Caption), Id(Id), Type(Type), State(State), HelpId(HelpId), + SubItems(std::move(SubItemsList)) {} + raw_ostream &log(raw_ostream &) const override; + + uint16_t getResFlags() const override { return 0x01; } + MenuDefKind getKind() const override { return MkPopup; } + static bool classof(const MenuDefinition *D) { + return D->getKind() == MkPopup; + } +}; + // Menu resource definition. class MenuResource : public OptStatementsRCResource { public: @@ -579,6 +617,25 @@ class MenuResource : public OptStatementsRCResource { } }; +class MenuExResource : public OptStatementsRCResource { +public: + MenuDefinitionList Elements; + + MenuExResource(MenuDefinitionList &&Items, uint16_t Flags) + : OptStatementsRCResource({}, Flags), Elements(std::move(Items)) {} + raw_ostream &log(raw_ostream &) const override; + + IntOrString getResourceType() const override { return RkMenu; } + Twine getResourceTypeName() const override { return "MENUEX"; } + Error visit(Visitor *V) const override { + return V->visitMenuExResource(this); + } + ResourceKind getKind() const override { return RkMenu; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkMenu; + } +}; + // STRINGTABLE resource. Contains a list of strings, each having its unique ID. // // Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381050(v=vs.85).aspx diff --git a/llvm/tools/llvm-rc/ResourceVisitor.h b/llvm/tools/llvm-rc/ResourceVisitor.h index 843c8d898a211..a950cd7555ecd 100644 --- a/llvm/tools/llvm-rc/ResourceVisitor.h +++ b/llvm/tools/llvm-rc/ResourceVisitor.h @@ -39,6 +39,7 @@ class Visitor { virtual Error visitHTMLResource(const RCResource *) = 0; virtual Error visitIconResource(const RCResource *) = 0; virtual Error visitMenuResource(const RCResource *) = 0; + virtual Error visitMenuExResource(const RCResource *) = 0; virtual Error visitStringTableResource(const RCResource *) = 0; virtual Error visitUserDefinedResource(const RCResource *) = 0; virtual Error visitVersionInfoResource(const RCResource *) = 0;