diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2018-07-28 10:51:19 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2018-07-28 10:51:19 +0000 |
commit | eb11fae6d08f479c0799db45860a98af528fa6e7 (patch) | |
tree | 44d492a50c8c1a7eb8e2d17ea3360ec4d066f042 /tools/llvm-rc/ResourceFileWriter.cpp | |
parent | b8a2042aa938069e862750553db0e4d82d25822c (diff) |
Notes
Diffstat (limited to 'tools/llvm-rc/ResourceFileWriter.cpp')
-rw-r--r-- | tools/llvm-rc/ResourceFileWriter.cpp | 195 |
1 files changed, 142 insertions, 53 deletions
diff --git a/tools/llvm-rc/ResourceFileWriter.cpp b/tools/llvm-rc/ResourceFileWriter.cpp index f141dc7e3564..4b561940d2ec 100644 --- a/tools/llvm-rc/ResourceFileWriter.cpp +++ b/tools/llvm-rc/ResourceFileWriter.cpp @@ -110,6 +110,18 @@ static bool stripQuotes(StringRef &Str, bool &IsLongString) { return true; } +static UTF16 cp1252ToUnicode(unsigned char C) { + static const UTF16 Map80[] = { + 0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f, + 0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178, + }; + if (C >= 0x80 && C <= 0x9F) + return Map80[C - 0x80]; + return C; +} + // Describes a way to handle '\0' characters when processing the string. // rc.exe tool sometimes behaves in a weird way in postprocessing. // If the string to be output is equivalent to a C-string (e.g. in MENU @@ -132,10 +144,26 @@ enum class NullHandlingMethod { // * Replace the escape sequences with their processed version. // For identifiers, this is no-op. static Error processString(StringRef Str, NullHandlingMethod NullHandler, - bool &IsLongString, SmallVectorImpl<UTF16> &Result) { + bool &IsLongString, SmallVectorImpl<UTF16> &Result, + int CodePage) { bool IsString = stripQuotes(Str, IsLongString); SmallVector<UTF16, 128> Chars; - convertUTF8ToUTF16String(Str, Chars); + + // Convert the input bytes according to the chosen codepage. + if (CodePage == CpUtf8) { + convertUTF8ToUTF16String(Str, Chars); + } else if (CodePage == CpWin1252) { + for (char C : Str) + Chars.push_back(cp1252ToUnicode((unsigned char)C)); + } else { + // For other, unknown codepages, only allow plain ASCII input. + for (char C : Str) { + if ((unsigned char)C > 0x7F) + return createError("Non-ASCII 8-bit codepoint (" + Twine(C) + + ") can't be interpreted in the current codepage"); + Chars.push_back((unsigned char)C); + } + } if (!IsString) { // It's an identifier if it's not a string. Make all characters uppercase. @@ -157,21 +185,35 @@ static Error processString(StringRef Str, NullHandlingMethod NullHandler, if (Char > 0xFF) return createError("Non-8-bit codepoint (" + Twine(Char) + ") can't occur in a user-defined narrow string"); + } + } + Result.push_back(Char); + return Error::success(); + }; + auto AddEscapedChar = [AddRes, IsLongString, CodePage](UTF16 Char) -> Error { + if (!IsLongString) { + // Escaped chars in narrow strings have to be interpreted according to + // the chosen code page. + if (Char > 0xFF) + return createError("Non-8-bit escaped char (" + Twine(Char) + + ") can't occur in narrow string"); + if (CodePage == CpUtf8) { + if (Char >= 0x80) + return createError("Unable to interpret single byte (" + Twine(Char) + + ") as UTF-8"); + } else if (CodePage == CpWin1252) { + Char = cp1252ToUnicode(Char); } else { - // In case of narrow non-user strings, Windows RC converts - // [0x80, 0xFF] chars according to the current codepage. - // There is no 'codepage' concept settled in every supported platform, - // so we should reject such inputs. - if (Char > 0x7F && Char <= 0xFF) + // Unknown/unsupported codepage, only allow ASCII input. + if (Char > 0x7F) return createError("Non-ASCII 8-bit codepoint (" + Twine(Char) + ") can't " "occur in a non-Unicode string"); } } - Result.push_back(Char); - return Error::success(); + return AddRes(Char); }; while (Pos < Chars.size()) { @@ -223,7 +265,7 @@ static Error processString(StringRef Str, NullHandlingMethod NullHandler, --RemainingChars; } - RETURN_IF_ERROR(AddRes(ReadInt)); + RETURN_IF_ERROR(AddEscapedChar(ReadInt)); continue; } @@ -240,7 +282,7 @@ static Error processString(StringRef Str, NullHandlingMethod NullHandler, ++Pos; } - RETURN_IF_ERROR(AddRes(ReadInt)); + RETURN_IF_ERROR(AddEscapedChar(ReadInt)); continue; } @@ -328,7 +370,8 @@ Error ResourceFileWriter::writeCString(StringRef Str, bool WriteTerminator) { SmallVector<UTF16, 128> ProcessedString; bool IsLongString; RETURN_IF_ERROR(processString(Str, NullHandlingMethod::CutAtNull, - IsLongString, ProcessedString)); + IsLongString, ProcessedString, + Params.CodePage)); for (auto Ch : ProcessedString) writeInt<uint16_t>(Ch); if (WriteTerminator) @@ -394,6 +437,10 @@ Error ResourceFileWriter::visitAcceleratorsResource(const RCResource *Res) { return writeResource(Res, &ResourceFileWriter::writeAcceleratorsBody); } +Error ResourceFileWriter::visitBitmapResource(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeBitmapBody); +} + Error ResourceFileWriter::visitCursorResource(const RCResource *Res) { return handleError(visitIconOrCursorResource(Res), Res); } @@ -411,6 +458,11 @@ Error ResourceFileWriter::visitCaptionStmt(const CaptionStmt *Stmt) { return Error::success(); } +Error ResourceFileWriter::visitClassStmt(const ClassStmt *Stmt) { + ObjectData.Class = Stmt->Value; + return Error::success(); +} + Error ResourceFileWriter::visitHTMLResource(const RCResource *Res) { return writeResource(Res, &ResourceFileWriter::writeHTMLBody); } @@ -435,8 +487,8 @@ Error ResourceFileWriter::visitStringTableResource(const RCResource *Base) { if (Iter == BundleData.end()) { // Need to create a bundle. StringTableData.BundleList.push_back(Key); - auto EmplaceResult = - BundleData.emplace(Key, StringTableInfo::Bundle(ObjectData)); + auto EmplaceResult = BundleData.emplace( + Key, StringTableInfo::Bundle(ObjectData, Res->MemoryFlags)); assert(EmplaceResult.second && "Could not create a bundle"); Iter = EmplaceResult.first; } @@ -509,7 +561,7 @@ Error ResourceFileWriter::writeResource( padStream(sizeof(uint32_t)); object::WinResHeaderSuffix HeaderSuffix{ ulittle32_t(0), // DataVersion; seems to always be 0 - ulittle16_t(Res->getMemoryFlags()), ulittle16_t(ObjectData.LanguageInfo), + ulittle16_t(Res->MemoryFlags), ulittle16_t(ObjectData.LanguageInfo), ulittle32_t(ObjectData.VersionInfo), ulittle32_t(ObjectData.Characteristics)}; writeObject(HeaderSuffix); @@ -641,6 +693,29 @@ Error ResourceFileWriter::writeAcceleratorsBody(const RCResource *Base) { return Error::success(); } +// --- BitmapResource helpers. --- // + +Error ResourceFileWriter::writeBitmapBody(const RCResource *Base) { + StringRef Filename = cast<BitmapResource>(Base)->BitmapLoc; + bool IsLong; + stripQuotes(Filename, IsLong); + + auto File = loadFile(Filename); + if (!File) + return File.takeError(); + + StringRef Buffer = (*File)->getBuffer(); + + // Skip the 14 byte BITMAPFILEHEADER. + constexpr size_t BITMAPFILEHEADER_size = 14; + if (Buffer.size() < BITMAPFILEHEADER_size || Buffer[0] != 'B' || + Buffer[1] != 'M') + return createError("Incorrect bitmap file."); + + *FS << Buffer.substr(BITMAPFILEHEADER_size); + return Error::success(); +} + // --- CursorResource and IconResource helpers. --- // // ICONRESDIR structure. Describes a single icon in resouce group. @@ -715,16 +790,14 @@ public: SingleIconCursorResource(IconCursorGroupType ResourceType, const ResourceDirEntryStart &HeaderEntry, - ArrayRef<uint8_t> ImageData) - : Type(ResourceType), Header(HeaderEntry), Image(ImageData) {} + ArrayRef<uint8_t> ImageData, uint16_t Flags) + : RCResource(Flags), 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; @@ -845,46 +918,57 @@ Error ResourceFileWriter::visitIconOrCursorResource(const RCResource *Base) { Reader.setOffset(ItemOffsets[ID]); ArrayRef<uint8_t> Image; RETURN_IF_ERROR(Reader.readArray(Image, ItemEntries[ID].Size)); - SingleIconCursorResource SingleRes(Type, ItemEntries[ID], Image); + SingleIconCursorResource SingleRes(Type, ItemEntries[ID], Image, + Base->MemoryFlags); 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. + // We need to rewrite the cursor headers, and fetch actual values + // for Planes/BitCount. 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; + ResourceDirEntryStart NewHeader = OldHeader; + + if (Type == IconCursorGroupType::Cursor) { + 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; + + // Two WORDs were written at the beginning of the resource (hotspot + // location). This is reflected in Size field. + NewHeader.Size += 2 * sizeof(uint16_t); + } // 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); + if (BMPHeader->Size == sizeof(BitmapInfoHeader)) { + NewHeader.Planes = BMPHeader->Planes; + NewHeader.BitCount = BMPHeader->BitCount; + } else { + // A PNG .ico file. + // https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473 + // "The image must be in 32bpp" + NewHeader.Planes = 1; + NewHeader.BitCount = 32; + } ItemEntries[ID] = NewHeader; } IconCursorGroupResource HeaderRes(Type, *Header, std::move(ItemEntries)); HeaderRes.setName(ResName); + if (Base->MemoryFlags & MfPreload) { + HeaderRes.MemoryFlags |= MfPreload; + HeaderRes.MemoryFlags &= ~MfPure; + } RETURN_IF_ERROR(visitIconOrCursorGroup(&HeaderRes)); return Error::success(); @@ -938,15 +1022,18 @@ Error ResourceFileWriter::writeSingleDialogControl(const Control &Ctl, // ID; it's 16-bit in DIALOG and 32-bit in DIALOGEX. if (!IsExtended) { - RETURN_IF_ERROR(checkNumberFits<uint16_t>( - Ctl.ID, "Control ID in simple DIALOG resource")); + // It's common to use -1, i.e. UINT32_MAX, for controls one doesn't + // want to refer to later. + if (Ctl.ID != static_cast<uint32_t>(-1)) + RETURN_IF_ERROR(checkNumberFits<uint16_t>( + Ctl.ID, "Control ID in simple DIALOG resource")); writeInt<uint16_t>(Ctl.ID); } else { writeInt<uint32_t>(Ctl.ID); } // Window class - either 0xFFFF + 16-bit integer or a string. - RETURN_IF_ERROR(writeIntOrString(IntOrString(TypeInfo.CtlClass))); + RETURN_IF_ERROR(writeIntOrString(Ctl.Class)); // Element caption/reference ID. ID is preceded by 0xFFFF. RETURN_IF_ERROR(checkIntOrString(Ctl.Title, "Control reference ID")); @@ -1038,8 +1125,8 @@ Error ResourceFileWriter::writeDialogBody(const RCResource *Base) { // think there is no menu attached to the dialog. writeInt<uint16_t>(0); - // Window CLASS field. Not kept here. - writeInt<uint16_t>(0); + // Window CLASS field. + RETURN_IF_ERROR(writeIntOrString(ObjectData.Class)); // Window title or a single word equal to 0. RETURN_IF_ERROR(writeCString(ObjectData.Caption)); @@ -1135,13 +1222,15 @@ public: using BundleType = ResourceFileWriter::StringTableInfo::Bundle; BundleType Bundle; - BundleResource(const BundleType &StrBundle) : Bundle(StrBundle) {} + BundleResource(const BundleType &StrBundle) + : RCResource(StrBundle.MemoryFlags), Bundle(StrBundle) {} IntOrString getResourceType() const override { return 6; } ResourceKind getKind() const override { return RkStringTableBundle; } static bool classof(const RCResource *Res) { return Res->getKind() == RkStringTableBundle; } + Twine getResourceTypeName() const override { return "STRINGTABLE"; } }; Error ResourceFileWriter::visitStringTableBundle(const RCResource *Res) { @@ -1168,7 +1257,7 @@ Error ResourceFileWriter::writeStringTableBundleBody(const RCResource *Base) { SmallVector<UTF16, 128> Data; RETURN_IF_ERROR(processString(Res->Bundle.Data[ID].getValueOr(StringRef()), NullHandlingMethod::CutAtDoubleNull, - IsLongString, Data)); + IsLongString, Data, Params.CodePage)); if (AppendNull && Res->Bundle.Data[ID]) Data.push_back('\0'); RETURN_IF_ERROR( @@ -1215,9 +1304,9 @@ Error ResourceFileWriter::writeUserDefinedBody(const RCResource *Base) { SmallVector<UTF16, 128> ProcessedString; bool IsLongString; - RETURN_IF_ERROR(processString(Elem.getString(), - NullHandlingMethod::UserResource, - IsLongString, ProcessedString)); + RETURN_IF_ERROR( + processString(Elem.getString(), NullHandlingMethod::UserResource, + IsLongString, ProcessedString, Params.CodePage)); for (auto Ch : ProcessedString) { if (IsLongString) { @@ -1241,6 +1330,7 @@ Error ResourceFileWriter::writeVersionInfoBlock(const VersionInfoBlock &Blk) { bool OutputHeader = Blk.Name != ""; uint64_t LengthLoc; + padStream(sizeof(uint32_t)); if (OutputHeader) { LengthLoc = writeInt<uint16_t>(0); writeInt<uint16_t>(0); @@ -1266,7 +1356,6 @@ Error ResourceFileWriter::writeVersionInfoBlock(const VersionInfoBlock &Blk) { writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc); } - padStream(sizeof(uint32_t)); return Error::success(); } @@ -1296,6 +1385,7 @@ Error ResourceFileWriter::writeVersionInfoValue(const VersionInfoValue &Val) { return createError(Twine("VALUE ") + Val.Key + " cannot contain both strings and integers"); + padStream(sizeof(uint32_t)); auto LengthLoc = writeInt<uint16_t>(0); auto ValLengthLoc = writeInt<uint16_t>(0); writeInt<uint16_t>(HasStrings); @@ -1325,7 +1415,6 @@ Error ResourceFileWriter::writeVersionInfoValue(const VersionInfoValue &Val) { } writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc); writeObjectAt(ulittle16_t(ValueLength), ValLengthLoc); - padStream(sizeof(uint32_t)); return Error::success(); } |