Index: llvm/test/tools/llvm-rc/Inputs/tag-icon-cursor-bad-offset.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-icon-cursor-bad-offset.rc @@ -0,0 +1,2 @@ +50 CURSOR "cursor-bad-offset.cur" + Index: llvm/test/tools/llvm-rc/Inputs/tag-icon-cursor-bad-type.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-icon-cursor-bad-type.rc @@ -0,0 +1 @@ +100 ICON "cursor.cur" Index: llvm/test/tools/llvm-rc/Inputs/tag-icon-cursor-eof.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-icon-cursor-eof.rc @@ -0,0 +1 @@ +72 CURSOR "cursor-eof.cur" Index: llvm/test/tools/llvm-rc/Inputs/tag-icon-cursor-nonexistent.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-icon-cursor-nonexistent.rc @@ -0,0 +1 @@ +500 CURSOR "this-file-does-not-exist.cur" Index: llvm/test/tools/llvm-rc/Inputs/tag-icon-cursor-nonsense.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-icon-cursor-nonsense.rc @@ -0,0 +1 @@ +1 ICON "tag-icon-cursor-nonsense.rc" Index: llvm/test/tools/llvm-rc/Inputs/tag-icon-cursor.rc =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/Inputs/tag-icon-cursor.rc @@ -0,0 +1,6 @@ +4464 CURSOR "cursor.cur" +4465 CUrsoR "cursor-8.cur" +100 ICON "icon-old.ico" +4466 cURSOR "cursor-32.cur" +100 ICON "icon-new.ico" +100 iCon "icon-png.ico" Index: llvm/test/tools/llvm-rc/tag-icon-cursor.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-rc/tag-icon-cursor.test @@ -0,0 +1,37 @@ +; RUN: rm -rf %t +; RUN: mkdir %t +; RUN: cd %t +; RUN: cp %p/Inputs/icon*.ico . +; RUN: cp %p/Inputs/cursor*.cur . +; RUN: cp %p/Inputs/tag-icon-cursor-nonsense.rc . + +; RUN: llvm-rc /FO %t/tag-icon-cursor.res %p/Inputs/tag-icon-cursor.rc +; RUN: diff %t/tag-icon-cursor.res %p/Inputs/tag-icon-cursor.res + +; RUN: not llvm-rc /FO %t/1 %p/Inputs/tag-icon-cursor-nonexistent.rc 2>&1 | FileCheck %s --check-prefix NOFILE +; NOFILE: llvm-rc: Error in CURSOR statement (ID 500): +; NOFILE-NEXT: Error opening cursor 'this-file-does-not-exist.cur': + + +; RUN: not llvm-rc /FO %t/1 %p/Inputs/tag-icon-cursor-nonsense.rc 2>&1 | FileCheck %s --check-prefix NONSENSE + +; NONSENSE: llvm-rc: Error in ICON statement (ID 1): +; NONSENSE-NEXT: Incorrect icon/cursor Reserved field; should be 0. + + +; RUN: not llvm-rc /FO %t/1 %p/Inputs/tag-icon-cursor-eof.rc 2>&1 | FileCheck %s --check-prefix EOF + +; EOF: llvm-rc: Error in CURSOR statement (ID 72): +; EOF-NEXT: Stream Error: The stream is too short to perform the requested operation. + + +; RUN: not llvm-rc /FO %t/1 %p/Inputs/tag-icon-cursor-bad-offset.rc 2>&1 | FileCheck %s --check-prefix OFFSET + +; OFFSET: llvm-rc: Error in CURSOR statement (ID 50): +; OFFSET-NEXT: Stream Error: The specified offset is invalid for the current stream. + + +; RUN: not llvm-rc /FO %t/1 %p/Inputs/tag-icon-cursor-bad-type.rc 2>&1 | FileCheck %s --check-prefix BADTYPE + +; BADTYPE: llvm-rc: Error in ICON statement (ID 100): +; BADTYPE-NEXT: Incorrect icon/cursor ResType field; should be 1. Index: llvm/tools/llvm-rc/ResourceScriptStmt.h =================================================================== --- llvm/tools/llvm-rc/ResourceScriptStmt.h +++ llvm/tools/llvm-rc/ResourceScriptStmt.h @@ -74,9 +74,13 @@ // (TYPE in RESOURCEHEADER structure). The numeric value assigned to each // kind is equal to this type ID. RkNull = 0, + RkSingleCursor = 1, + RkSingleIcon = 3, RkMenu = 4, RkDialog = 5, RkAccelerators = 9, + RkCursorGroup = 12, + RkIconGroup = 14, RkVersionInfo = 16, RkHTML = 23, @@ -88,7 +92,9 @@ RkBase, RkCursor, RkIcon, - RkUser + RkUser, + RkSingleCursorOrIconRes, + RkCursorOrIconGroupRes }; // Non-zero memory flags. @@ -258,22 +264,38 @@ // // Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa380920(v=vs.85).aspx class CursorResource : public RCResource { +public: StringRef CursorLoc; -public: CursorResource(StringRef Location) : CursorLoc(Location) {} raw_ostream &log(raw_ostream &) const override; + + Twine getResourceTypeName() const override { return "CURSOR"; } + Error visit(Visitor *V) const override { + return V->visitCursorResource(this); + } + ResourceKind getKind() const override { return RkCursor; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkCursor; + } }; // ICON resource. Represents a single ".ico" file containing a group of icons. // // Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381018(v=vs.85).aspx class IconResource : public RCResource { +public: StringRef IconLoc; -public: IconResource(StringRef Location) : IconLoc(Location) {} raw_ostream &log(raw_ostream &) const override; + + Twine getResourceTypeName() const override { return "ICON"; } + Error visit(Visitor *V) const override { return V->visitIconResource(this); } + ResourceKind getKind() const override { return RkIcon; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkIcon; + } }; // HTML resource. Represents a local webpage that is to be embedded into the Index: llvm/tools/llvm-rc/ResourceSerializator.h =================================================================== --- llvm/tools/llvm-rc/ResourceSerializator.h +++ llvm/tools/llvm-rc/ResourceSerializator.h @@ -23,14 +23,16 @@ class ResourceSerializator : public Visitor { public: ResourceSerializator(std::unique_ptr Stream) - : FS(std::move(Stream)) { + : FS(std::move(Stream)), IconCursorID(1) { assert(FS && "Output stream needs to be provided to the serializator"); } Error visitNullResource(const RCResource *) override; Error visitAcceleratorsResource(const RCResource *) override; + Error visitCursorResource(const RCResource *) override; Error visitDialogResource(const RCResource *) override; Error visitHTMLResource(const RCResource *) override; + Error visitIconResource(const RCResource *) override; Error visitMenuResource(const RCResource *) override; Error visitCaptionStmt(const CaptionStmt *) override; @@ -74,6 +76,13 @@ bool IsLastItem); Error writeAcceleratorsBody(const RCResource *); + // CursorResource and IconResource + Error visitIconOrCursorResource(const RCResource *); + Error visitIconOrCursorGroup(const RCResource *); + Error visitSingleIconOrCursor(const RCResource *); + Error writeSingleIconOrCursorBody(const RCResource *); + Error writeIconOrCursorGroupBody(const RCResource *); + // DialogResource Error writeSingleDialogControl(const Control &, bool IsExtended); Error writeDialogBody(const RCResource *); @@ -110,6 +119,10 @@ Error appendFile(StringRef Filename); void padStream(uint64_t Length); + + // Icon and cursor IDs are allocated starting from 1 and increasing for + // each icon/cursor dumped. This maintains the current ID to be allocated. + uint16_t IconCursorID; }; } // namespace rc Index: llvm/tools/llvm-rc/ResourceSerializator.cpp =================================================================== --- llvm/tools/llvm-rc/ResourceSerializator.cpp +++ llvm/tools/llvm-rc/ResourceSerializator.cpp @@ -220,10 +220,18 @@ return writeResource(Res, &ResourceSerializator::writeAcceleratorsBody); } +Error ResourceSerializator::visitCursorResource(const RCResource *Res) { + return handleError(visitIconOrCursorResource(Res), Res); +} + Error ResourceSerializator::visitDialogResource(const RCResource *Res) { return writeResource(Res, &ResourceSerializator::writeDialogBody); } +Error ResourceSerializator::visitIconResource(const RCResource *Res) { + return handleError(visitIconOrCursorResource(Res), Res); +} + Error ResourceSerializator::visitCaptionStmt(const CaptionStmt *Stmt) { ObjectData.Caption = Stmt->Value; return Error::success(); @@ -422,6 +430,261 @@ return Error::success(); } +// --- CursorResource and IconResource helpers. --- // + +// ICONRESDIR structure. Describes a single icon in resouce group. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648016.aspx +struct IconResDir { + uint8_t Width; + uint8_t Height; + uint8_t ColorCount; + uint8_t Reserved; +}; + +// CURSORDIR structure. Describes a single cursor in resource group. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648011(v=vs.85).aspx +struct CursorDir { + ulittle16_t Width; + ulittle16_t Height; +}; + +// RESDIRENTRY structure, stripped from the last item. Stripping made +// for compatibility with RESDIR. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026(v=vs.85).aspx +struct ResourceDirEntryStart { + union { + CursorDir Cursor; // Used in CURSOR resources. + IconResDir Icon; // Used in .ico and .cur files, and ICON resources. + }; + ulittle16_t Planes; // HotspotX (.cur files but not CURSOR resource). + ulittle16_t BitCount; // HotspotY (.cur files but not CURSOR resource). + ulittle32_t Size; + // ulittle32_t ImageOffset; // Offset to image data (ICONDIRENTRY only). + // ulittle16_t IconID; // Resource icon ID (RESDIR only). +}; + +// BITMAPINFOHEADER structure. Describes basic information about the bitmap +// being read. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx +struct BitmapInfoHeader { + ulittle32_t Size; + ulittle32_t Width; + ulittle32_t Height; + ulittle16_t Planes; + ulittle16_t BitCount; + ulittle32_t Compression; + ulittle32_t SizeImage; + ulittle32_t XPelsPerMeter; + ulittle32_t YPelsPerMeter; + ulittle32_t ClrUsed; + ulittle32_t ClrImportant; +}; + +// Group icon directory header. Called ICONDIR in .ico/.cur files and +// NEWHEADER in .res files. +// +// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648023(v=vs.85).aspx +struct GroupIconDir { + ulittle16_t Reserved; // Always 0. + ulittle16_t ResType; // 1 for icons, 2 for cursors. + ulittle16_t ResCount; // Number of items. +}; + +enum class IconCursorGroupType { Icon, Cursor }; + +class SingleIconCursorResource : public RCResource { +public: + IconCursorGroupType Type; + const ResourceDirEntryStart &Header; + ArrayRef Image; + + SingleIconCursorResource(IconCursorGroupType ResourceType, + const ResourceDirEntryStart &HeaderEntry, + ArrayRef ImageData) + : Type(ResourceType), Header(HeaderEntry), Image(ImageData) {} + + Twine getResourceTypeName() const override { return "Icon/cursor image"; } + IntOrString getResourceType() const override { + return Type == IconCursorGroupType::Icon ? RkSingleIcon : RkSingleCursor; + } + uint16_t getMemoryFlags() const override { + return MfDiscardable | MfMoveable; + } + ResourceKind getKind() const override { return RkSingleCursorOrIconRes; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkSingleCursorOrIconRes; + } +}; + +class IconCursorGroupResource : public RCResource { +public: + IconCursorGroupType Type; + GroupIconDir Header; + std::vector ItemEntries; + + IconCursorGroupResource(IconCursorGroupType ResourceType, + const GroupIconDir &HeaderData, + std::vector &&Entries) + : Type(ResourceType), Header(HeaderData), + ItemEntries(std::move(Entries)) {} + + Twine getResourceTypeName() const override { return "Icon/cursor group"; } + IntOrString getResourceType() const override { + return Type == IconCursorGroupType::Icon ? RkIconGroup : RkCursorGroup; + } + ResourceKind getKind() const override { return RkCursorOrIconGroupRes; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkCursorOrIconGroupRes; + } +}; + +Error ResourceSerializator::writeSingleIconOrCursorBody( + const RCResource *Base) { + auto *Res = cast(Base); + if (Res->Type == IconCursorGroupType::Cursor) { + // In case of cursors, two WORDS are appended to the beginning + // of the resource: HotspotX (Planes in RESDIRENTRY), + // and HotspotY (BitCount). + // + // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026.aspx + // (Remarks section). + writeObject(Res->Header.Planes); + writeObject(Res->Header.BitCount); + } + + writeObject(Res->Image.data(), Res->Image.size()); + return Error::success(); +} + +Error ResourceSerializator::writeIconOrCursorGroupBody(const RCResource *Base) { + auto *Res = cast(Base); + writeObject(Res->Header); + for (auto Item : Res->ItemEntries) { + writeObject(Item); + writeObject(ulittle16_t(IconCursorID++)); + } + return Error::success(); +} + +Error ResourceSerializator::visitSingleIconOrCursor(const RCResource *Res) { + return writeResource(Res, &ResourceSerializator::writeSingleIconOrCursorBody); +} + +Error ResourceSerializator::visitIconOrCursorGroup(const RCResource *Res) { + return writeResource(Res, &ResourceSerializator::writeIconOrCursorGroupBody); +} + +Error ResourceSerializator::visitIconOrCursorResource(const RCResource *Base) { + IconCursorGroupType Type; + StringRef FileStr; + IntOrString ResName = Base->ResName; + + if (auto *IconRes = dyn_cast(Base)) { + FileStr = IconRes->IconLoc; + Type = IconCursorGroupType::Icon; + } else { + auto *CursorRes = dyn_cast(Base); + FileStr = CursorRes->CursorLoc; + Type = IconCursorGroupType::Cursor; + } + + bool IsLong; + stripQuotes(FileStr, IsLong); + ErrorOr> File = + MemoryBuffer::getFile(FileStr, -1, false); + + if (!File) + return make_error( + "Error opening " + + Twine(Type == IconCursorGroupType::Icon ? "icon" : "cursor") + + " '" + FileStr + "': " + File.getError().message(), + File.getError()); + + BinaryStreamReader Reader((*File)->getBuffer(), support::little); + + // Read the file headers. + // - At the beginning, ICONDIR/NEWHEADER header. + // - Then, a number of RESDIR headers follow. These contain offsets + // to data. + const GroupIconDir *Header; + + RETURN_IF_ERROR(Reader.readObject(Header)); + if (Header->Reserved != 0) + return createError("Incorrect icon/cursor Reserved field; should be 0."); + uint16_t NeededType = Type == IconCursorGroupType::Icon ? 1 : 2; + if (Header->ResType != NeededType) + return createError("Incorrect icon/cursor ResType field; should be " + + Twine(NeededType) + "."); + + uint16_t NumItems = Header->ResCount; + + // Read single ico/cur headers. + std::vector ItemEntries; + ItemEntries.reserve(NumItems); + std::vector ItemOffsets(NumItems); + for (size_t ID = 0; ID < NumItems; ++ID) { + const ResourceDirEntryStart *Object; + RETURN_IF_ERROR(Reader.readObject(Object)); + ItemEntries.push_back(*Object); + RETURN_IF_ERROR(Reader.readInteger(ItemOffsets[ID])); + } + + // Now write each icon/cursors one by one. At first, all the contents + // without ICO/CUR header. This is described by SingleIconCursorResource. + for (size_t ID = 0; ID < NumItems; ++ID) { + // Load the fragment of file. + Reader.setOffset(ItemOffsets[ID]); + ArrayRef Image; + RETURN_IF_ERROR(Reader.readArray(Image, ItemEntries[ID].Size)); + SingleIconCursorResource SingleRes(Type, ItemEntries[ID], Image); + SingleRes.setName(IconCursorID + ID); + RETURN_IF_ERROR(visitSingleIconOrCursor(&SingleRes)); + } + + // Now, write all the headers concatenated into a separate resource. + for (size_t ID = 0; ID < NumItems; ++ID) { + if (Type == IconCursorGroupType::Icon) { + // rc.exe seems to always set NumPlanes to 1. No idea why it happens. + ItemEntries[ID].Planes = 1; + continue; + } + + // We need to rewrite the cursor headers. + const auto &OldHeader = ItemEntries[ID]; + ResourceDirEntryStart NewHeader; + NewHeader.Cursor.Width = OldHeader.Icon.Width; + // Each cursor in fact stores two bitmaps, one under another. + // Height provided in cursor definition describes the height of the + // cursor, whereas the value existing in resource definition describes + // the height of the bitmap. Therefore, we need to double this height. + NewHeader.Cursor.Height = OldHeader.Icon.Height * 2; + + // Now, we actually need to read the bitmap header to find + // the number of planes and the number of bits per pixel. + Reader.setOffset(ItemOffsets[ID]); + const BitmapInfoHeader *BMPHeader; + RETURN_IF_ERROR(Reader.readObject(BMPHeader)); + NewHeader.Planes = BMPHeader->Planes; + NewHeader.BitCount = BMPHeader->BitCount; + + // Two WORDs were written at the beginning of the resource (hotspot + // location). This is reflected in Size field. + NewHeader.Size = OldHeader.Size + 2 * sizeof(uint16_t); + + ItemEntries[ID] = NewHeader; + } + + IconCursorGroupResource HeaderRes(Type, *Header, std::move(ItemEntries)); + HeaderRes.setName(ResName); + RETURN_IF_ERROR(visitIconOrCursorGroup(&HeaderRes)); + + return Error::success(); +} + // --- DialogResource helpers. --- // Error ResourceSerializator::writeSingleDialogControl(const Control &Ctl, Index: llvm/tools/llvm-rc/ResourceVisitor.h =================================================================== --- llvm/tools/llvm-rc/ResourceVisitor.h +++ llvm/tools/llvm-rc/ResourceVisitor.h @@ -32,8 +32,10 @@ public: virtual Error visitNullResource(const RCResource *) = 0; virtual Error visitAcceleratorsResource(const RCResource *) = 0; + virtual Error visitCursorResource(const RCResource *) = 0; virtual Error visitDialogResource(const RCResource *) = 0; virtual Error visitHTMLResource(const RCResource *) = 0; + virtual Error visitIconResource(const RCResource *) = 0; virtual Error visitMenuResource(const RCResource *) = 0; virtual Error visitCaptionStmt(const CaptionStmt *) = 0;