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::visitCursorResource(const RCResource
*Res
) {
441 return handleError(visitIconOrCursorResource(Res
), Res
);
444 Error
ResourceFileWriter::visitDialogResource(const RCResource
*Res
) {
445 return writeResource(Res
, &ResourceFileWriter::writeDialogBody
);
448 Error
ResourceFileWriter::visitIconResource(const RCResource
*Res
) {
449 return handleError(visitIconOrCursorResource(Res
), Res
);
452 Error
ResourceFileWriter::visitCaptionStmt(const CaptionStmt
*Stmt
) {
453 ObjectData
.Caption
= Stmt
->Value
;
454 return Error::success();
457 Error
ResourceFileWriter::visitHTMLResource(const RCResource
*Res
) {
458 return writeResource(Res
, &ResourceFileWriter::writeHTMLBody
);
461 Error
ResourceFileWriter::visitMenuResource(const RCResource
*Res
) {
462 return writeResource(Res
, &ResourceFileWriter::writeMenuBody
);
465 Error
ResourceFileWriter::visitStringTableResource(const RCResource
*Base
) {
466 const auto *Res
= cast
<StringTableResource
>(Base
);
468 ContextKeeper
RAII(this);
469 RETURN_IF_ERROR(Res
->applyStmts(this));
471 for (auto &String
: Res
->Table
) {
472 RETURN_IF_ERROR(checkNumberFits
<uint16_t>(String
.first
, "String ID"));
473 uint16_t BundleID
= String
.first
>> 4;
474 StringTableInfo::BundleKey
Key(BundleID
, ObjectData
.LanguageInfo
);
475 auto &BundleData
= StringTableData
.BundleData
;
476 auto Iter
= BundleData
.find(Key
);
478 if (Iter
== BundleData
.end()) {
479 // Need to create a bundle.
480 StringTableData
.BundleList
.push_back(Key
);
482 BundleData
.emplace(Key
, StringTableInfo::Bundle(ObjectData
));
483 assert(EmplaceResult
.second
&& "Could not create a bundle");
484 Iter
= EmplaceResult
.first
;
488 insertStringIntoBundle(Iter
->second
, String
.first
, String
.second
));
491 return Error::success();
494 Error
ResourceFileWriter::visitUserDefinedResource(const RCResource
*Res
) {
495 return writeResource(Res
, &ResourceFileWriter::writeUserDefinedBody
);
498 Error
ResourceFileWriter::visitVersionInfoResource(const RCResource
*Res
) {
499 return writeResource(Res
, &ResourceFileWriter::writeVersionInfoBody
);
502 Error
ResourceFileWriter::visitCharacteristicsStmt(
503 const CharacteristicsStmt
*Stmt
) {
504 ObjectData
.Characteristics
= Stmt
->Value
;
505 return Error::success();
508 Error
ResourceFileWriter::visitFontStmt(const FontStmt
*Stmt
) {
509 RETURN_IF_ERROR(checkNumberFits
<uint16_t>(Stmt
->Size
, "Font size"));
510 RETURN_IF_ERROR(checkNumberFits
<uint16_t>(Stmt
->Weight
, "Font weight"));
511 RETURN_IF_ERROR(checkNumberFits
<uint8_t>(Stmt
->Charset
, "Font charset"));
512 ObjectInfo::FontInfo Font
{Stmt
->Size
, Stmt
->Name
, Stmt
->Weight
, Stmt
->Italic
,
514 ObjectData
.Font
.emplace(Font
);
515 return Error::success();
518 Error
ResourceFileWriter::visitLanguageStmt(const LanguageResource
*Stmt
) {
519 RETURN_IF_ERROR(checkNumberFits(Stmt
->Lang
, 10, "Primary language ID"));
520 RETURN_IF_ERROR(checkNumberFits(Stmt
->SubLang
, 6, "Sublanguage ID"));
521 ObjectData
.LanguageInfo
= Stmt
->Lang
| (Stmt
->SubLang
<< 10);
522 return Error::success();
525 Error
ResourceFileWriter::visitStyleStmt(const StyleStmt
*Stmt
) {
526 ObjectData
.Style
= Stmt
->Value
;
527 return Error::success();
530 Error
ResourceFileWriter::visitVersionStmt(const VersionStmt
*Stmt
) {
531 ObjectData
.VersionInfo
= Stmt
->Value
;
532 return Error::success();
535 Error
ResourceFileWriter::writeResource(
536 const RCResource
*Res
,
537 Error (ResourceFileWriter::*BodyWriter
)(const RCResource
*)) {
538 // We don't know the sizes yet.
539 object::WinResHeaderPrefix HeaderPrefix
{ulittle32_t(0U), ulittle32_t(0U)};
540 uint64_t HeaderLoc
= writeObject(HeaderPrefix
);
542 auto ResType
= Res
->getResourceType();
543 RETURN_IF_ERROR(checkIntOrString(ResType
, "Resource type"));
544 RETURN_IF_ERROR(checkIntOrString(Res
->ResName
, "Resource ID"));
545 RETURN_IF_ERROR(handleError(writeIdentifier(ResType
), Res
));
546 RETURN_IF_ERROR(handleError(writeIdentifier(Res
->ResName
), Res
));
548 // Apply the resource-local optional statements.
549 ContextKeeper
RAII(this);
550 RETURN_IF_ERROR(handleError(Res
->applyStmts(this), Res
));
552 padStream(sizeof(uint32_t));
553 object::WinResHeaderSuffix HeaderSuffix
{
554 ulittle32_t(0), // DataVersion; seems to always be 0
555 ulittle16_t(Res
->getMemoryFlags()), ulittle16_t(ObjectData
.LanguageInfo
),
556 ulittle32_t(ObjectData
.VersionInfo
),
557 ulittle32_t(ObjectData
.Characteristics
)};
558 writeObject(HeaderSuffix
);
560 uint64_t DataLoc
= tell();
561 RETURN_IF_ERROR(handleError((this->*BodyWriter
)(Res
), Res
));
562 // RETURN_IF_ERROR(handleError(dumpResource(Ctx)));
565 HeaderPrefix
.DataSize
= tell() - DataLoc
;
566 HeaderPrefix
.HeaderSize
= DataLoc
- HeaderLoc
;
567 writeObjectAt(HeaderPrefix
, HeaderLoc
);
568 padStream(sizeof(uint32_t));
570 return Error::success();
573 // --- NullResource helpers. --- //
575 Error
ResourceFileWriter::writeNullBody(const RCResource
*) {
576 return Error::success();
579 // --- AcceleratorsResource helpers. --- //
581 Error
ResourceFileWriter::writeSingleAccelerator(
582 const AcceleratorsResource::Accelerator
&Obj
, bool IsLastItem
) {
583 using Accelerator
= AcceleratorsResource::Accelerator
;
584 using Opt
= Accelerator::Options
;
586 struct AccelTableEntry
{
588 ulittle16_t ANSICode
;
591 } Entry
{ulittle16_t(0), ulittle16_t(0), ulittle16_t(0), 0};
593 bool IsASCII
= Obj
.Flags
& Opt::ASCII
, IsVirtKey
= Obj
.Flags
& Opt::VIRTKEY
;
595 // Remove ASCII flags (which doesn't occur in .res files).
596 Entry
.Flags
= Obj
.Flags
& ~Opt::ASCII
;
601 RETURN_IF_ERROR(checkNumberFits
<uint16_t>(Obj
.Id
, "ACCELERATORS entry ID"));
602 Entry
.Id
= ulittle16_t(Obj
.Id
);
604 auto createAccError
= [&Obj
](const char *Msg
) {
605 return createError("Accelerator ID " + Twine(Obj
.Id
) + ": " + Msg
);
608 if (IsASCII
&& IsVirtKey
)
609 return createAccError("Accelerator can't be both ASCII and VIRTKEY");
611 if (!IsVirtKey
&& (Obj
.Flags
& (Opt::ALT
| Opt::SHIFT
| Opt::CONTROL
)))
612 return createAccError("Can only apply ALT, SHIFT or CONTROL to VIRTKEY"
615 if (Obj
.Event
.isInt()) {
616 if (!IsASCII
&& !IsVirtKey
)
617 return createAccError(
618 "Accelerator with a numeric event must be either ASCII"
621 uint32_t EventVal
= Obj
.Event
.getInt();
623 checkNumberFits
<uint16_t>(EventVal
, "Numeric event key ID"));
624 Entry
.ANSICode
= ulittle16_t(EventVal
);
626 return Error::success();
629 StringRef Str
= Obj
.Event
.getString();
631 stripQuotes(Str
, IsWide
);
633 if (Str
.size() == 0 || Str
.size() > 2)
634 return createAccError(
635 "Accelerator string events should have length 1 or 2");
639 return createAccError("No character following '^' in accelerator event");
641 return createAccError(
642 "VIRTKEY accelerator events can't be preceded by '^'");
645 if (Ch
>= 'a' && Ch
<= 'z')
646 Entry
.ANSICode
= ulittle16_t(Ch
- 'a' + 1);
647 else if (Ch
>= 'A' && Ch
<= 'Z')
648 Entry
.ANSICode
= ulittle16_t(Ch
- 'A' + 1);
650 return createAccError("Control character accelerator event should be"
654 return Error::success();
658 return createAccError("Event string should be one-character, possibly"
661 uint8_t EventCh
= Str
[0];
662 // The original tool just warns in this situation. We chose to fail.
663 if (IsVirtKey
&& !isalnum(EventCh
))
664 return createAccError("Non-alphanumeric characters cannot describe virtual"
667 return createAccError("Non-ASCII description of accelerator");
670 EventCh
= toupper(EventCh
);
671 Entry
.ANSICode
= ulittle16_t(EventCh
);
673 return Error::success();
676 Error
ResourceFileWriter::writeAcceleratorsBody(const RCResource
*Base
) {
677 auto *Res
= cast
<AcceleratorsResource
>(Base
);
678 size_t AcceleratorId
= 0;
679 for (auto &Acc
: Res
->Accelerators
) {
682 writeSingleAccelerator(Acc
, AcceleratorId
== Res
->Accelerators
.size()));
684 return Error::success();
687 // --- CursorResource and IconResource helpers. --- //
689 // ICONRESDIR structure. Describes a single icon in resouce group.
691 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648016.aspx
699 // CURSORDIR structure. Describes a single cursor in resource group.
701 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648011(v=vs.85).aspx
707 // RESDIRENTRY structure, stripped from the last item. Stripping made
708 // for compatibility with RESDIR.
710 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026(v=vs.85).aspx
711 struct ResourceDirEntryStart
{
713 CursorDir Cursor
; // Used in CURSOR resources.
714 IconResDir Icon
; // Used in .ico and .cur files, and ICON resources.
716 ulittle16_t Planes
; // HotspotX (.cur files but not CURSOR resource).
717 ulittle16_t BitCount
; // HotspotY (.cur files but not CURSOR resource).
719 // ulittle32_t ImageOffset; // Offset to image data (ICONDIRENTRY only).
720 // ulittle16_t IconID; // Resource icon ID (RESDIR only).
723 // BITMAPINFOHEADER structure. Describes basic information about the bitmap
726 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
727 struct BitmapInfoHeader
{
732 ulittle16_t BitCount
;
733 ulittle32_t Compression
;
734 ulittle32_t SizeImage
;
735 ulittle32_t XPelsPerMeter
;
736 ulittle32_t YPelsPerMeter
;
738 ulittle32_t ClrImportant
;
741 // Group icon directory header. Called ICONDIR in .ico/.cur files and
742 // NEWHEADER in .res files.
744 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648023(v=vs.85).aspx
745 struct GroupIconDir
{
746 ulittle16_t Reserved
; // Always 0.
747 ulittle16_t ResType
; // 1 for icons, 2 for cursors.
748 ulittle16_t ResCount
; // Number of items.
751 enum class IconCursorGroupType
{ Icon
, Cursor
};
753 class SingleIconCursorResource
: public RCResource
{
755 IconCursorGroupType Type
;
756 const ResourceDirEntryStart
&Header
;
757 ArrayRef
<uint8_t> Image
;
759 SingleIconCursorResource(IconCursorGroupType ResourceType
,
760 const ResourceDirEntryStart
&HeaderEntry
,
761 ArrayRef
<uint8_t> ImageData
)
762 : Type(ResourceType
), Header(HeaderEntry
), Image(ImageData
) {}
764 Twine
getResourceTypeName() const override
{ return "Icon/cursor image"; }
765 IntOrString
getResourceType() const override
{
766 return Type
== IconCursorGroupType::Icon
? RkSingleIcon
: RkSingleCursor
;
768 uint16_t getMemoryFlags() const override
{
769 return MfDiscardable
| MfMoveable
;
771 ResourceKind
getKind() const override
{ return RkSingleCursorOrIconRes
; }
772 static bool classof(const RCResource
*Res
) {
773 return Res
->getKind() == RkSingleCursorOrIconRes
;
777 class IconCursorGroupResource
: public RCResource
{
779 IconCursorGroupType Type
;
781 std::vector
<ResourceDirEntryStart
> ItemEntries
;
783 IconCursorGroupResource(IconCursorGroupType ResourceType
,
784 const GroupIconDir
&HeaderData
,
785 std::vector
<ResourceDirEntryStart
> &&Entries
)
786 : Type(ResourceType
), Header(HeaderData
),
787 ItemEntries(std::move(Entries
)) {}
789 Twine
getResourceTypeName() const override
{ return "Icon/cursor group"; }
790 IntOrString
getResourceType() const override
{
791 return Type
== IconCursorGroupType::Icon
? RkIconGroup
: RkCursorGroup
;
793 ResourceKind
getKind() const override
{ return RkCursorOrIconGroupRes
; }
794 static bool classof(const RCResource
*Res
) {
795 return Res
->getKind() == RkCursorOrIconGroupRes
;
799 Error
ResourceFileWriter::writeSingleIconOrCursorBody(const RCResource
*Base
) {
800 auto *Res
= cast
<SingleIconCursorResource
>(Base
);
801 if (Res
->Type
== IconCursorGroupType::Cursor
) {
802 // In case of cursors, two WORDS are appended to the beginning
803 // of the resource: HotspotX (Planes in RESDIRENTRY),
804 // and HotspotY (BitCount).
806 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026.aspx
807 // (Remarks section).
808 writeObject(Res
->Header
.Planes
);
809 writeObject(Res
->Header
.BitCount
);
812 writeObject(Res
->Image
);
813 return Error::success();
816 Error
ResourceFileWriter::writeIconOrCursorGroupBody(const RCResource
*Base
) {
817 auto *Res
= cast
<IconCursorGroupResource
>(Base
);
818 writeObject(Res
->Header
);
819 for (auto Item
: Res
->ItemEntries
) {
821 writeInt(IconCursorID
++);
823 return Error::success();
826 Error
ResourceFileWriter::visitSingleIconOrCursor(const RCResource
*Res
) {
827 return writeResource(Res
, &ResourceFileWriter::writeSingleIconOrCursorBody
);
830 Error
ResourceFileWriter::visitIconOrCursorGroup(const RCResource
*Res
) {
831 return writeResource(Res
, &ResourceFileWriter::writeIconOrCursorGroupBody
);
834 Error
ResourceFileWriter::visitIconOrCursorResource(const RCResource
*Base
) {
835 IconCursorGroupType Type
;
837 IntOrString ResName
= Base
->ResName
;
839 if (auto *IconRes
= dyn_cast
<IconResource
>(Base
)) {
840 FileStr
= IconRes
->IconLoc
;
841 Type
= IconCursorGroupType::Icon
;
843 auto *CursorRes
= dyn_cast
<CursorResource
>(Base
);
844 FileStr
= CursorRes
->CursorLoc
;
845 Type
= IconCursorGroupType::Cursor
;
849 stripQuotes(FileStr
, IsLong
);
850 auto File
= loadFile(FileStr
);
853 return File
.takeError();
855 BinaryStreamReader
Reader((*File
)->getBuffer(), support::little
);
857 // Read the file headers.
858 // - At the beginning, ICONDIR/NEWHEADER header.
859 // - Then, a number of RESDIR headers follow. These contain offsets
861 const GroupIconDir
*Header
;
863 RETURN_IF_ERROR(Reader
.readObject(Header
));
864 if (Header
->Reserved
!= 0)
865 return createError("Incorrect icon/cursor Reserved field; should be 0.");
866 uint16_t NeededType
= Type
== IconCursorGroupType::Icon
? 1 : 2;
867 if (Header
->ResType
!= NeededType
)
868 return createError("Incorrect icon/cursor ResType field; should be " +
869 Twine(NeededType
) + ".");
871 uint16_t NumItems
= Header
->ResCount
;
873 // Read single ico/cur headers.
874 std::vector
<ResourceDirEntryStart
> ItemEntries
;
875 ItemEntries
.reserve(NumItems
);
876 std::vector
<uint32_t> ItemOffsets(NumItems
);
877 for (size_t ID
= 0; ID
< NumItems
; ++ID
) {
878 const ResourceDirEntryStart
*Object
;
879 RETURN_IF_ERROR(Reader
.readObject(Object
));
880 ItemEntries
.push_back(*Object
);
881 RETURN_IF_ERROR(Reader
.readInteger(ItemOffsets
[ID
]));
884 // Now write each icon/cursors one by one. At first, all the contents
885 // without ICO/CUR header. This is described by SingleIconCursorResource.
886 for (size_t ID
= 0; ID
< NumItems
; ++ID
) {
887 // Load the fragment of file.
888 Reader
.setOffset(ItemOffsets
[ID
]);
889 ArrayRef
<uint8_t> Image
;
890 RETURN_IF_ERROR(Reader
.readArray(Image
, ItemEntries
[ID
].Size
));
891 SingleIconCursorResource
SingleRes(Type
, ItemEntries
[ID
], Image
);
892 SingleRes
.setName(IconCursorID
+ ID
);
893 RETURN_IF_ERROR(visitSingleIconOrCursor(&SingleRes
));
896 // Now, write all the headers concatenated into a separate resource.
897 for (size_t ID
= 0; ID
< NumItems
; ++ID
) {
898 if (Type
== IconCursorGroupType::Icon
) {
899 // rc.exe seems to always set NumPlanes to 1. No idea why it happens.
900 ItemEntries
[ID
].Planes
= 1;
904 // We need to rewrite the cursor headers.
905 const auto &OldHeader
= ItemEntries
[ID
];
906 ResourceDirEntryStart NewHeader
;
907 NewHeader
.Cursor
.Width
= OldHeader
.Icon
.Width
;
908 // Each cursor in fact stores two bitmaps, one under another.
909 // Height provided in cursor definition describes the height of the
910 // cursor, whereas the value existing in resource definition describes
911 // the height of the bitmap. Therefore, we need to double this height.
912 NewHeader
.Cursor
.Height
= OldHeader
.Icon
.Height
* 2;
914 // Now, we actually need to read the bitmap header to find
915 // the number of planes and the number of bits per pixel.
916 Reader
.setOffset(ItemOffsets
[ID
]);
917 const BitmapInfoHeader
*BMPHeader
;
918 RETURN_IF_ERROR(Reader
.readObject(BMPHeader
));
919 NewHeader
.Planes
= BMPHeader
->Planes
;
920 NewHeader
.BitCount
= BMPHeader
->BitCount
;
922 // Two WORDs were written at the beginning of the resource (hotspot
923 // location). This is reflected in Size field.
924 NewHeader
.Size
= OldHeader
.Size
+ 2 * sizeof(uint16_t);
926 ItemEntries
[ID
] = NewHeader
;
929 IconCursorGroupResource
HeaderRes(Type
, *Header
, std::move(ItemEntries
));
930 HeaderRes
.setName(ResName
);
931 RETURN_IF_ERROR(visitIconOrCursorGroup(&HeaderRes
));
933 return Error::success();
936 // --- DialogResource helpers. --- //
938 Error
ResourceFileWriter::writeSingleDialogControl(const Control
&Ctl
,
940 // Each control should be aligned to DWORD.
941 padStream(sizeof(uint32_t));
943 auto TypeInfo
= Control::SupportedCtls
.lookup(Ctl
.Type
);
944 uint32_t CtlStyle
= TypeInfo
.Style
| Ctl
.Style
.getValueOr(0);
945 uint32_t CtlExtStyle
= Ctl
.ExtStyle
.getValueOr(0);
947 // DIALOG(EX) item header prefix.
951 ulittle32_t ExtStyle
;
952 } Prefix
{ulittle32_t(CtlStyle
), ulittle32_t(CtlExtStyle
)};
957 ulittle32_t ExtStyle
;
959 } Prefix
{ulittle32_t(Ctl
.HelpID
.getValueOr(0)), ulittle32_t(CtlExtStyle
),
960 ulittle32_t(CtlStyle
)};
964 // Common fixed-length part.
965 RETURN_IF_ERROR(checkSignedNumberFits
<int16_t>(
966 Ctl
.X
, "Dialog control x-coordinate", true));
967 RETURN_IF_ERROR(checkSignedNumberFits
<int16_t>(
968 Ctl
.Y
, "Dialog control y-coordinate", true));
970 checkSignedNumberFits
<int16_t>(Ctl
.Width
, "Dialog control width", false));
971 RETURN_IF_ERROR(checkSignedNumberFits
<int16_t>(
972 Ctl
.Height
, "Dialog control height", false));
978 } Middle
{ulittle16_t(Ctl
.X
), ulittle16_t(Ctl
.Y
), ulittle16_t(Ctl
.Width
),
979 ulittle16_t(Ctl
.Height
)};
982 // ID; it's 16-bit in DIALOG and 32-bit in DIALOGEX.
984 RETURN_IF_ERROR(checkNumberFits
<uint16_t>(
985 Ctl
.ID
, "Control ID in simple DIALOG resource"));
986 writeInt
<uint16_t>(Ctl
.ID
);
988 writeInt
<uint32_t>(Ctl
.ID
);
991 // Window class - either 0xFFFF + 16-bit integer or a string.
992 RETURN_IF_ERROR(writeIntOrString(IntOrString(TypeInfo
.CtlClass
)));
994 // Element caption/reference ID. ID is preceded by 0xFFFF.
995 RETURN_IF_ERROR(checkIntOrString(Ctl
.Title
, "Control reference ID"));
996 RETURN_IF_ERROR(writeIntOrString(Ctl
.Title
));
998 // # bytes of extra creation data count. Don't pass any.
999 writeInt
<uint16_t>(0);
1001 return Error::success();
1004 Error
ResourceFileWriter::writeDialogBody(const RCResource
*Base
) {
1005 auto *Res
= cast
<DialogResource
>(Base
);
1007 // Default style: WS_POPUP | WS_BORDER | WS_SYSMENU.
1008 const uint32_t DefaultStyle
= 0x80880000;
1009 const uint32_t StyleFontFlag
= 0x40;
1010 const uint32_t StyleCaptionFlag
= 0x00C00000;
1012 uint32_t UsedStyle
= ObjectData
.Style
.getValueOr(DefaultStyle
);
1013 if (ObjectData
.Font
)
1014 UsedStyle
|= StyleFontFlag
;
1016 UsedStyle
&= ~StyleFontFlag
;
1018 // Actually, in case of empty (but existent) caption, the examined field
1019 // is equal to "\"\"". That's why empty captions are still noticed.
1020 if (ObjectData
.Caption
!= "")
1021 UsedStyle
|= StyleCaptionFlag
;
1023 const uint16_t DialogExMagic
= 0xFFFF;
1025 // Write DIALOG(EX) header prefix. These are pretty different.
1026 if (!Res
->IsExtended
) {
1027 // We cannot let the higher word of DefaultStyle be equal to 0xFFFF.
1028 // In such a case, whole object (in .res file) is equivalent to a
1029 // DIALOGEX. It might lead to access violation/segmentation fault in
1030 // resource readers. For example,
1031 // 1 DIALOG 0, 0, 0, 65432
1032 // STYLE 0xFFFF0001 {}
1033 // would be compiled to a DIALOGEX with 65432 controls.
1034 if ((UsedStyle
>> 16) == DialogExMagic
)
1035 return createError("16 higher bits of DIALOG resource style cannot be"
1036 " equal to 0xFFFF");
1040 ulittle32_t ExtStyle
;
1041 } Prefix
{ulittle32_t(UsedStyle
),
1042 ulittle32_t(0)}; // As of now, we don't keep EXSTYLE.
1044 writeObject(Prefix
);
1047 ulittle16_t Version
;
1050 ulittle32_t ExtStyle
;
1052 } Prefix
{ulittle16_t(1), ulittle16_t(DialogExMagic
),
1053 ulittle32_t(Res
->HelpID
), ulittle32_t(0), ulittle32_t(UsedStyle
)};
1055 writeObject(Prefix
);
1058 // Now, a common part. First, fixed-length fields.
1059 RETURN_IF_ERROR(checkNumberFits
<uint16_t>(Res
->Controls
.size(),
1060 "Number of dialog controls"));
1062 checkSignedNumberFits
<int16_t>(Res
->X
, "Dialog x-coordinate", true));
1064 checkSignedNumberFits
<int16_t>(Res
->Y
, "Dialog y-coordinate", true));
1066 checkSignedNumberFits
<int16_t>(Res
->Width
, "Dialog width", false));
1068 checkSignedNumberFits
<int16_t>(Res
->Height
, "Dialog height", false));
1073 ulittle16_t DialogWidth
;
1074 ulittle16_t DialogHeight
;
1075 } Middle
{ulittle16_t(Res
->Controls
.size()), ulittle16_t(Res
->X
),
1076 ulittle16_t(Res
->Y
), ulittle16_t(Res
->Width
),
1077 ulittle16_t(Res
->Height
)};
1078 writeObject(Middle
);
1080 // MENU field. As of now, we don't keep them in the state and can peacefully
1081 // think there is no menu attached to the dialog.
1082 writeInt
<uint16_t>(0);
1084 // Window CLASS field. Not kept here.
1085 writeInt
<uint16_t>(0);
1087 // Window title or a single word equal to 0.
1088 RETURN_IF_ERROR(writeCString(ObjectData
.Caption
));
1090 // If there *is* a window font declared, output its data.
1091 auto &Font
= ObjectData
.Font
;
1093 writeInt
<uint16_t>(Font
->Size
);
1094 // Additional description occurs only in DIALOGEX.
1095 if (Res
->IsExtended
) {
1096 writeInt
<uint16_t>(Font
->Weight
);
1097 writeInt
<uint8_t>(Font
->IsItalic
);
1098 writeInt
<uint8_t>(Font
->Charset
);
1100 RETURN_IF_ERROR(writeCString(Font
->Typeface
));
1103 auto handleCtlError
= [&](Error
&&Err
, const Control
&Ctl
) -> Error
{
1105 return Error::success();
1106 return joinErrors(createError("Error in " + Twine(Ctl
.Type
) +
1107 " control (ID " + Twine(Ctl
.ID
) + "):"),
1111 for (auto &Ctl
: Res
->Controls
)
1113 handleCtlError(writeSingleDialogControl(Ctl
, Res
->IsExtended
), Ctl
));
1115 return Error::success();
1118 // --- HTMLResource helpers. --- //
1120 Error
ResourceFileWriter::writeHTMLBody(const RCResource
*Base
) {
1121 return appendFile(cast
<HTMLResource
>(Base
)->HTMLLoc
);
1124 // --- MenuResource helpers. --- //
1126 Error
ResourceFileWriter::writeMenuDefinition(
1127 const std::unique_ptr
<MenuDefinition
> &Def
, uint16_t Flags
) {
1129 const MenuDefinition
*DefPtr
= Def
.get();
1131 if (auto *MenuItemPtr
= dyn_cast
<MenuItem
>(DefPtr
)) {
1132 writeInt
<uint16_t>(Flags
);
1134 checkNumberFits
<uint16_t>(MenuItemPtr
->Id
, "MENUITEM action ID"));
1135 writeInt
<uint16_t>(MenuItemPtr
->Id
);
1136 RETURN_IF_ERROR(writeCString(MenuItemPtr
->Name
));
1137 return Error::success();
1140 if (isa
<MenuSeparator
>(DefPtr
)) {
1141 writeInt
<uint16_t>(Flags
);
1142 writeInt
<uint32_t>(0);
1143 return Error::success();
1146 auto *PopupPtr
= cast
<PopupItem
>(DefPtr
);
1147 writeInt
<uint16_t>(Flags
);
1148 RETURN_IF_ERROR(writeCString(PopupPtr
->Name
));
1149 return writeMenuDefinitionList(PopupPtr
->SubItems
);
1152 Error
ResourceFileWriter::writeMenuDefinitionList(
1153 const MenuDefinitionList
&List
) {
1154 for (auto &Def
: List
.Definitions
) {
1155 uint16_t Flags
= Def
->getResFlags();
1156 // Last element receives an additional 0x80 flag.
1157 const uint16_t LastElementFlag
= 0x0080;
1158 if (&Def
== &List
.Definitions
.back())
1159 Flags
|= LastElementFlag
;
1161 RETURN_IF_ERROR(writeMenuDefinition(Def
, Flags
));
1163 return Error::success();
1166 Error
ResourceFileWriter::writeMenuBody(const RCResource
*Base
) {
1167 // At first, MENUHEADER structure. In fact, these are two WORDs equal to 0.
1168 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648018.aspx
1169 writeInt
<uint32_t>(0);
1171 return writeMenuDefinitionList(cast
<MenuResource
>(Base
)->Elements
);
1174 // --- StringTableResource helpers. --- //
1176 class BundleResource
: public RCResource
{
1178 using BundleType
= ResourceFileWriter::StringTableInfo::Bundle
;
1181 BundleResource(const BundleType
&StrBundle
) : Bundle(StrBundle
) {}
1182 IntOrString
getResourceType() const override
{ return 6; }
1184 ResourceKind
getKind() const override
{ return RkStringTableBundle
; }
1185 static bool classof(const RCResource
*Res
) {
1186 return Res
->getKind() == RkStringTableBundle
;
1188 Twine
getResourceTypeName() const override
{ return "STRINGTABLE"; }
1191 Error
ResourceFileWriter::visitStringTableBundle(const RCResource
*Res
) {
1192 return writeResource(Res
, &ResourceFileWriter::writeStringTableBundleBody
);
1195 Error
ResourceFileWriter::insertStringIntoBundle(
1196 StringTableInfo::Bundle
&Bundle
, uint16_t StringID
, StringRef String
) {
1197 uint16_t StringLoc
= StringID
& 15;
1198 if (Bundle
.Data
[StringLoc
])
1199 return createError("Multiple STRINGTABLE strings located under ID " +
1201 Bundle
.Data
[StringLoc
] = String
;
1202 return Error::success();
1205 Error
ResourceFileWriter::writeStringTableBundleBody(const RCResource
*Base
) {
1206 auto *Res
= cast
<BundleResource
>(Base
);
1207 for (size_t ID
= 0; ID
< Res
->Bundle
.Data
.size(); ++ID
) {
1208 // The string format is a tiny bit different here. We
1209 // first output the size of the string, and then the string itself
1210 // (which is not null-terminated).
1212 SmallVector
<UTF16
, 128> Data
;
1213 RETURN_IF_ERROR(processString(Res
->Bundle
.Data
[ID
].getValueOr(StringRef()),
1214 NullHandlingMethod::CutAtDoubleNull
,
1215 IsLongString
, Data
, Params
.CodePage
));
1216 if (AppendNull
&& Res
->Bundle
.Data
[ID
])
1217 Data
.push_back('\0');
1219 checkNumberFits
<uint16_t>(Data
.size(), "STRINGTABLE string size"));
1220 writeInt
<uint16_t>(Data
.size());
1221 for (auto Char
: Data
)
1224 return Error::success();
1227 Error
ResourceFileWriter::dumpAllStringTables() {
1228 for (auto Key
: StringTableData
.BundleList
) {
1229 auto Iter
= StringTableData
.BundleData
.find(Key
);
1230 assert(Iter
!= StringTableData
.BundleData
.end());
1232 // For a moment, revert the context info to moment of bundle declaration.
1233 ContextKeeper
RAII(this);
1234 ObjectData
= Iter
->second
.DeclTimeInfo
;
1236 BundleResource
Res(Iter
->second
);
1237 // Bundle #(k+1) contains keys [16k, 16k + 15].
1238 Res
.setName(Key
.first
+ 1);
1239 RETURN_IF_ERROR(visitStringTableBundle(&Res
));
1241 return Error::success();
1244 // --- UserDefinedResource helpers. --- //
1246 Error
ResourceFileWriter::writeUserDefinedBody(const RCResource
*Base
) {
1247 auto *Res
= cast
<UserDefinedResource
>(Base
);
1249 if (Res
->IsFileResource
)
1250 return appendFile(Res
->FileLoc
);
1252 for (auto &Elem
: Res
->Contents
) {
1255 checkRCInt(Elem
.getInt(), "Number in user-defined resource"));
1256 writeRCInt(Elem
.getInt());
1260 SmallVector
<UTF16
, 128> ProcessedString
;
1263 processString(Elem
.getString(), NullHandlingMethod::UserResource
,
1264 IsLongString
, ProcessedString
, Params
.CodePage
));
1266 for (auto Ch
: ProcessedString
) {
1272 RETURN_IF_ERROR(checkNumberFits
<uint8_t>(
1273 Ch
, "Character in narrow string in user-defined resource"));
1274 writeInt
<uint8_t>(Ch
);
1278 return Error::success();
1281 // --- VersionInfoResourceResource helpers. --- //
1283 Error
ResourceFileWriter::writeVersionInfoBlock(const VersionInfoBlock
&Blk
) {
1284 // Output the header if the block has name.
1285 bool OutputHeader
= Blk
.Name
!= "";
1288 padStream(sizeof(uint32_t));
1290 LengthLoc
= writeInt
<uint16_t>(0);
1291 writeInt
<uint16_t>(0);
1292 writeInt
<uint16_t>(1); // true
1293 RETURN_IF_ERROR(writeCString(Blk
.Name
));
1294 padStream(sizeof(uint32_t));
1297 for (const std::unique_ptr
<VersionInfoStmt
> &Item
: Blk
.Stmts
) {
1298 VersionInfoStmt
*ItemPtr
= Item
.get();
1300 if (auto *BlockPtr
= dyn_cast
<VersionInfoBlock
>(ItemPtr
)) {
1301 RETURN_IF_ERROR(writeVersionInfoBlock(*BlockPtr
));
1305 auto *ValuePtr
= cast
<VersionInfoValue
>(ItemPtr
);
1306 RETURN_IF_ERROR(writeVersionInfoValue(*ValuePtr
));
1310 uint64_t CurLoc
= tell();
1311 writeObjectAt(ulittle16_t(CurLoc
- LengthLoc
), LengthLoc
);
1314 return Error::success();
1317 Error
ResourceFileWriter::writeVersionInfoValue(const VersionInfoValue
&Val
) {
1318 // rc has a peculiar algorithm to output VERSIONINFO VALUEs. Each VALUE
1319 // is a mapping from the key (string) to the value (a sequence of ints or
1320 // a sequence of strings).
1322 // If integers are to be written: width of each integer written depends on
1323 // whether it's been declared 'long' (it's DWORD then) or not (it's WORD).
1324 // ValueLength defined in structure referenced below is then the total
1325 // number of bytes taken by these integers.
1327 // If strings are to be written: characters are always WORDs.
1328 // Moreover, '\0' character is written after the last string, and between
1329 // every two strings separated by comma (if strings are not comma-separated,
1330 // they're simply concatenated). ValueLength is equal to the number of WORDs
1331 // written (that is, half of the bytes written).
1333 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms646994.aspx
1334 bool HasStrings
= false, HasInts
= false;
1335 for (auto &Item
: Val
.Values
)
1336 (Item
.isInt() ? HasInts
: HasStrings
) = true;
1338 assert((HasStrings
|| HasInts
) && "VALUE must have at least one argument");
1339 if (HasStrings
&& HasInts
)
1340 return createError(Twine("VALUE ") + Val
.Key
+
1341 " cannot contain both strings and integers");
1343 padStream(sizeof(uint32_t));
1344 auto LengthLoc
= writeInt
<uint16_t>(0);
1345 auto ValLengthLoc
= writeInt
<uint16_t>(0);
1346 writeInt
<uint16_t>(HasStrings
);
1347 RETURN_IF_ERROR(writeCString(Val
.Key
));
1348 padStream(sizeof(uint32_t));
1350 auto DataLoc
= tell();
1351 for (size_t Id
= 0; Id
< Val
.Values
.size(); ++Id
) {
1352 auto &Item
= Val
.Values
[Id
];
1354 auto Value
= Item
.getInt();
1355 RETURN_IF_ERROR(checkRCInt(Value
, "VERSIONINFO integer value"));
1360 bool WriteTerminator
=
1361 Id
== Val
.Values
.size() - 1 || Val
.HasPrecedingComma
[Id
+ 1];
1362 RETURN_IF_ERROR(writeCString(Item
.getString(), WriteTerminator
));
1365 auto CurLoc
= tell();
1366 auto ValueLength
= CurLoc
- DataLoc
;
1368 assert(ValueLength
% 2 == 0);
1371 writeObjectAt(ulittle16_t(CurLoc
- LengthLoc
), LengthLoc
);
1372 writeObjectAt(ulittle16_t(ValueLength
), ValLengthLoc
);
1373 return Error::success();
1376 template <typename Ty
>
1377 static Ty
getWithDefault(const StringMap
<Ty
> &Map
, StringRef Key
,
1378 const Ty
&Default
) {
1379 auto Iter
= Map
.find(Key
);
1380 if (Iter
!= Map
.end())
1381 return Iter
->getValue();
1385 Error
ResourceFileWriter::writeVersionInfoBody(const RCResource
*Base
) {
1386 auto *Res
= cast
<VersionInfoResource
>(Base
);
1388 const auto &FixedData
= Res
->FixedData
;
1390 struct /* VS_FIXEDFILEINFO */ {
1391 ulittle32_t Signature
= ulittle32_t(0xFEEF04BD);
1392 ulittle32_t StructVersion
= ulittle32_t(0x10000);
1393 // It's weird to have most-significant DWORD first on the little-endian
1394 // machines, but let it be this way.
1395 ulittle32_t FileVersionMS
;
1396 ulittle32_t FileVersionLS
;
1397 ulittle32_t ProductVersionMS
;
1398 ulittle32_t ProductVersionLS
;
1399 ulittle32_t FileFlagsMask
;
1400 ulittle32_t FileFlags
;
1402 ulittle32_t FileType
;
1403 ulittle32_t FileSubtype
;
1404 // MS implementation seems to always set these fields to 0.
1405 ulittle32_t FileDateMS
= ulittle32_t(0);
1406 ulittle32_t FileDateLS
= ulittle32_t(0);
1409 // First, VS_VERSIONINFO.
1410 auto LengthLoc
= writeInt
<uint16_t>(0);
1411 writeInt
<uint16_t>(sizeof(FixedInfo
));
1412 writeInt
<uint16_t>(0);
1413 cantFail(writeCString("VS_VERSION_INFO"));
1414 padStream(sizeof(uint32_t));
1416 using VersionInfoFixed
= VersionInfoResource::VersionInfoFixed
;
1417 auto GetField
= [&](VersionInfoFixed::VersionInfoFixedType Type
) {
1418 static const SmallVector
<uint32_t, 4> DefaultOut
{0, 0, 0, 0};
1419 if (!FixedData
.IsTypePresent
[(int)Type
])
1421 return FixedData
.FixedInfo
[(int)Type
];
1424 auto FileVer
= GetField(VersionInfoFixed::FtFileVersion
);
1425 RETURN_IF_ERROR(checkNumberFits
<uint16_t>(
1426 *std::max_element(FileVer
.begin(), FileVer
.end()), "FILEVERSION fields"));
1427 FixedInfo
.FileVersionMS
= (FileVer
[0] << 16) | FileVer
[1];
1428 FixedInfo
.FileVersionLS
= (FileVer
[2] << 16) | FileVer
[3];
1430 auto ProdVer
= GetField(VersionInfoFixed::FtProductVersion
);
1431 RETURN_IF_ERROR(checkNumberFits
<uint16_t>(
1432 *std::max_element(ProdVer
.begin(), ProdVer
.end()),
1433 "PRODUCTVERSION fields"));
1434 FixedInfo
.ProductVersionMS
= (ProdVer
[0] << 16) | ProdVer
[1];
1435 FixedInfo
.ProductVersionLS
= (ProdVer
[2] << 16) | ProdVer
[3];
1437 FixedInfo
.FileFlagsMask
= GetField(VersionInfoFixed::FtFileFlagsMask
)[0];
1438 FixedInfo
.FileFlags
= GetField(VersionInfoFixed::FtFileFlags
)[0];
1439 FixedInfo
.FileOS
= GetField(VersionInfoFixed::FtFileOS
)[0];
1440 FixedInfo
.FileType
= GetField(VersionInfoFixed::FtFileType
)[0];
1441 FixedInfo
.FileSubtype
= GetField(VersionInfoFixed::FtFileSubtype
)[0];
1443 writeObject(FixedInfo
);
1444 padStream(sizeof(uint32_t));
1446 RETURN_IF_ERROR(writeVersionInfoBlock(Res
->MainBlock
));
1448 // FIXME: check overflow?
1449 writeObjectAt(ulittle16_t(tell() - LengthLoc
), LengthLoc
);
1451 return Error::success();
1454 Expected
<std::unique_ptr
<MemoryBuffer
>>
1455 ResourceFileWriter::loadFile(StringRef File
) const {
1456 SmallString
<128> Path
;
1457 SmallString
<128> Cwd
;
1458 std::unique_ptr
<MemoryBuffer
> Result
;
1460 // 1. The current working directory.
1461 sys::fs::current_path(Cwd
);
1462 Path
.assign(Cwd
.begin(), Cwd
.end());
1463 sys::path::append(Path
, File
);
1464 if (sys::fs::exists(Path
))
1465 return errorOrToExpected(MemoryBuffer::getFile(Path
, -1, false));
1467 // 2. The directory of the input resource file, if it is different from the
1469 // working directory.
1470 StringRef InputFileDir
= sys::path::parent_path(Params
.InputFilePath
);
1471 Path
.assign(InputFileDir
.begin(), InputFileDir
.end());
1472 sys::path::append(Path
, File
);
1473 if (sys::fs::exists(Path
))
1474 return errorOrToExpected(MemoryBuffer::getFile(Path
, -1, false));
1476 // 3. All of the include directories specified on the command line.
1477 for (StringRef ForceInclude
: Params
.Include
) {
1478 Path
.assign(ForceInclude
.begin(), ForceInclude
.end());
1479 sys::path::append(Path
, File
);
1480 if (sys::fs::exists(Path
))
1481 return errorOrToExpected(MemoryBuffer::getFile(Path
, -1, false));
1485 llvm::sys::Process::FindInEnvPath("INCLUDE", File
, Params
.NoInclude
))
1486 return errorOrToExpected(MemoryBuffer::getFile(*Result
, -1, false));
1488 return make_error
<StringError
>("error : file not found : " + Twine(File
),
1489 inconvertibleErrorCode());