1 //===-- ResourceFileWriter.cpp --------------------------------*- C++-*-===//
3 // The LLVM Compiler Infrastructure
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
8 //===---------------------------------------------------------------------===//
10 // This implements the visitor serializing resources to a .res stream.
12 //===---------------------------------------------------------------------===//
14 #include "ResourceFileWriter.h"
16 #include "llvm/Object/WindowsResource.h"
17 #include "llvm/Support/ConvertUTF.h"
18 #include "llvm/Support/Endian.h"
19 #include "llvm/Support/EndianStream.h"
20 #include "llvm/Support/MemoryBuffer.h"
21 #include "llvm/Support/Path.h"
22 #include "llvm/Support/Process.h"
24 using namespace llvm::support;
26 // Take an expression returning llvm::Error and forward the error if it exists.
27 #define RETURN_IF_ERROR(Expr) \
28 if (auto Err = (Expr)) \
34 // Class that employs RAII to save the current FileWriter object state
35 // and revert to it as soon as we leave the scope. This is useful if resources
36 // declare their own resource-local statements.
38 ResourceFileWriter *FileWriter;
39 ResourceFileWriter::ObjectInfo SavedInfo;
42 ContextKeeper(ResourceFileWriter *V)
43 : FileWriter(V), SavedInfo(V->ObjectData) {}
44 ~ContextKeeper() { FileWriter->ObjectData = SavedInfo; }
47 static Error createError(const Twine &Message,
48 std::errc Type = std::errc::invalid_argument) {
49 return make_error<StringError>(Message, std::make_error_code(Type));
52 static Error checkNumberFits(uint32_t Number, size_t MaxBits,
53 const Twine &FieldName) {
54 assert(1 <= MaxBits && MaxBits <= 32);
55 if (!(Number >> MaxBits))
56 return Error::success();
57 return createError(FieldName + " (" + Twine(Number) + ") does not fit in " +
58 Twine(MaxBits) + " bits.",
59 std::errc::value_too_large);
62 template <typename FitType>
63 static Error checkNumberFits(uint32_t Number, const Twine &FieldName) {
64 return checkNumberFits(Number, sizeof(FitType) * 8, FieldName);
67 // A similar function for signed integers.
68 template <typename FitType>
69 static Error checkSignedNumberFits(uint32_t Number, const Twine &FieldName,
71 int32_t SignedNum = Number;
72 if (SignedNum < std::numeric_limits<FitType>::min() ||
73 SignedNum > std::numeric_limits<FitType>::max())
74 return createError(FieldName + " (" + Twine(SignedNum) +
75 ") does not fit in " + Twine(sizeof(FitType) * 8) +
76 "-bit signed integer type.",
77 std::errc::value_too_large);
79 if (!CanBeNegative && SignedNum < 0)
80 return createError(FieldName + " (" + Twine(SignedNum) +
81 ") cannot be negative.");
83 return Error::success();
86 static Error checkRCInt(RCInt Number, const Twine &FieldName) {
88 return Error::success();
89 return checkNumberFits<uint16_t>(Number, FieldName);
92 static Error checkIntOrString(IntOrString Value, const Twine &FieldName) {
94 return Error::success();
95 return checkNumberFits<uint16_t>(Value.getInt(), FieldName);
98 static bool stripQuotes(StringRef &Str, bool &IsLongString) {
99 if (!Str.contains('"'))
102 // Just take the contents of the string, checking if it's been marked long.
103 IsLongString = Str.startswith_lower("L");
105 Str = Str.drop_front();
107 bool StripSuccess = Str.consume_front("\"") && Str.consume_back("\"");
109 assert(StripSuccess && "Strings should be enclosed in quotes.");
113 static UTF16 cp1252ToUnicode(unsigned char C) {
114 static const UTF16 Map80[] = {
115 0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021,
116 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f,
117 0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014,
118 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178,
120 if (C >= 0x80 && C <= 0x9F)
121 return Map80[C - 0x80];
125 // Describes a way to handle '\0' characters when processing the string.
126 // rc.exe tool sometimes behaves in a weird way in postprocessing.
127 // If the string to be output is equivalent to a C-string (e.g. in MENU
128 // titles), string is (predictably) truncated after first 0-byte.
129 // When outputting a string table, the behavior is equivalent to appending
130 // '\0\0' at the end of the string, and then stripping the string
131 // before the first '\0\0' occurrence.
132 // Finally, when handling strings in user-defined resources, 0-bytes
133 // aren't stripped, nor do they terminate the string.
135 enum class NullHandlingMethod {
136 UserResource, // Don't terminate string on '\0'.
137 CutAtNull, // Terminate string on '\0'.
138 CutAtDoubleNull // Terminate string on '\0\0'; strip final '\0'.
141 // Parses an identifier or string and returns a processed version of it:
142 // * String the string boundary quotes.
143 // * Squash "" to a single ".
144 // * Replace the escape sequences with their processed version.
145 // For identifiers, this is no-op.
146 static Error processString(StringRef Str, NullHandlingMethod NullHandler,
147 bool &IsLongString, SmallVectorImpl<UTF16> &Result,
149 bool IsString = stripQuotes(Str, IsLongString);
150 SmallVector<UTF16, 128> Chars;
152 // Convert the input bytes according to the chosen codepage.
153 if (CodePage == CpUtf8) {
154 convertUTF8ToUTF16String(Str, Chars);
155 } else if (CodePage == CpWin1252) {
157 Chars.push_back(cp1252ToUnicode((unsigned char)C));
159 // For other, unknown codepages, only allow plain ASCII input.
161 if ((unsigned char)C > 0x7F)
162 return createError("Non-ASCII 8-bit codepoint (" + Twine(C) +
163 ") can't be interpreted in the current codepage");
164 Chars.push_back((unsigned char)C);
169 // It's an identifier if it's not a string. Make all characters uppercase.
170 for (UTF16 &Ch : Chars) {
171 assert(Ch <= 0x7F && "We didn't allow identifiers to be non-ASCII");
175 return Error::success();
177 Result.reserve(Chars.size());
180 auto AddRes = [&Result, NullHandler, IsLongString](UTF16 Char) -> Error {
182 if (NullHandler == NullHandlingMethod::UserResource) {
183 // Narrow strings in user-defined resources are *not* output in
186 return createError("Non-8-bit codepoint (" + Twine(Char) +
187 ") can't occur in a user-defined narrow string");
191 Result.push_back(Char);
192 return Error::success();
194 auto AddEscapedChar = [AddRes, IsLongString, CodePage](UTF16 Char) -> Error {
196 // Escaped chars in narrow strings have to be interpreted according to
197 // the chosen code page.
199 return createError("Non-8-bit escaped char (" + Twine(Char) +
200 ") can't occur in narrow string");
201 if (CodePage == CpUtf8) {
203 return createError("Unable to interpret single byte (" + Twine(Char) +
205 } else if (CodePage == CpWin1252) {
206 Char = cp1252ToUnicode(Char);
208 // Unknown/unsupported codepage, only allow ASCII input.
210 return createError("Non-ASCII 8-bit codepoint (" + Twine(Char) +
212 "occur in a non-Unicode string");
219 while (Pos < Chars.size()) {
220 UTF16 CurChar = Chars[Pos];
224 if (CurChar == '"') {
225 if (Pos == Chars.size() || Chars[Pos] != '"')
226 return createError("Expected \"\"");
228 RETURN_IF_ERROR(AddRes('"'));
232 if (CurChar == '\\') {
233 UTF16 TypeChar = Chars[Pos];
236 if (TypeChar == 'x' || TypeChar == 'X') {
237 // Read a hex number. Max number of characters to read differs between
238 // narrow and wide strings.
240 size_t RemainingChars = IsLongString ? 4 : 2;
241 // We don't want to read non-ASCII hex digits. std:: functions past
244 // FIXME: actually, Microsoft version probably doesn't check this
245 // condition and uses their Unicode version of 'isxdigit'. However,
246 // there are some hex-digit Unicode character outside of ASCII, and
247 // some of these are actually accepted by rc.exe, the notable example
248 // being fullwidth forms (U+FF10..U+FF19 etc.) These can be written
249 // instead of ASCII digits in \x... escape sequence and get accepted.
250 // However, the resulting hexcodes seem totally unpredictable.
251 // We think it's infeasible to try to reproduce this behavior, nor to
252 // put effort in order to detect it.
253 while (RemainingChars && Pos < Chars.size() && Chars[Pos] < 0x80) {
254 if (!isxdigit(Chars[Pos]))
256 char Digit = tolower(Chars[Pos]);
261 ReadInt |= Digit - '0';
263 ReadInt |= Digit - 'a' + 10;
268 RETURN_IF_ERROR(AddEscapedChar(ReadInt));
272 if (TypeChar >= '0' && TypeChar < '8') {
273 // Read an octal number. Note that we've already read the first digit.
274 UTF16 ReadInt = TypeChar - '0';
275 size_t RemainingChars = IsLongString ? 6 : 2;
277 while (RemainingChars && Pos < Chars.size() && Chars[Pos] >= '0' &&
280 ReadInt |= Chars[Pos] - '0';
285 RETURN_IF_ERROR(AddEscapedChar(ReadInt));
293 // Windows '\a' translates into '\b' (Backspace).
294 RETURN_IF_ERROR(AddRes('\b'));
297 case 'n': // Somehow, RC doesn't recognize '\N' and '\R'.
298 RETURN_IF_ERROR(AddRes('\n'));
302 RETURN_IF_ERROR(AddRes('\r'));
307 RETURN_IF_ERROR(AddRes('\t'));
311 RETURN_IF_ERROR(AddRes('\\'));
315 // RC accepts \" only if another " comes afterwards; then, \"" means
317 if (Pos == Chars.size() || Chars[Pos] != '"')
318 return createError("Expected \\\"\"");
320 RETURN_IF_ERROR(AddRes('"'));
324 // If TypeChar means nothing, \ is should be output to stdout with
325 // following char. However, rc.exe consumes these characters when
326 // dealing with wide strings.
328 RETURN_IF_ERROR(AddRes('\\'));
329 RETURN_IF_ERROR(AddRes(TypeChar));
337 // If nothing interesting happens, just output the character.
338 RETURN_IF_ERROR(AddRes(CurChar));
341 switch (NullHandler) {
342 case NullHandlingMethod::CutAtNull:
343 for (size_t Pos = 0; Pos < Result.size(); ++Pos)
344 if (Result[Pos] == '\0')
348 case NullHandlingMethod::CutAtDoubleNull:
349 for (size_t Pos = 0; Pos + 1 < Result.size(); ++Pos)
350 if (Result[Pos] == '\0' && Result[Pos + 1] == '\0')
352 if (Result.size() > 0 && Result.back() == '\0')
356 case NullHandlingMethod::UserResource:
360 return Error::success();
363 uint64_t ResourceFileWriter::writeObject(const ArrayRef<uint8_t> Data) {
364 uint64_t Result = tell();
365 FS->write((const char *)Data.begin(), Data.size());
369 Error ResourceFileWriter::writeCString(StringRef Str, bool WriteTerminator) {
370 SmallVector<UTF16, 128> ProcessedString;
372 RETURN_IF_ERROR(processString(Str, NullHandlingMethod::CutAtNull,
373 IsLongString, ProcessedString,
375 for (auto Ch : ProcessedString)
376 writeInt<uint16_t>(Ch);
378 writeInt<uint16_t>(0);
379 return Error::success();
382 Error ResourceFileWriter::writeIdentifier(const IntOrString &Ident) {
383 return writeIntOrString(Ident);
386 Error ResourceFileWriter::writeIntOrString(const IntOrString &Value) {
388 return writeCString(Value.getString());
390 writeInt<uint16_t>(0xFFFF);
391 writeInt<uint16_t>(Value.getInt());
392 return Error::success();
395 void ResourceFileWriter::writeRCInt(RCInt Value) {
397 writeInt<uint32_t>(Value);
399 writeInt<uint16_t>(Value);
402 Error ResourceFileWriter::appendFile(StringRef Filename) {
404 stripQuotes(Filename, IsLong);
406 auto File = loadFile(Filename);
408 return File.takeError();
410 *FS << (*File)->getBuffer();
411 return Error::success();
414 void ResourceFileWriter::padStream(uint64_t Length) {
416 uint64_t Location = tell();
418 uint64_t Pad = (Length - Location) % Length;
419 for (uint64_t i = 0; i < Pad; ++i)
420 writeInt<uint8_t>(0);
423 Error ResourceFileWriter::handleError(Error Err, const RCResource *Res) {
425 return joinErrors(createError("Error in " + Res->getResourceTypeName() +
426 " statement (ID " + Twine(Res->ResName) +
429 return Error::success();
432 Error ResourceFileWriter::visitNullResource(const RCResource *Res) {
433 return writeResource(Res, &ResourceFileWriter::writeNullBody);
436 Error ResourceFileWriter::visitAcceleratorsResource(const RCResource *Res) {
437 return writeResource(Res, &ResourceFileWriter::writeAcceleratorsBody);
440 Error ResourceFileWriter::visitBitmapResource(const RCResource *Res) {
441 return writeResource(Res, &ResourceFileWriter::writeBitmapBody);
444 Error ResourceFileWriter::visitCursorResource(const RCResource *Res) {
445 return handleError(visitIconOrCursorResource(Res), Res);
448 Error ResourceFileWriter::visitDialogResource(const RCResource *Res) {
449 return writeResource(Res, &ResourceFileWriter::writeDialogBody);
452 Error ResourceFileWriter::visitIconResource(const RCResource *Res) {
453 return handleError(visitIconOrCursorResource(Res), Res);
456 Error ResourceFileWriter::visitCaptionStmt(const CaptionStmt *Stmt) {
457 ObjectData.Caption = Stmt->Value;
458 return Error::success();
461 Error ResourceFileWriter::visitHTMLResource(const RCResource *Res) {
462 return writeResource(Res, &ResourceFileWriter::writeHTMLBody);
465 Error ResourceFileWriter::visitMenuResource(const RCResource *Res) {
466 return writeResource(Res, &ResourceFileWriter::writeMenuBody);
469 Error ResourceFileWriter::visitStringTableResource(const RCResource *Base) {
470 const auto *Res = cast<StringTableResource>(Base);
472 ContextKeeper RAII(this);
473 RETURN_IF_ERROR(Res->applyStmts(this));
475 for (auto &String : Res->Table) {
476 RETURN_IF_ERROR(checkNumberFits<uint16_t>(String.first, "String ID"));
477 uint16_t BundleID = String.first >> 4;
478 StringTableInfo::BundleKey Key(BundleID, ObjectData.LanguageInfo);
479 auto &BundleData = StringTableData.BundleData;
480 auto Iter = BundleData.find(Key);
482 if (Iter == BundleData.end()) {
483 // Need to create a bundle.
484 StringTableData.BundleList.push_back(Key);
486 BundleData.emplace(Key, StringTableInfo::Bundle(ObjectData));
487 assert(EmplaceResult.second && "Could not create a bundle");
488 Iter = EmplaceResult.first;
492 insertStringIntoBundle(Iter->second, String.first, String.second));
495 return Error::success();
498 Error ResourceFileWriter::visitUserDefinedResource(const RCResource *Res) {
499 return writeResource(Res, &ResourceFileWriter::writeUserDefinedBody);
502 Error ResourceFileWriter::visitVersionInfoResource(const RCResource *Res) {
503 return writeResource(Res, &ResourceFileWriter::writeVersionInfoBody);
506 Error ResourceFileWriter::visitCharacteristicsStmt(
507 const CharacteristicsStmt *Stmt) {
508 ObjectData.Characteristics = Stmt->Value;
509 return Error::success();
512 Error ResourceFileWriter::visitFontStmt(const FontStmt *Stmt) {
513 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Stmt->Size, "Font size"));
514 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Stmt->Weight, "Font weight"));
515 RETURN_IF_ERROR(checkNumberFits<uint8_t>(Stmt->Charset, "Font charset"));
516 ObjectInfo::FontInfo Font{Stmt->Size, Stmt->Name, Stmt->Weight, Stmt->Italic,
518 ObjectData.Font.emplace(Font);
519 return Error::success();
522 Error ResourceFileWriter::visitLanguageStmt(const LanguageResource *Stmt) {
523 RETURN_IF_ERROR(checkNumberFits(Stmt->Lang, 10, "Primary language ID"));
524 RETURN_IF_ERROR(checkNumberFits(Stmt->SubLang, 6, "Sublanguage ID"));
525 ObjectData.LanguageInfo = Stmt->Lang | (Stmt->SubLang << 10);
526 return Error::success();
529 Error ResourceFileWriter::visitStyleStmt(const StyleStmt *Stmt) {
530 ObjectData.Style = Stmt->Value;
531 return Error::success();
534 Error ResourceFileWriter::visitVersionStmt(const VersionStmt *Stmt) {
535 ObjectData.VersionInfo = Stmt->Value;
536 return Error::success();
539 Error ResourceFileWriter::writeResource(
540 const RCResource *Res,
541 Error (ResourceFileWriter::*BodyWriter)(const RCResource *)) {
542 // We don't know the sizes yet.
543 object::WinResHeaderPrefix HeaderPrefix{ulittle32_t(0U), ulittle32_t(0U)};
544 uint64_t HeaderLoc = writeObject(HeaderPrefix);
546 auto ResType = Res->getResourceType();
547 RETURN_IF_ERROR(checkIntOrString(ResType, "Resource type"));
548 RETURN_IF_ERROR(checkIntOrString(Res->ResName, "Resource ID"));
549 RETURN_IF_ERROR(handleError(writeIdentifier(ResType), Res));
550 RETURN_IF_ERROR(handleError(writeIdentifier(Res->ResName), Res));
552 // Apply the resource-local optional statements.
553 ContextKeeper RAII(this);
554 RETURN_IF_ERROR(handleError(Res->applyStmts(this), Res));
556 padStream(sizeof(uint32_t));
557 object::WinResHeaderSuffix HeaderSuffix{
558 ulittle32_t(0), // DataVersion; seems to always be 0
559 ulittle16_t(Res->getMemoryFlags()), ulittle16_t(ObjectData.LanguageInfo),
560 ulittle32_t(ObjectData.VersionInfo),
561 ulittle32_t(ObjectData.Characteristics)};
562 writeObject(HeaderSuffix);
564 uint64_t DataLoc = tell();
565 RETURN_IF_ERROR(handleError((this->*BodyWriter)(Res), Res));
566 // RETURN_IF_ERROR(handleError(dumpResource(Ctx)));
569 HeaderPrefix.DataSize = tell() - DataLoc;
570 HeaderPrefix.HeaderSize = DataLoc - HeaderLoc;
571 writeObjectAt(HeaderPrefix, HeaderLoc);
572 padStream(sizeof(uint32_t));
574 return Error::success();
577 // --- NullResource helpers. --- //
579 Error ResourceFileWriter::writeNullBody(const RCResource *) {
580 return Error::success();
583 // --- AcceleratorsResource helpers. --- //
585 Error ResourceFileWriter::writeSingleAccelerator(
586 const AcceleratorsResource::Accelerator &Obj, bool IsLastItem) {
587 using Accelerator = AcceleratorsResource::Accelerator;
588 using Opt = Accelerator::Options;
590 struct AccelTableEntry {
592 ulittle16_t ANSICode;
595 } Entry{ulittle16_t(0), ulittle16_t(0), ulittle16_t(0), 0};
597 bool IsASCII = Obj.Flags & Opt::ASCII, IsVirtKey = Obj.Flags & Opt::VIRTKEY;
599 // Remove ASCII flags (which doesn't occur in .res files).
600 Entry.Flags = Obj.Flags & ~Opt::ASCII;
605 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Obj.Id, "ACCELERATORS entry ID"));
606 Entry.Id = ulittle16_t(Obj.Id);
608 auto createAccError = [&Obj](const char *Msg) {
609 return createError("Accelerator ID " + Twine(Obj.Id) + ": " + Msg);
612 if (IsASCII && IsVirtKey)
613 return createAccError("Accelerator can't be both ASCII and VIRTKEY");
615 if (!IsVirtKey && (Obj.Flags & (Opt::ALT | Opt::SHIFT | Opt::CONTROL)))
616 return createAccError("Can only apply ALT, SHIFT or CONTROL to VIRTKEY"
619 if (Obj.Event.isInt()) {
620 if (!IsASCII && !IsVirtKey)
621 return createAccError(
622 "Accelerator with a numeric event must be either ASCII"
625 uint32_t EventVal = Obj.Event.getInt();
627 checkNumberFits<uint16_t>(EventVal, "Numeric event key ID"));
628 Entry.ANSICode = ulittle16_t(EventVal);
630 return Error::success();
633 StringRef Str = Obj.Event.getString();
635 stripQuotes(Str, IsWide);
637 if (Str.size() == 0 || Str.size() > 2)
638 return createAccError(
639 "Accelerator string events should have length 1 or 2");
643 return createAccError("No character following '^' in accelerator event");
645 return createAccError(
646 "VIRTKEY accelerator events can't be preceded by '^'");
649 if (Ch >= 'a' && Ch <= 'z')
650 Entry.ANSICode = ulittle16_t(Ch - 'a' + 1);
651 else if (Ch >= 'A' && Ch <= 'Z')
652 Entry.ANSICode = ulittle16_t(Ch - 'A' + 1);
654 return createAccError("Control character accelerator event should be"
658 return Error::success();
662 return createAccError("Event string should be one-character, possibly"
665 uint8_t EventCh = Str[0];
666 // The original tool just warns in this situation. We chose to fail.
667 if (IsVirtKey && !isalnum(EventCh))
668 return createAccError("Non-alphanumeric characters cannot describe virtual"
671 return createAccError("Non-ASCII description of accelerator");
674 EventCh = toupper(EventCh);
675 Entry.ANSICode = ulittle16_t(EventCh);
677 return Error::success();
680 Error ResourceFileWriter::writeAcceleratorsBody(const RCResource *Base) {
681 auto *Res = cast<AcceleratorsResource>(Base);
682 size_t AcceleratorId = 0;
683 for (auto &Acc : Res->Accelerators) {
686 writeSingleAccelerator(Acc, AcceleratorId == Res->Accelerators.size()));
688 return Error::success();
691 // --- BitmapResource helpers. --- //
693 Error ResourceFileWriter::writeBitmapBody(const RCResource *Base) {
694 StringRef Filename = cast<BitmapResource>(Base)->BitmapLoc;
696 stripQuotes(Filename, IsLong);
698 auto File = loadFile(Filename);
700 return File.takeError();
702 StringRef Buffer = (*File)->getBuffer();
704 // Skip the 14 byte BITMAPFILEHEADER.
705 constexpr size_t BITMAPFILEHEADER_size = 14;
706 if (Buffer.size() < BITMAPFILEHEADER_size || Buffer[0] != 'B' ||
708 return createError("Incorrect bitmap file.");
710 *FS << Buffer.substr(BITMAPFILEHEADER_size);
711 return Error::success();
714 // --- CursorResource and IconResource helpers. --- //
716 // ICONRESDIR structure. Describes a single icon in resouce group.
718 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648016.aspx
726 // CURSORDIR structure. Describes a single cursor in resource group.
728 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648011(v=vs.85).aspx
734 // RESDIRENTRY structure, stripped from the last item. Stripping made
735 // for compatibility with RESDIR.
737 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026(v=vs.85).aspx
738 struct ResourceDirEntryStart {
740 CursorDir Cursor; // Used in CURSOR resources.
741 IconResDir Icon; // Used in .ico and .cur files, and ICON resources.
743 ulittle16_t Planes; // HotspotX (.cur files but not CURSOR resource).
744 ulittle16_t BitCount; // HotspotY (.cur files but not CURSOR resource).
746 // ulittle32_t ImageOffset; // Offset to image data (ICONDIRENTRY only).
747 // ulittle16_t IconID; // Resource icon ID (RESDIR only).
750 // BITMAPINFOHEADER structure. Describes basic information about the bitmap
753 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
754 struct BitmapInfoHeader {
759 ulittle16_t BitCount;
760 ulittle32_t Compression;
761 ulittle32_t SizeImage;
762 ulittle32_t XPelsPerMeter;
763 ulittle32_t YPelsPerMeter;
765 ulittle32_t ClrImportant;
768 // Group icon directory header. Called ICONDIR in .ico/.cur files and
769 // NEWHEADER in .res files.
771 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648023(v=vs.85).aspx
772 struct GroupIconDir {
773 ulittle16_t Reserved; // Always 0.
774 ulittle16_t ResType; // 1 for icons, 2 for cursors.
775 ulittle16_t ResCount; // Number of items.
778 enum class IconCursorGroupType { Icon, Cursor };
780 class SingleIconCursorResource : public RCResource {
782 IconCursorGroupType Type;
783 const ResourceDirEntryStart &Header;
784 ArrayRef<uint8_t> Image;
786 SingleIconCursorResource(IconCursorGroupType ResourceType,
787 const ResourceDirEntryStart &HeaderEntry,
788 ArrayRef<uint8_t> ImageData)
789 : Type(ResourceType), Header(HeaderEntry), Image(ImageData) {}
791 Twine getResourceTypeName() const override { return "Icon/cursor image"; }
792 IntOrString getResourceType() const override {
793 return Type == IconCursorGroupType::Icon ? RkSingleIcon : RkSingleCursor;
795 uint16_t getMemoryFlags() const override {
796 return MfDiscardable | MfMoveable;
798 ResourceKind getKind() const override { return RkSingleCursorOrIconRes; }
799 static bool classof(const RCResource *Res) {
800 return Res->getKind() == RkSingleCursorOrIconRes;
804 class IconCursorGroupResource : public RCResource {
806 IconCursorGroupType Type;
808 std::vector<ResourceDirEntryStart> ItemEntries;
810 IconCursorGroupResource(IconCursorGroupType ResourceType,
811 const GroupIconDir &HeaderData,
812 std::vector<ResourceDirEntryStart> &&Entries)
813 : Type(ResourceType), Header(HeaderData),
814 ItemEntries(std::move(Entries)) {}
816 Twine getResourceTypeName() const override { return "Icon/cursor group"; }
817 IntOrString getResourceType() const override {
818 return Type == IconCursorGroupType::Icon ? RkIconGroup : RkCursorGroup;
820 ResourceKind getKind() const override { return RkCursorOrIconGroupRes; }
821 static bool classof(const RCResource *Res) {
822 return Res->getKind() == RkCursorOrIconGroupRes;
826 Error ResourceFileWriter::writeSingleIconOrCursorBody(const RCResource *Base) {
827 auto *Res = cast<SingleIconCursorResource>(Base);
828 if (Res->Type == IconCursorGroupType::Cursor) {
829 // In case of cursors, two WORDS are appended to the beginning
830 // of the resource: HotspotX (Planes in RESDIRENTRY),
831 // and HotspotY (BitCount).
833 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026.aspx
834 // (Remarks section).
835 writeObject(Res->Header.Planes);
836 writeObject(Res->Header.BitCount);
839 writeObject(Res->Image);
840 return Error::success();
843 Error ResourceFileWriter::writeIconOrCursorGroupBody(const RCResource *Base) {
844 auto *Res = cast<IconCursorGroupResource>(Base);
845 writeObject(Res->Header);
846 for (auto Item : Res->ItemEntries) {
848 writeInt(IconCursorID++);
850 return Error::success();
853 Error ResourceFileWriter::visitSingleIconOrCursor(const RCResource *Res) {
854 return writeResource(Res, &ResourceFileWriter::writeSingleIconOrCursorBody);
857 Error ResourceFileWriter::visitIconOrCursorGroup(const RCResource *Res) {
858 return writeResource(Res, &ResourceFileWriter::writeIconOrCursorGroupBody);
861 Error ResourceFileWriter::visitIconOrCursorResource(const RCResource *Base) {
862 IconCursorGroupType Type;
864 IntOrString ResName = Base->ResName;
866 if (auto *IconRes = dyn_cast<IconResource>(Base)) {
867 FileStr = IconRes->IconLoc;
868 Type = IconCursorGroupType::Icon;
870 auto *CursorRes = dyn_cast<CursorResource>(Base);
871 FileStr = CursorRes->CursorLoc;
872 Type = IconCursorGroupType::Cursor;
876 stripQuotes(FileStr, IsLong);
877 auto File = loadFile(FileStr);
880 return File.takeError();
882 BinaryStreamReader Reader((*File)->getBuffer(), support::little);
884 // Read the file headers.
885 // - At the beginning, ICONDIR/NEWHEADER header.
886 // - Then, a number of RESDIR headers follow. These contain offsets
888 const GroupIconDir *Header;
890 RETURN_IF_ERROR(Reader.readObject(Header));
891 if (Header->Reserved != 0)
892 return createError("Incorrect icon/cursor Reserved field; should be 0.");
893 uint16_t NeededType = Type == IconCursorGroupType::Icon ? 1 : 2;
894 if (Header->ResType != NeededType)
895 return createError("Incorrect icon/cursor ResType field; should be " +
896 Twine(NeededType) + ".");
898 uint16_t NumItems = Header->ResCount;
900 // Read single ico/cur headers.
901 std::vector<ResourceDirEntryStart> ItemEntries;
902 ItemEntries.reserve(NumItems);
903 std::vector<uint32_t> ItemOffsets(NumItems);
904 for (size_t ID = 0; ID < NumItems; ++ID) {
905 const ResourceDirEntryStart *Object;
906 RETURN_IF_ERROR(Reader.readObject(Object));
907 ItemEntries.push_back(*Object);
908 RETURN_IF_ERROR(Reader.readInteger(ItemOffsets[ID]));
911 // Now write each icon/cursors one by one. At first, all the contents
912 // without ICO/CUR header. This is described by SingleIconCursorResource.
913 for (size_t ID = 0; ID < NumItems; ++ID) {
914 // Load the fragment of file.
915 Reader.setOffset(ItemOffsets[ID]);
916 ArrayRef<uint8_t> Image;
917 RETURN_IF_ERROR(Reader.readArray(Image, ItemEntries[ID].Size));
918 SingleIconCursorResource SingleRes(Type, ItemEntries[ID], Image);
919 SingleRes.setName(IconCursorID + ID);
920 RETURN_IF_ERROR(visitSingleIconOrCursor(&SingleRes));
923 // Now, write all the headers concatenated into a separate resource.
924 for (size_t ID = 0; ID < NumItems; ++ID) {
925 if (Type == IconCursorGroupType::Icon) {
926 // rc.exe seems to always set NumPlanes to 1. No idea why it happens.
927 ItemEntries[ID].Planes = 1;
931 // We need to rewrite the cursor headers.
932 const auto &OldHeader = ItemEntries[ID];
933 ResourceDirEntryStart NewHeader;
934 NewHeader.Cursor.Width = OldHeader.Icon.Width;
935 // Each cursor in fact stores two bitmaps, one under another.
936 // Height provided in cursor definition describes the height of the
937 // cursor, whereas the value existing in resource definition describes
938 // the height of the bitmap. Therefore, we need to double this height.
939 NewHeader.Cursor.Height = OldHeader.Icon.Height * 2;
941 // Now, we actually need to read the bitmap header to find
942 // the number of planes and the number of bits per pixel.
943 Reader.setOffset(ItemOffsets[ID]);
944 const BitmapInfoHeader *BMPHeader;
945 RETURN_IF_ERROR(Reader.readObject(BMPHeader));
946 NewHeader.Planes = BMPHeader->Planes;
947 NewHeader.BitCount = BMPHeader->BitCount;
949 // Two WORDs were written at the beginning of the resource (hotspot
950 // location). This is reflected in Size field.
951 NewHeader.Size = OldHeader.Size + 2 * sizeof(uint16_t);
953 ItemEntries[ID] = NewHeader;
956 IconCursorGroupResource HeaderRes(Type, *Header, std::move(ItemEntries));
957 HeaderRes.setName(ResName);
958 RETURN_IF_ERROR(visitIconOrCursorGroup(&HeaderRes));
960 return Error::success();
963 // --- DialogResource helpers. --- //
965 Error ResourceFileWriter::writeSingleDialogControl(const Control &Ctl,
967 // Each control should be aligned to DWORD.
968 padStream(sizeof(uint32_t));
970 auto TypeInfo = Control::SupportedCtls.lookup(Ctl.Type);
971 uint32_t CtlStyle = TypeInfo.Style | Ctl.Style.getValueOr(0);
972 uint32_t CtlExtStyle = Ctl.ExtStyle.getValueOr(0);
974 // DIALOG(EX) item header prefix.
978 ulittle32_t ExtStyle;
979 } Prefix{ulittle32_t(CtlStyle), ulittle32_t(CtlExtStyle)};
984 ulittle32_t ExtStyle;
986 } Prefix{ulittle32_t(Ctl.HelpID.getValueOr(0)), ulittle32_t(CtlExtStyle),
987 ulittle32_t(CtlStyle)};
991 // Common fixed-length part.
992 RETURN_IF_ERROR(checkSignedNumberFits<int16_t>(
993 Ctl.X, "Dialog control x-coordinate", true));
994 RETURN_IF_ERROR(checkSignedNumberFits<int16_t>(
995 Ctl.Y, "Dialog control y-coordinate", true));
997 checkSignedNumberFits<int16_t>(Ctl.Width, "Dialog control width", false));
998 RETURN_IF_ERROR(checkSignedNumberFits<int16_t>(
999 Ctl.Height, "Dialog control height", false));
1005 } Middle{ulittle16_t(Ctl.X), ulittle16_t(Ctl.Y), ulittle16_t(Ctl.Width),
1006 ulittle16_t(Ctl.Height)};
1007 writeObject(Middle);
1009 // ID; it's 16-bit in DIALOG and 32-bit in DIALOGEX.
1011 RETURN_IF_ERROR(checkNumberFits<uint16_t>(
1012 Ctl.ID, "Control ID in simple DIALOG resource"));
1013 writeInt<uint16_t>(Ctl.ID);
1015 writeInt<uint32_t>(Ctl.ID);
1018 // Window class - either 0xFFFF + 16-bit integer or a string.
1019 RETURN_IF_ERROR(writeIntOrString(Ctl.Class));
1021 // Element caption/reference ID. ID is preceded by 0xFFFF.
1022 RETURN_IF_ERROR(checkIntOrString(Ctl.Title, "Control reference ID"));
1023 RETURN_IF_ERROR(writeIntOrString(Ctl.Title));
1025 // # bytes of extra creation data count. Don't pass any.
1026 writeInt<uint16_t>(0);
1028 return Error::success();
1031 Error ResourceFileWriter::writeDialogBody(const RCResource *Base) {
1032 auto *Res = cast<DialogResource>(Base);
1034 // Default style: WS_POPUP | WS_BORDER | WS_SYSMENU.
1035 const uint32_t DefaultStyle = 0x80880000;
1036 const uint32_t StyleFontFlag = 0x40;
1037 const uint32_t StyleCaptionFlag = 0x00C00000;
1039 uint32_t UsedStyle = ObjectData.Style.getValueOr(DefaultStyle);
1040 if (ObjectData.Font)
1041 UsedStyle |= StyleFontFlag;
1043 UsedStyle &= ~StyleFontFlag;
1045 // Actually, in case of empty (but existent) caption, the examined field
1046 // is equal to "\"\"". That's why empty captions are still noticed.
1047 if (ObjectData.Caption != "")
1048 UsedStyle |= StyleCaptionFlag;
1050 const uint16_t DialogExMagic = 0xFFFF;
1052 // Write DIALOG(EX) header prefix. These are pretty different.
1053 if (!Res->IsExtended) {
1054 // We cannot let the higher word of DefaultStyle be equal to 0xFFFF.
1055 // In such a case, whole object (in .res file) is equivalent to a
1056 // DIALOGEX. It might lead to access violation/segmentation fault in
1057 // resource readers. For example,
1058 // 1 DIALOG 0, 0, 0, 65432
1059 // STYLE 0xFFFF0001 {}
1060 // would be compiled to a DIALOGEX with 65432 controls.
1061 if ((UsedStyle >> 16) == DialogExMagic)
1062 return createError("16 higher bits of DIALOG resource style cannot be"
1063 " equal to 0xFFFF");
1067 ulittle32_t ExtStyle;
1068 } Prefix{ulittle32_t(UsedStyle),
1069 ulittle32_t(0)}; // As of now, we don't keep EXSTYLE.
1071 writeObject(Prefix);
1074 ulittle16_t Version;
1077 ulittle32_t ExtStyle;
1079 } Prefix{ulittle16_t(1), ulittle16_t(DialogExMagic),
1080 ulittle32_t(Res->HelpID), ulittle32_t(0), ulittle32_t(UsedStyle)};
1082 writeObject(Prefix);
1085 // Now, a common part. First, fixed-length fields.
1086 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Res->Controls.size(),
1087 "Number of dialog controls"));
1089 checkSignedNumberFits<int16_t>(Res->X, "Dialog x-coordinate", true));
1091 checkSignedNumberFits<int16_t>(Res->Y, "Dialog y-coordinate", true));
1093 checkSignedNumberFits<int16_t>(Res->Width, "Dialog width", false));
1095 checkSignedNumberFits<int16_t>(Res->Height, "Dialog height", false));
1100 ulittle16_t DialogWidth;
1101 ulittle16_t DialogHeight;
1102 } Middle{ulittle16_t(Res->Controls.size()), ulittle16_t(Res->X),
1103 ulittle16_t(Res->Y), ulittle16_t(Res->Width),
1104 ulittle16_t(Res->Height)};
1105 writeObject(Middle);
1107 // MENU field. As of now, we don't keep them in the state and can peacefully
1108 // think there is no menu attached to the dialog.
1109 writeInt<uint16_t>(0);
1111 // Window CLASS field. Not kept here.
1112 writeInt<uint16_t>(0);
1114 // Window title or a single word equal to 0.
1115 RETURN_IF_ERROR(writeCString(ObjectData.Caption));
1117 // If there *is* a window font declared, output its data.
1118 auto &Font = ObjectData.Font;
1120 writeInt<uint16_t>(Font->Size);
1121 // Additional description occurs only in DIALOGEX.
1122 if (Res->IsExtended) {
1123 writeInt<uint16_t>(Font->Weight);
1124 writeInt<uint8_t>(Font->IsItalic);
1125 writeInt<uint8_t>(Font->Charset);
1127 RETURN_IF_ERROR(writeCString(Font->Typeface));
1130 auto handleCtlError = [&](Error &&Err, const Control &Ctl) -> Error {
1132 return Error::success();
1133 return joinErrors(createError("Error in " + Twine(Ctl.Type) +
1134 " control (ID " + Twine(Ctl.ID) + "):"),
1138 for (auto &Ctl : Res->Controls)
1140 handleCtlError(writeSingleDialogControl(Ctl, Res->IsExtended), Ctl));
1142 return Error::success();
1145 // --- HTMLResource helpers. --- //
1147 Error ResourceFileWriter::writeHTMLBody(const RCResource *Base) {
1148 return appendFile(cast<HTMLResource>(Base)->HTMLLoc);
1151 // --- MenuResource helpers. --- //
1153 Error ResourceFileWriter::writeMenuDefinition(
1154 const std::unique_ptr<MenuDefinition> &Def, uint16_t Flags) {
1156 const MenuDefinition *DefPtr = Def.get();
1158 if (auto *MenuItemPtr = dyn_cast<MenuItem>(DefPtr)) {
1159 writeInt<uint16_t>(Flags);
1161 checkNumberFits<uint16_t>(MenuItemPtr->Id, "MENUITEM action ID"));
1162 writeInt<uint16_t>(MenuItemPtr->Id);
1163 RETURN_IF_ERROR(writeCString(MenuItemPtr->Name));
1164 return Error::success();
1167 if (isa<MenuSeparator>(DefPtr)) {
1168 writeInt<uint16_t>(Flags);
1169 writeInt<uint32_t>(0);
1170 return Error::success();
1173 auto *PopupPtr = cast<PopupItem>(DefPtr);
1174 writeInt<uint16_t>(Flags);
1175 RETURN_IF_ERROR(writeCString(PopupPtr->Name));
1176 return writeMenuDefinitionList(PopupPtr->SubItems);
1179 Error ResourceFileWriter::writeMenuDefinitionList(
1180 const MenuDefinitionList &List) {
1181 for (auto &Def : List.Definitions) {
1182 uint16_t Flags = Def->getResFlags();
1183 // Last element receives an additional 0x80 flag.
1184 const uint16_t LastElementFlag = 0x0080;
1185 if (&Def == &List.Definitions.back())
1186 Flags |= LastElementFlag;
1188 RETURN_IF_ERROR(writeMenuDefinition(Def, Flags));
1190 return Error::success();
1193 Error ResourceFileWriter::writeMenuBody(const RCResource *Base) {
1194 // At first, MENUHEADER structure. In fact, these are two WORDs equal to 0.
1195 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648018.aspx
1196 writeInt<uint32_t>(0);
1198 return writeMenuDefinitionList(cast<MenuResource>(Base)->Elements);
1201 // --- StringTableResource helpers. --- //
1203 class BundleResource : public RCResource {
1205 using BundleType = ResourceFileWriter::StringTableInfo::Bundle;
1208 BundleResource(const BundleType &StrBundle) : Bundle(StrBundle) {}
1209 IntOrString getResourceType() const override { return 6; }
1211 ResourceKind getKind() const override { return RkStringTableBundle; }
1212 static bool classof(const RCResource *Res) {
1213 return Res->getKind() == RkStringTableBundle;
1215 Twine getResourceTypeName() const override { return "STRINGTABLE"; }
1218 Error ResourceFileWriter::visitStringTableBundle(const RCResource *Res) {
1219 return writeResource(Res, &ResourceFileWriter::writeStringTableBundleBody);
1222 Error ResourceFileWriter::insertStringIntoBundle(
1223 StringTableInfo::Bundle &Bundle, uint16_t StringID, StringRef String) {
1224 uint16_t StringLoc = StringID & 15;
1225 if (Bundle.Data[StringLoc])
1226 return createError("Multiple STRINGTABLE strings located under ID " +
1228 Bundle.Data[StringLoc] = String;
1229 return Error::success();
1232 Error ResourceFileWriter::writeStringTableBundleBody(const RCResource *Base) {
1233 auto *Res = cast<BundleResource>(Base);
1234 for (size_t ID = 0; ID < Res->Bundle.Data.size(); ++ID) {
1235 // The string format is a tiny bit different here. We
1236 // first output the size of the string, and then the string itself
1237 // (which is not null-terminated).
1239 SmallVector<UTF16, 128> Data;
1240 RETURN_IF_ERROR(processString(Res->Bundle.Data[ID].getValueOr(StringRef()),
1241 NullHandlingMethod::CutAtDoubleNull,
1242 IsLongString, Data, Params.CodePage));
1243 if (AppendNull && Res->Bundle.Data[ID])
1244 Data.push_back('\0');
1246 checkNumberFits<uint16_t>(Data.size(), "STRINGTABLE string size"));
1247 writeInt<uint16_t>(Data.size());
1248 for (auto Char : Data)
1251 return Error::success();
1254 Error ResourceFileWriter::dumpAllStringTables() {
1255 for (auto Key : StringTableData.BundleList) {
1256 auto Iter = StringTableData.BundleData.find(Key);
1257 assert(Iter != StringTableData.BundleData.end());
1259 // For a moment, revert the context info to moment of bundle declaration.
1260 ContextKeeper RAII(this);
1261 ObjectData = Iter->second.DeclTimeInfo;
1263 BundleResource Res(Iter->second);
1264 // Bundle #(k+1) contains keys [16k, 16k + 15].
1265 Res.setName(Key.first + 1);
1266 RETURN_IF_ERROR(visitStringTableBundle(&Res));
1268 return Error::success();
1271 // --- UserDefinedResource helpers. --- //
1273 Error ResourceFileWriter::writeUserDefinedBody(const RCResource *Base) {
1274 auto *Res = cast<UserDefinedResource>(Base);
1276 if (Res->IsFileResource)
1277 return appendFile(Res->FileLoc);
1279 for (auto &Elem : Res->Contents) {
1282 checkRCInt(Elem.getInt(), "Number in user-defined resource"));
1283 writeRCInt(Elem.getInt());
1287 SmallVector<UTF16, 128> ProcessedString;
1290 processString(Elem.getString(), NullHandlingMethod::UserResource,
1291 IsLongString, ProcessedString, Params.CodePage));
1293 for (auto Ch : ProcessedString) {
1299 RETURN_IF_ERROR(checkNumberFits<uint8_t>(
1300 Ch, "Character in narrow string in user-defined resource"));
1301 writeInt<uint8_t>(Ch);
1305 return Error::success();
1308 // --- VersionInfoResourceResource helpers. --- //
1310 Error ResourceFileWriter::writeVersionInfoBlock(const VersionInfoBlock &Blk) {
1311 // Output the header if the block has name.
1312 bool OutputHeader = Blk.Name != "";
1315 padStream(sizeof(uint32_t));
1317 LengthLoc = writeInt<uint16_t>(0);
1318 writeInt<uint16_t>(0);
1319 writeInt<uint16_t>(1); // true
1320 RETURN_IF_ERROR(writeCString(Blk.Name));
1321 padStream(sizeof(uint32_t));
1324 for (const std::unique_ptr<VersionInfoStmt> &Item : Blk.Stmts) {
1325 VersionInfoStmt *ItemPtr = Item.get();
1327 if (auto *BlockPtr = dyn_cast<VersionInfoBlock>(ItemPtr)) {
1328 RETURN_IF_ERROR(writeVersionInfoBlock(*BlockPtr));
1332 auto *ValuePtr = cast<VersionInfoValue>(ItemPtr);
1333 RETURN_IF_ERROR(writeVersionInfoValue(*ValuePtr));
1337 uint64_t CurLoc = tell();
1338 writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc);
1341 return Error::success();
1344 Error ResourceFileWriter::writeVersionInfoValue(const VersionInfoValue &Val) {
1345 // rc has a peculiar algorithm to output VERSIONINFO VALUEs. Each VALUE
1346 // is a mapping from the key (string) to the value (a sequence of ints or
1347 // a sequence of strings).
1349 // If integers are to be written: width of each integer written depends on
1350 // whether it's been declared 'long' (it's DWORD then) or not (it's WORD).
1351 // ValueLength defined in structure referenced below is then the total
1352 // number of bytes taken by these integers.
1354 // If strings are to be written: characters are always WORDs.
1355 // Moreover, '\0' character is written after the last string, and between
1356 // every two strings separated by comma (if strings are not comma-separated,
1357 // they're simply concatenated). ValueLength is equal to the number of WORDs
1358 // written (that is, half of the bytes written).
1360 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms646994.aspx
1361 bool HasStrings = false, HasInts = false;
1362 for (auto &Item : Val.Values)
1363 (Item.isInt() ? HasInts : HasStrings) = true;
1365 assert((HasStrings || HasInts) && "VALUE must have at least one argument");
1366 if (HasStrings && HasInts)
1367 return createError(Twine("VALUE ") + Val.Key +
1368 " cannot contain both strings and integers");
1370 padStream(sizeof(uint32_t));
1371 auto LengthLoc = writeInt<uint16_t>(0);
1372 auto ValLengthLoc = writeInt<uint16_t>(0);
1373 writeInt<uint16_t>(HasStrings);
1374 RETURN_IF_ERROR(writeCString(Val.Key));
1375 padStream(sizeof(uint32_t));
1377 auto DataLoc = tell();
1378 for (size_t Id = 0; Id < Val.Values.size(); ++Id) {
1379 auto &Item = Val.Values[Id];
1381 auto Value = Item.getInt();
1382 RETURN_IF_ERROR(checkRCInt(Value, "VERSIONINFO integer value"));
1387 bool WriteTerminator =
1388 Id == Val.Values.size() - 1 || Val.HasPrecedingComma[Id + 1];
1389 RETURN_IF_ERROR(writeCString(Item.getString(), WriteTerminator));
1392 auto CurLoc = tell();
1393 auto ValueLength = CurLoc - DataLoc;
1395 assert(ValueLength % 2 == 0);
1398 writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc);
1399 writeObjectAt(ulittle16_t(ValueLength), ValLengthLoc);
1400 return Error::success();
1403 template <typename Ty>
1404 static Ty getWithDefault(const StringMap<Ty> &Map, StringRef Key,
1405 const Ty &Default) {
1406 auto Iter = Map.find(Key);
1407 if (Iter != Map.end())
1408 return Iter->getValue();
1412 Error ResourceFileWriter::writeVersionInfoBody(const RCResource *Base) {
1413 auto *Res = cast<VersionInfoResource>(Base);
1415 const auto &FixedData = Res->FixedData;
1417 struct /* VS_FIXEDFILEINFO */ {
1418 ulittle32_t Signature = ulittle32_t(0xFEEF04BD);
1419 ulittle32_t StructVersion = ulittle32_t(0x10000);
1420 // It's weird to have most-significant DWORD first on the little-endian
1421 // machines, but let it be this way.
1422 ulittle32_t FileVersionMS;
1423 ulittle32_t FileVersionLS;
1424 ulittle32_t ProductVersionMS;
1425 ulittle32_t ProductVersionLS;
1426 ulittle32_t FileFlagsMask;
1427 ulittle32_t FileFlags;
1429 ulittle32_t FileType;
1430 ulittle32_t FileSubtype;
1431 // MS implementation seems to always set these fields to 0.
1432 ulittle32_t FileDateMS = ulittle32_t(0);
1433 ulittle32_t FileDateLS = ulittle32_t(0);
1436 // First, VS_VERSIONINFO.
1437 auto LengthLoc = writeInt<uint16_t>(0);
1438 writeInt<uint16_t>(sizeof(FixedInfo));
1439 writeInt<uint16_t>(0);
1440 cantFail(writeCString("VS_VERSION_INFO"));
1441 padStream(sizeof(uint32_t));
1443 using VersionInfoFixed = VersionInfoResource::VersionInfoFixed;
1444 auto GetField = [&](VersionInfoFixed::VersionInfoFixedType Type) {
1445 static const SmallVector<uint32_t, 4> DefaultOut{0, 0, 0, 0};
1446 if (!FixedData.IsTypePresent[(int)Type])
1448 return FixedData.FixedInfo[(int)Type];
1451 auto FileVer = GetField(VersionInfoFixed::FtFileVersion);
1452 RETURN_IF_ERROR(checkNumberFits<uint16_t>(
1453 *std::max_element(FileVer.begin(), FileVer.end()), "FILEVERSION fields"));
1454 FixedInfo.FileVersionMS = (FileVer[0] << 16) | FileVer[1];
1455 FixedInfo.FileVersionLS = (FileVer[2] << 16) | FileVer[3];
1457 auto ProdVer = GetField(VersionInfoFixed::FtProductVersion);
1458 RETURN_IF_ERROR(checkNumberFits<uint16_t>(
1459 *std::max_element(ProdVer.begin(), ProdVer.end()),
1460 "PRODUCTVERSION fields"));
1461 FixedInfo.ProductVersionMS = (ProdVer[0] << 16) | ProdVer[1];
1462 FixedInfo.ProductVersionLS = (ProdVer[2] << 16) | ProdVer[3];
1464 FixedInfo.FileFlagsMask = GetField(VersionInfoFixed::FtFileFlagsMask)[0];
1465 FixedInfo.FileFlags = GetField(VersionInfoFixed::FtFileFlags)[0];
1466 FixedInfo.FileOS = GetField(VersionInfoFixed::FtFileOS)[0];
1467 FixedInfo.FileType = GetField(VersionInfoFixed::FtFileType)[0];
1468 FixedInfo.FileSubtype = GetField(VersionInfoFixed::FtFileSubtype)[0];
1470 writeObject(FixedInfo);
1471 padStream(sizeof(uint32_t));
1473 RETURN_IF_ERROR(writeVersionInfoBlock(Res->MainBlock));
1475 // FIXME: check overflow?
1476 writeObjectAt(ulittle16_t(tell() - LengthLoc), LengthLoc);
1478 return Error::success();
1481 Expected<std::unique_ptr<MemoryBuffer>>
1482 ResourceFileWriter::loadFile(StringRef File) const {
1483 SmallString<128> Path;
1484 SmallString<128> Cwd;
1485 std::unique_ptr<MemoryBuffer> Result;
1487 // 1. The current working directory.
1488 sys::fs::current_path(Cwd);
1489 Path.assign(Cwd.begin(), Cwd.end());
1490 sys::path::append(Path, File);
1491 if (sys::fs::exists(Path))
1492 return errorOrToExpected(MemoryBuffer::getFile(Path, -1, false));
1494 // 2. The directory of the input resource file, if it is different from the
1496 // working directory.
1497 StringRef InputFileDir = sys::path::parent_path(Params.InputFilePath);
1498 Path.assign(InputFileDir.begin(), InputFileDir.end());
1499 sys::path::append(Path, File);
1500 if (sys::fs::exists(Path))
1501 return errorOrToExpected(MemoryBuffer::getFile(Path, -1, false));
1503 // 3. All of the include directories specified on the command line.
1504 for (StringRef ForceInclude : Params.Include) {
1505 Path.assign(ForceInclude.begin(), ForceInclude.end());
1506 sys::path::append(Path, File);
1507 if (sys::fs::exists(Path))
1508 return errorOrToExpected(MemoryBuffer::getFile(Path, -1, false));
1512 llvm::sys::Process::FindInEnvPath("INCLUDE", File, Params.NoInclude))
1513 return errorOrToExpected(MemoryBuffer::getFile(*Result, -1, false));
1515 return make_error<StringError>("error : file not found : " + Twine(File),
1516 inconvertibleErrorCode());