1 //===-- ResourceFileWriter.cpp --------------------------------*- C++-*-===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===---------------------------------------------------------------------===//
9 // This implements the visitor serializing resources to a .res stream.
11 //===---------------------------------------------------------------------===//
13 #include "ResourceFileWriter.h"
14 #include "llvm/Object/WindowsResource.h"
15 #include "llvm/Support/ConvertUTF.h"
16 #include "llvm/Support/Endian.h"
17 #include "llvm/Support/EndianStream.h"
18 #include "llvm/Support/FileSystem.h"
19 #include "llvm/Support/MemoryBuffer.h"
20 #include "llvm/Support/Path.h"
21 #include "llvm/Support/Process.h"
23 using namespace llvm::support
;
25 // Take an expression returning llvm::Error and forward the error if it exists.
26 #define RETURN_IF_ERROR(Expr) \
27 if (auto Err = (Expr)) \
33 // Class that employs RAII to save the current FileWriter object state
34 // and revert to it as soon as we leave the scope. This is useful if resources
35 // declare their own resource-local statements.
37 ResourceFileWriter
*FileWriter
;
38 ResourceFileWriter::ObjectInfo SavedInfo
;
41 ContextKeeper(ResourceFileWriter
*V
)
42 : FileWriter(V
), SavedInfo(V
->ObjectData
) {}
43 ~ContextKeeper() { FileWriter
->ObjectData
= SavedInfo
; }
46 static Error
createError(const Twine
&Message
,
47 std::errc Type
= std::errc::invalid_argument
) {
48 return make_error
<StringError
>(Message
, std::make_error_code(Type
));
51 static Error
checkNumberFits(uint32_t Number
, size_t MaxBits
,
52 const Twine
&FieldName
) {
53 assert(1 <= MaxBits
&& MaxBits
<= 32);
54 if (!(Number
>> MaxBits
))
55 return Error::success();
56 return createError(FieldName
+ " (" + Twine(Number
) + ") does not fit in " +
57 Twine(MaxBits
) + " bits.",
58 std::errc::value_too_large
);
61 template <typename FitType
>
62 static Error
checkNumberFits(uint32_t Number
, const Twine
&FieldName
) {
63 return checkNumberFits(Number
, sizeof(FitType
) * 8, FieldName
);
66 // A similar function for signed integers.
67 template <typename FitType
>
68 static Error
checkSignedNumberFits(uint32_t Number
, const Twine
&FieldName
,
70 int32_t SignedNum
= Number
;
71 if (SignedNum
< std::numeric_limits
<FitType
>::min() ||
72 SignedNum
> std::numeric_limits
<FitType
>::max())
73 return createError(FieldName
+ " (" + Twine(SignedNum
) +
74 ") does not fit in " + Twine(sizeof(FitType
) * 8) +
75 "-bit signed integer type.",
76 std::errc::value_too_large
);
78 if (!CanBeNegative
&& SignedNum
< 0)
79 return createError(FieldName
+ " (" + Twine(SignedNum
) +
80 ") cannot be negative.");
82 return Error::success();
85 static Error
checkRCInt(RCInt Number
, const Twine
&FieldName
) {
87 return Error::success();
88 return checkNumberFits
<uint16_t>(Number
, FieldName
);
91 static Error
checkIntOrString(IntOrString Value
, const Twine
&FieldName
) {
93 return Error::success();
94 return checkNumberFits
<uint16_t>(Value
.getInt(), FieldName
);
97 static bool stripQuotes(StringRef
&Str
, bool &IsLongString
) {
98 if (!Str
.contains('"'))
101 // Just take the contents of the string, checking if it's been marked long.
102 IsLongString
= Str
.startswith_insensitive("L");
104 Str
= Str
.drop_front();
106 bool StripSuccess
= Str
.consume_front("\"") && Str
.consume_back("\"");
108 assert(StripSuccess
&& "Strings should be enclosed in quotes.");
112 static UTF16
cp1252ToUnicode(unsigned char C
) {
113 static const UTF16 Map80
[] = {
114 0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021,
115 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f,
116 0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014,
117 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178,
119 if (C
>= 0x80 && C
<= 0x9F)
120 return Map80
[C
- 0x80];
124 // Describes a way to handle '\0' characters when processing the string.
125 // rc.exe tool sometimes behaves in a weird way in postprocessing.
126 // If the string to be output is equivalent to a C-string (e.g. in MENU
127 // titles), string is (predictably) truncated after first 0-byte.
128 // When outputting a string table, the behavior is equivalent to appending
129 // '\0\0' at the end of the string, and then stripping the string
130 // before the first '\0\0' occurrence.
131 // Finally, when handling strings in user-defined resources, 0-bytes
132 // aren't stripped, nor do they terminate the string.
134 enum class NullHandlingMethod
{
135 UserResource
, // Don't terminate string on '\0'.
136 CutAtNull
, // Terminate string on '\0'.
137 CutAtDoubleNull
// Terminate string on '\0\0'; strip final '\0'.
140 // Parses an identifier or string and returns a processed version of it:
141 // * Strip the string boundary quotes.
142 // * Convert the input code page characters to UTF16.
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::visitClassStmt(const ClassStmt
*Stmt
) {
462 ObjectData
.Class
= Stmt
->Value
;
463 return Error::success();
466 Error
ResourceFileWriter::visitHTMLResource(const RCResource
*Res
) {
467 return writeResource(Res
, &ResourceFileWriter::writeHTMLBody
);
470 Error
ResourceFileWriter::visitMenuResource(const RCResource
*Res
) {
471 return writeResource(Res
, &ResourceFileWriter::writeMenuBody
);
474 Error
ResourceFileWriter::visitStringTableResource(const RCResource
*Base
) {
475 const auto *Res
= cast
<StringTableResource
>(Base
);
477 ContextKeeper
RAII(this);
478 RETURN_IF_ERROR(Res
->applyStmts(this));
480 for (auto &String
: Res
->Table
) {
481 RETURN_IF_ERROR(checkNumberFits
<uint16_t>(String
.first
, "String ID"));
482 uint16_t BundleID
= String
.first
>> 4;
483 StringTableInfo::BundleKey
Key(BundleID
, ObjectData
.LanguageInfo
);
484 auto &BundleData
= StringTableData
.BundleData
;
485 auto Iter
= BundleData
.find(Key
);
487 if (Iter
== BundleData
.end()) {
488 // Need to create a bundle.
489 StringTableData
.BundleList
.push_back(Key
);
490 auto EmplaceResult
= BundleData
.emplace(
491 Key
, StringTableInfo::Bundle(ObjectData
, Res
->MemoryFlags
));
492 assert(EmplaceResult
.second
&& "Could not create a bundle");
493 Iter
= EmplaceResult
.first
;
497 insertStringIntoBundle(Iter
->second
, String
.first
, String
.second
));
500 return Error::success();
503 Error
ResourceFileWriter::visitUserDefinedResource(const RCResource
*Res
) {
504 return writeResource(Res
, &ResourceFileWriter::writeUserDefinedBody
);
507 Error
ResourceFileWriter::visitVersionInfoResource(const RCResource
*Res
) {
508 return writeResource(Res
, &ResourceFileWriter::writeVersionInfoBody
);
511 Error
ResourceFileWriter::visitCharacteristicsStmt(
512 const CharacteristicsStmt
*Stmt
) {
513 ObjectData
.Characteristics
= Stmt
->Value
;
514 return Error::success();
517 Error
ResourceFileWriter::visitExStyleStmt(const ExStyleStmt
*Stmt
) {
518 ObjectData
.ExStyle
= Stmt
->Value
;
519 return Error::success();
522 Error
ResourceFileWriter::visitFontStmt(const FontStmt
*Stmt
) {
523 RETURN_IF_ERROR(checkNumberFits
<uint16_t>(Stmt
->Size
, "Font size"));
524 RETURN_IF_ERROR(checkNumberFits
<uint16_t>(Stmt
->Weight
, "Font weight"));
525 RETURN_IF_ERROR(checkNumberFits
<uint8_t>(Stmt
->Charset
, "Font charset"));
526 ObjectInfo::FontInfo Font
{Stmt
->Size
, Stmt
->Name
, Stmt
->Weight
, Stmt
->Italic
,
528 ObjectData
.Font
.emplace(Font
);
529 return Error::success();
532 Error
ResourceFileWriter::visitLanguageStmt(const LanguageResource
*Stmt
) {
533 RETURN_IF_ERROR(checkNumberFits(Stmt
->Lang
, 10, "Primary language ID"));
534 RETURN_IF_ERROR(checkNumberFits(Stmt
->SubLang
, 6, "Sublanguage ID"));
535 ObjectData
.LanguageInfo
= Stmt
->Lang
| (Stmt
->SubLang
<< 10);
536 return Error::success();
539 Error
ResourceFileWriter::visitStyleStmt(const StyleStmt
*Stmt
) {
540 ObjectData
.Style
= Stmt
->Value
;
541 return Error::success();
544 Error
ResourceFileWriter::visitVersionStmt(const VersionStmt
*Stmt
) {
545 ObjectData
.VersionInfo
= Stmt
->Value
;
546 return Error::success();
549 Error
ResourceFileWriter::writeResource(
550 const RCResource
*Res
,
551 Error (ResourceFileWriter::*BodyWriter
)(const RCResource
*)) {
552 // We don't know the sizes yet.
553 object::WinResHeaderPrefix HeaderPrefix
{ulittle32_t(0U), ulittle32_t(0U)};
554 uint64_t HeaderLoc
= writeObject(HeaderPrefix
);
556 auto ResType
= Res
->getResourceType();
557 RETURN_IF_ERROR(checkIntOrString(ResType
, "Resource type"));
558 RETURN_IF_ERROR(checkIntOrString(Res
->ResName
, "Resource ID"));
559 RETURN_IF_ERROR(handleError(writeIdentifier(ResType
), Res
));
560 RETURN_IF_ERROR(handleError(writeIdentifier(Res
->ResName
), Res
));
562 // Apply the resource-local optional statements.
563 ContextKeeper
RAII(this);
564 RETURN_IF_ERROR(handleError(Res
->applyStmts(this), Res
));
566 padStream(sizeof(uint32_t));
567 object::WinResHeaderSuffix HeaderSuffix
{
568 ulittle32_t(0), // DataVersion; seems to always be 0
569 ulittle16_t(Res
->MemoryFlags
), ulittle16_t(ObjectData
.LanguageInfo
),
570 ulittle32_t(ObjectData
.VersionInfo
),
571 ulittle32_t(ObjectData
.Characteristics
)};
572 writeObject(HeaderSuffix
);
574 uint64_t DataLoc
= tell();
575 RETURN_IF_ERROR(handleError((this->*BodyWriter
)(Res
), Res
));
576 // RETURN_IF_ERROR(handleError(dumpResource(Ctx)));
579 HeaderPrefix
.DataSize
= tell() - DataLoc
;
580 HeaderPrefix
.HeaderSize
= DataLoc
- HeaderLoc
;
581 writeObjectAt(HeaderPrefix
, HeaderLoc
);
582 padStream(sizeof(uint32_t));
584 return Error::success();
587 // --- NullResource helpers. --- //
589 Error
ResourceFileWriter::writeNullBody(const RCResource
*) {
590 return Error::success();
593 // --- AcceleratorsResource helpers. --- //
595 Error
ResourceFileWriter::writeSingleAccelerator(
596 const AcceleratorsResource::Accelerator
&Obj
, bool IsLastItem
) {
597 using Accelerator
= AcceleratorsResource::Accelerator
;
598 using Opt
= Accelerator::Options
;
600 struct AccelTableEntry
{
602 ulittle16_t ANSICode
;
605 } Entry
{ulittle16_t(0), ulittle16_t(0), ulittle16_t(0), 0};
607 bool IsASCII
= Obj
.Flags
& Opt::ASCII
, IsVirtKey
= Obj
.Flags
& Opt::VIRTKEY
;
609 // Remove ASCII flags (which doesn't occur in .res files).
610 Entry
.Flags
= Obj
.Flags
& ~Opt::ASCII
;
615 RETURN_IF_ERROR(checkNumberFits
<uint16_t>(Obj
.Id
, "ACCELERATORS entry ID"));
616 Entry
.Id
= ulittle16_t(Obj
.Id
);
618 auto createAccError
= [&Obj
](const char *Msg
) {
619 return createError("Accelerator ID " + Twine(Obj
.Id
) + ": " + Msg
);
622 if (IsASCII
&& IsVirtKey
)
623 return createAccError("Accelerator can't be both ASCII and VIRTKEY");
625 if (!IsVirtKey
&& (Obj
.Flags
& (Opt::ALT
| Opt::SHIFT
| Opt::CONTROL
)))
626 return createAccError("Can only apply ALT, SHIFT or CONTROL to VIRTKEY"
629 if (Obj
.Event
.isInt()) {
630 if (!IsASCII
&& !IsVirtKey
)
631 return createAccError(
632 "Accelerator with a numeric event must be either ASCII"
635 uint32_t EventVal
= Obj
.Event
.getInt();
637 checkNumberFits
<uint16_t>(EventVal
, "Numeric event key ID"));
638 Entry
.ANSICode
= ulittle16_t(EventVal
);
640 return Error::success();
643 StringRef Str
= Obj
.Event
.getString();
645 stripQuotes(Str
, IsWide
);
647 if (Str
.size() == 0 || Str
.size() > 2)
648 return createAccError(
649 "Accelerator string events should have length 1 or 2");
653 return createAccError("No character following '^' in accelerator event");
655 return createAccError(
656 "VIRTKEY accelerator events can't be preceded by '^'");
659 if (Ch
>= 'a' && Ch
<= 'z')
660 Entry
.ANSICode
= ulittle16_t(Ch
- 'a' + 1);
661 else if (Ch
>= 'A' && Ch
<= 'Z')
662 Entry
.ANSICode
= ulittle16_t(Ch
- 'A' + 1);
664 return createAccError("Control character accelerator event should be"
668 return Error::success();
672 return createAccError("Event string should be one-character, possibly"
675 uint8_t EventCh
= Str
[0];
676 // The original tool just warns in this situation. We chose to fail.
677 if (IsVirtKey
&& !isalnum(EventCh
))
678 return createAccError("Non-alphanumeric characters cannot describe virtual"
681 return createAccError("Non-ASCII description of accelerator");
684 EventCh
= toupper(EventCh
);
685 Entry
.ANSICode
= ulittle16_t(EventCh
);
687 return Error::success();
690 Error
ResourceFileWriter::writeAcceleratorsBody(const RCResource
*Base
) {
691 auto *Res
= cast
<AcceleratorsResource
>(Base
);
692 size_t AcceleratorId
= 0;
693 for (auto &Acc
: Res
->Accelerators
) {
696 writeSingleAccelerator(Acc
, AcceleratorId
== Res
->Accelerators
.size()));
698 return Error::success();
701 // --- BitmapResource helpers. --- //
703 Error
ResourceFileWriter::writeBitmapBody(const RCResource
*Base
) {
704 StringRef Filename
= cast
<BitmapResource
>(Base
)->BitmapLoc
;
706 stripQuotes(Filename
, IsLong
);
708 auto File
= loadFile(Filename
);
710 return File
.takeError();
712 StringRef Buffer
= (*File
)->getBuffer();
714 // Skip the 14 byte BITMAPFILEHEADER.
715 constexpr size_t BITMAPFILEHEADER_size
= 14;
716 if (Buffer
.size() < BITMAPFILEHEADER_size
|| Buffer
[0] != 'B' ||
718 return createError("Incorrect bitmap file.");
720 *FS
<< Buffer
.substr(BITMAPFILEHEADER_size
);
721 return Error::success();
724 // --- CursorResource and IconResource helpers. --- //
726 // ICONRESDIR structure. Describes a single icon in resource group.
728 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648016.aspx
736 // CURSORDIR structure. Describes a single cursor in resource group.
738 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648011(v=vs.85).aspx
744 // RESDIRENTRY structure, stripped from the last item. Stripping made
745 // for compatibility with RESDIR.
747 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026(v=vs.85).aspx
748 struct ResourceDirEntryStart
{
750 CursorDir Cursor
; // Used in CURSOR resources.
751 IconResDir Icon
; // Used in .ico and .cur files, and ICON resources.
753 ulittle16_t Planes
; // HotspotX (.cur files but not CURSOR resource).
754 ulittle16_t BitCount
; // HotspotY (.cur files but not CURSOR resource).
756 // ulittle32_t ImageOffset; // Offset to image data (ICONDIRENTRY only).
757 // ulittle16_t IconID; // Resource icon ID (RESDIR only).
760 // BITMAPINFOHEADER structure. Describes basic information about the bitmap
763 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
764 struct BitmapInfoHeader
{
769 ulittle16_t BitCount
;
770 ulittle32_t Compression
;
771 ulittle32_t SizeImage
;
772 ulittle32_t XPelsPerMeter
;
773 ulittle32_t YPelsPerMeter
;
775 ulittle32_t ClrImportant
;
778 // Group icon directory header. Called ICONDIR in .ico/.cur files and
779 // NEWHEADER in .res files.
781 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648023(v=vs.85).aspx
782 struct GroupIconDir
{
783 ulittle16_t Reserved
; // Always 0.
784 ulittle16_t ResType
; // 1 for icons, 2 for cursors.
785 ulittle16_t ResCount
; // Number of items.
788 enum class IconCursorGroupType
{ Icon
, Cursor
};
790 class SingleIconCursorResource
: public RCResource
{
792 IconCursorGroupType Type
;
793 const ResourceDirEntryStart
&Header
;
794 ArrayRef
<uint8_t> Image
;
796 SingleIconCursorResource(IconCursorGroupType ResourceType
,
797 const ResourceDirEntryStart
&HeaderEntry
,
798 ArrayRef
<uint8_t> ImageData
, uint16_t Flags
)
799 : RCResource(Flags
), Type(ResourceType
), Header(HeaderEntry
),
802 Twine
getResourceTypeName() const override
{ return "Icon/cursor image"; }
803 IntOrString
getResourceType() const override
{
804 return Type
== IconCursorGroupType::Icon
? RkSingleIcon
: RkSingleCursor
;
806 ResourceKind
getKind() const override
{ return RkSingleCursorOrIconRes
; }
807 static bool classof(const RCResource
*Res
) {
808 return Res
->getKind() == RkSingleCursorOrIconRes
;
812 class IconCursorGroupResource
: public RCResource
{
814 IconCursorGroupType Type
;
816 std::vector
<ResourceDirEntryStart
> ItemEntries
;
818 IconCursorGroupResource(IconCursorGroupType ResourceType
,
819 const GroupIconDir
&HeaderData
,
820 std::vector
<ResourceDirEntryStart
> &&Entries
)
821 : Type(ResourceType
), Header(HeaderData
),
822 ItemEntries(std::move(Entries
)) {}
824 Twine
getResourceTypeName() const override
{ return "Icon/cursor group"; }
825 IntOrString
getResourceType() const override
{
826 return Type
== IconCursorGroupType::Icon
? RkIconGroup
: RkCursorGroup
;
828 ResourceKind
getKind() const override
{ return RkCursorOrIconGroupRes
; }
829 static bool classof(const RCResource
*Res
) {
830 return Res
->getKind() == RkCursorOrIconGroupRes
;
834 Error
ResourceFileWriter::writeSingleIconOrCursorBody(const RCResource
*Base
) {
835 auto *Res
= cast
<SingleIconCursorResource
>(Base
);
836 if (Res
->Type
== IconCursorGroupType::Cursor
) {
837 // In case of cursors, two WORDS are appended to the beginning
838 // of the resource: HotspotX (Planes in RESDIRENTRY),
839 // and HotspotY (BitCount).
841 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026.aspx
842 // (Remarks section).
843 writeObject(Res
->Header
.Planes
);
844 writeObject(Res
->Header
.BitCount
);
847 writeObject(Res
->Image
);
848 return Error::success();
851 Error
ResourceFileWriter::writeIconOrCursorGroupBody(const RCResource
*Base
) {
852 auto *Res
= cast
<IconCursorGroupResource
>(Base
);
853 writeObject(Res
->Header
);
854 for (auto Item
: Res
->ItemEntries
) {
856 writeInt(IconCursorID
++);
858 return Error::success();
861 Error
ResourceFileWriter::visitSingleIconOrCursor(const RCResource
*Res
) {
862 return writeResource(Res
, &ResourceFileWriter::writeSingleIconOrCursorBody
);
865 Error
ResourceFileWriter::visitIconOrCursorGroup(const RCResource
*Res
) {
866 return writeResource(Res
, &ResourceFileWriter::writeIconOrCursorGroupBody
);
869 Error
ResourceFileWriter::visitIconOrCursorResource(const RCResource
*Base
) {
870 IconCursorGroupType Type
;
872 IntOrString ResName
= Base
->ResName
;
874 if (auto *IconRes
= dyn_cast
<IconResource
>(Base
)) {
875 FileStr
= IconRes
->IconLoc
;
876 Type
= IconCursorGroupType::Icon
;
878 auto *CursorRes
= dyn_cast
<CursorResource
>(Base
);
879 FileStr
= CursorRes
->CursorLoc
;
880 Type
= IconCursorGroupType::Cursor
;
884 stripQuotes(FileStr
, IsLong
);
885 auto File
= loadFile(FileStr
);
888 return File
.takeError();
890 BinaryStreamReader
Reader((*File
)->getBuffer(), support::little
);
892 // Read the file headers.
893 // - At the beginning, ICONDIR/NEWHEADER header.
894 // - Then, a number of RESDIR headers follow. These contain offsets
896 const GroupIconDir
*Header
;
898 RETURN_IF_ERROR(Reader
.readObject(Header
));
899 if (Header
->Reserved
!= 0)
900 return createError("Incorrect icon/cursor Reserved field; should be 0.");
901 uint16_t NeededType
= Type
== IconCursorGroupType::Icon
? 1 : 2;
902 if (Header
->ResType
!= NeededType
)
903 return createError("Incorrect icon/cursor ResType field; should be " +
904 Twine(NeededType
) + ".");
906 uint16_t NumItems
= Header
->ResCount
;
908 // Read single ico/cur headers.
909 std::vector
<ResourceDirEntryStart
> ItemEntries
;
910 ItemEntries
.reserve(NumItems
);
911 std::vector
<uint32_t> ItemOffsets(NumItems
);
912 for (size_t ID
= 0; ID
< NumItems
; ++ID
) {
913 const ResourceDirEntryStart
*Object
;
914 RETURN_IF_ERROR(Reader
.readObject(Object
));
915 ItemEntries
.push_back(*Object
);
916 RETURN_IF_ERROR(Reader
.readInteger(ItemOffsets
[ID
]));
919 // Now write each icon/cursors one by one. At first, all the contents
920 // without ICO/CUR header. This is described by SingleIconCursorResource.
921 for (size_t ID
= 0; ID
< NumItems
; ++ID
) {
922 // Load the fragment of file.
923 Reader
.setOffset(ItemOffsets
[ID
]);
924 ArrayRef
<uint8_t> Image
;
925 RETURN_IF_ERROR(Reader
.readArray(Image
, ItemEntries
[ID
].Size
));
926 SingleIconCursorResource
SingleRes(Type
, ItemEntries
[ID
], Image
,
928 SingleRes
.setName(IconCursorID
+ ID
);
929 RETURN_IF_ERROR(visitSingleIconOrCursor(&SingleRes
));
932 // Now, write all the headers concatenated into a separate resource.
933 for (size_t ID
= 0; ID
< NumItems
; ++ID
) {
934 // We need to rewrite the cursor headers, and fetch actual values
935 // for Planes/BitCount.
936 const auto &OldHeader
= ItemEntries
[ID
];
937 ResourceDirEntryStart NewHeader
= OldHeader
;
939 if (Type
== IconCursorGroupType::Cursor
) {
940 NewHeader
.Cursor
.Width
= OldHeader
.Icon
.Width
;
941 // Each cursor in fact stores two bitmaps, one under another.
942 // Height provided in cursor definition describes the height of the
943 // cursor, whereas the value existing in resource definition describes
944 // the height of the bitmap. Therefore, we need to double this height.
945 NewHeader
.Cursor
.Height
= OldHeader
.Icon
.Height
* 2;
947 // Two WORDs were written at the beginning of the resource (hotspot
948 // location). This is reflected in Size field.
949 NewHeader
.Size
+= 2 * sizeof(uint16_t);
952 // Now, we actually need to read the bitmap header to find
953 // the number of planes and the number of bits per pixel.
954 Reader
.setOffset(ItemOffsets
[ID
]);
955 const BitmapInfoHeader
*BMPHeader
;
956 RETURN_IF_ERROR(Reader
.readObject(BMPHeader
));
957 if (BMPHeader
->Size
== sizeof(BitmapInfoHeader
)) {
958 NewHeader
.Planes
= BMPHeader
->Planes
;
959 NewHeader
.BitCount
= BMPHeader
->BitCount
;
962 // https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473
963 // "The image must be in 32bpp"
964 NewHeader
.Planes
= 1;
965 NewHeader
.BitCount
= 32;
968 ItemEntries
[ID
] = NewHeader
;
971 IconCursorGroupResource
HeaderRes(Type
, *Header
, std::move(ItemEntries
));
972 HeaderRes
.setName(ResName
);
973 if (Base
->MemoryFlags
& MfPreload
) {
974 HeaderRes
.MemoryFlags
|= MfPreload
;
975 HeaderRes
.MemoryFlags
&= ~MfPure
;
977 RETURN_IF_ERROR(visitIconOrCursorGroup(&HeaderRes
));
979 return Error::success();
982 // --- DialogResource helpers. --- //
984 Error
ResourceFileWriter::writeSingleDialogControl(const Control
&Ctl
,
986 // Each control should be aligned to DWORD.
987 padStream(sizeof(uint32_t));
989 auto TypeInfo
= Control::SupportedCtls
.lookup(Ctl
.Type
);
990 IntWithNotMask
CtlStyle(TypeInfo
.Style
);
991 CtlStyle
|= Ctl
.Style
.getValueOr(RCInt(0));
992 uint32_t CtlExtStyle
= Ctl
.ExtStyle
.getValueOr(0);
994 // DIALOG(EX) item header prefix.
998 ulittle32_t ExtStyle
;
999 } Prefix
{ulittle32_t(CtlStyle
.getValue()), ulittle32_t(CtlExtStyle
)};
1000 writeObject(Prefix
);
1004 ulittle32_t ExtStyle
;
1006 } Prefix
{ulittle32_t(Ctl
.HelpID
.getValueOr(0)), ulittle32_t(CtlExtStyle
),
1007 ulittle32_t(CtlStyle
.getValue())};
1008 writeObject(Prefix
);
1011 // Common fixed-length part.
1012 RETURN_IF_ERROR(checkSignedNumberFits
<int16_t>(
1013 Ctl
.X
, "Dialog control x-coordinate", true));
1014 RETURN_IF_ERROR(checkSignedNumberFits
<int16_t>(
1015 Ctl
.Y
, "Dialog control y-coordinate", true));
1017 checkSignedNumberFits
<int16_t>(Ctl
.Width
, "Dialog control width", false));
1018 RETURN_IF_ERROR(checkSignedNumberFits
<int16_t>(
1019 Ctl
.Height
, "Dialog control height", false));
1025 } Middle
{ulittle16_t(Ctl
.X
), ulittle16_t(Ctl
.Y
), ulittle16_t(Ctl
.Width
),
1026 ulittle16_t(Ctl
.Height
)};
1027 writeObject(Middle
);
1029 // ID; it's 16-bit in DIALOG and 32-bit in DIALOGEX.
1031 // It's common to use -1, i.e. UINT32_MAX, for controls one doesn't
1032 // want to refer to later.
1033 if (Ctl
.ID
!= static_cast<uint32_t>(-1))
1034 RETURN_IF_ERROR(checkNumberFits
<uint16_t>(
1035 Ctl
.ID
, "Control ID in simple DIALOG resource"));
1036 writeInt
<uint16_t>(Ctl
.ID
);
1038 writeInt
<uint32_t>(Ctl
.ID
);
1041 // Window class - either 0xFFFF + 16-bit integer or a string.
1042 RETURN_IF_ERROR(writeIntOrString(Ctl
.Class
));
1044 // Element caption/reference ID. ID is preceded by 0xFFFF.
1045 RETURN_IF_ERROR(checkIntOrString(Ctl
.Title
, "Control reference ID"));
1046 RETURN_IF_ERROR(writeIntOrString(Ctl
.Title
));
1048 // # bytes of extra creation data count. Don't pass any.
1049 writeInt
<uint16_t>(0);
1051 return Error::success();
1054 Error
ResourceFileWriter::writeDialogBody(const RCResource
*Base
) {
1055 auto *Res
= cast
<DialogResource
>(Base
);
1057 // Default style: WS_POPUP | WS_BORDER | WS_SYSMENU.
1058 const uint32_t DefaultStyle
= 0x80880000;
1059 const uint32_t StyleFontFlag
= 0x40;
1060 const uint32_t StyleCaptionFlag
= 0x00C00000;
1062 uint32_t UsedStyle
= ObjectData
.Style
.getValueOr(DefaultStyle
);
1063 if (ObjectData
.Font
)
1064 UsedStyle
|= StyleFontFlag
;
1066 UsedStyle
&= ~StyleFontFlag
;
1068 // Actually, in case of empty (but existent) caption, the examined field
1069 // is equal to "\"\"". That's why empty captions are still noticed.
1070 if (ObjectData
.Caption
!= "")
1071 UsedStyle
|= StyleCaptionFlag
;
1073 const uint16_t DialogExMagic
= 0xFFFF;
1074 uint32_t ExStyle
= ObjectData
.ExStyle
.getValueOr(0);
1076 // Write DIALOG(EX) header prefix. These are pretty different.
1077 if (!Res
->IsExtended
) {
1078 // We cannot let the higher word of DefaultStyle be equal to 0xFFFF.
1079 // In such a case, whole object (in .res file) is equivalent to a
1080 // DIALOGEX. It might lead to access violation/segmentation fault in
1081 // resource readers. For example,
1082 // 1 DIALOG 0, 0, 0, 65432
1083 // STYLE 0xFFFF0001 {}
1084 // would be compiled to a DIALOGEX with 65432 controls.
1085 if ((UsedStyle
>> 16) == DialogExMagic
)
1086 return createError("16 higher bits of DIALOG resource style cannot be"
1087 " equal to 0xFFFF");
1091 ulittle32_t ExtStyle
;
1092 } Prefix
{ulittle32_t(UsedStyle
),
1093 ulittle32_t(ExStyle
)};
1095 writeObject(Prefix
);
1098 ulittle16_t Version
;
1101 ulittle32_t ExtStyle
;
1103 } Prefix
{ulittle16_t(1), ulittle16_t(DialogExMagic
),
1104 ulittle32_t(Res
->HelpID
), ulittle32_t(ExStyle
), ulittle32_t(UsedStyle
)};
1106 writeObject(Prefix
);
1109 // Now, a common part. First, fixed-length fields.
1110 RETURN_IF_ERROR(checkNumberFits
<uint16_t>(Res
->Controls
.size(),
1111 "Number of dialog controls"));
1113 checkSignedNumberFits
<int16_t>(Res
->X
, "Dialog x-coordinate", true));
1115 checkSignedNumberFits
<int16_t>(Res
->Y
, "Dialog y-coordinate", true));
1117 checkSignedNumberFits
<int16_t>(Res
->Width
, "Dialog width", false));
1119 checkSignedNumberFits
<int16_t>(Res
->Height
, "Dialog height", false));
1124 ulittle16_t DialogWidth
;
1125 ulittle16_t DialogHeight
;
1126 } Middle
{ulittle16_t(Res
->Controls
.size()), ulittle16_t(Res
->X
),
1127 ulittle16_t(Res
->Y
), ulittle16_t(Res
->Width
),
1128 ulittle16_t(Res
->Height
)};
1129 writeObject(Middle
);
1131 // MENU field. As of now, we don't keep them in the state and can peacefully
1132 // think there is no menu attached to the dialog.
1133 writeInt
<uint16_t>(0);
1135 // Window CLASS field.
1136 RETURN_IF_ERROR(writeIntOrString(ObjectData
.Class
));
1138 // Window title or a single word equal to 0.
1139 RETURN_IF_ERROR(writeCString(ObjectData
.Caption
));
1141 // If there *is* a window font declared, output its data.
1142 auto &Font
= ObjectData
.Font
;
1144 writeInt
<uint16_t>(Font
->Size
);
1145 // Additional description occurs only in DIALOGEX.
1146 if (Res
->IsExtended
) {
1147 writeInt
<uint16_t>(Font
->Weight
);
1148 writeInt
<uint8_t>(Font
->IsItalic
);
1149 writeInt
<uint8_t>(Font
->Charset
);
1151 RETURN_IF_ERROR(writeCString(Font
->Typeface
));
1154 auto handleCtlError
= [&](Error
&&Err
, const Control
&Ctl
) -> Error
{
1156 return Error::success();
1157 return joinErrors(createError("Error in " + Twine(Ctl
.Type
) +
1158 " control (ID " + Twine(Ctl
.ID
) + "):"),
1162 for (auto &Ctl
: Res
->Controls
)
1164 handleCtlError(writeSingleDialogControl(Ctl
, Res
->IsExtended
), Ctl
));
1166 return Error::success();
1169 // --- HTMLResource helpers. --- //
1171 Error
ResourceFileWriter::writeHTMLBody(const RCResource
*Base
) {
1172 return appendFile(cast
<HTMLResource
>(Base
)->HTMLLoc
);
1175 // --- MenuResource helpers. --- //
1177 Error
ResourceFileWriter::writeMenuDefinition(
1178 const std::unique_ptr
<MenuDefinition
> &Def
, uint16_t Flags
) {
1180 const MenuDefinition
*DefPtr
= Def
.get();
1182 if (auto *MenuItemPtr
= dyn_cast
<MenuItem
>(DefPtr
)) {
1183 writeInt
<uint16_t>(Flags
);
1184 // Some resource files use -1, i.e. UINT32_MAX, for empty menu items.
1185 if (MenuItemPtr
->Id
!= static_cast<uint32_t>(-1))
1187 checkNumberFits
<uint16_t>(MenuItemPtr
->Id
, "MENUITEM action ID"));
1188 writeInt
<uint16_t>(MenuItemPtr
->Id
);
1189 RETURN_IF_ERROR(writeCString(MenuItemPtr
->Name
));
1190 return Error::success();
1193 if (isa
<MenuSeparator
>(DefPtr
)) {
1194 writeInt
<uint16_t>(Flags
);
1195 writeInt
<uint32_t>(0);
1196 return Error::success();
1199 auto *PopupPtr
= cast
<PopupItem
>(DefPtr
);
1200 writeInt
<uint16_t>(Flags
);
1201 RETURN_IF_ERROR(writeCString(PopupPtr
->Name
));
1202 return writeMenuDefinitionList(PopupPtr
->SubItems
);
1205 Error
ResourceFileWriter::writeMenuDefinitionList(
1206 const MenuDefinitionList
&List
) {
1207 for (auto &Def
: List
.Definitions
) {
1208 uint16_t Flags
= Def
->getResFlags();
1209 // Last element receives an additional 0x80 flag.
1210 const uint16_t LastElementFlag
= 0x0080;
1211 if (&Def
== &List
.Definitions
.back())
1212 Flags
|= LastElementFlag
;
1214 RETURN_IF_ERROR(writeMenuDefinition(Def
, Flags
));
1216 return Error::success();
1219 Error
ResourceFileWriter::writeMenuBody(const RCResource
*Base
) {
1220 // At first, MENUHEADER structure. In fact, these are two WORDs equal to 0.
1221 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648018.aspx
1222 writeInt
<uint32_t>(0);
1224 return writeMenuDefinitionList(cast
<MenuResource
>(Base
)->Elements
);
1227 // --- StringTableResource helpers. --- //
1229 class BundleResource
: public RCResource
{
1231 using BundleType
= ResourceFileWriter::StringTableInfo::Bundle
;
1234 BundleResource(const BundleType
&StrBundle
)
1235 : RCResource(StrBundle
.MemoryFlags
), Bundle(StrBundle
) {}
1236 IntOrString
getResourceType() const override
{ return 6; }
1238 ResourceKind
getKind() const override
{ return RkStringTableBundle
; }
1239 static bool classof(const RCResource
*Res
) {
1240 return Res
->getKind() == RkStringTableBundle
;
1242 Twine
getResourceTypeName() const override
{ return "STRINGTABLE"; }
1245 Error
ResourceFileWriter::visitStringTableBundle(const RCResource
*Res
) {
1246 return writeResource(Res
, &ResourceFileWriter::writeStringTableBundleBody
);
1249 Error
ResourceFileWriter::insertStringIntoBundle(
1250 StringTableInfo::Bundle
&Bundle
, uint16_t StringID
,
1251 const std::vector
<StringRef
> &String
) {
1252 uint16_t StringLoc
= StringID
& 15;
1253 if (Bundle
.Data
[StringLoc
])
1254 return createError("Multiple STRINGTABLE strings located under ID " +
1256 Bundle
.Data
[StringLoc
] = String
;
1257 return Error::success();
1260 Error
ResourceFileWriter::writeStringTableBundleBody(const RCResource
*Base
) {
1261 auto *Res
= cast
<BundleResource
>(Base
);
1262 for (size_t ID
= 0; ID
< Res
->Bundle
.Data
.size(); ++ID
) {
1263 // The string format is a tiny bit different here. We
1264 // first output the size of the string, and then the string itself
1265 // (which is not null-terminated).
1266 SmallVector
<UTF16
, 128> Data
;
1267 if (Res
->Bundle
.Data
[ID
]) {
1269 for (StringRef S
: *Res
->Bundle
.Data
[ID
])
1270 RETURN_IF_ERROR(processString(S
, NullHandlingMethod::CutAtDoubleNull
,
1271 IsLongString
, Data
, Params
.CodePage
));
1273 Data
.push_back('\0');
1276 checkNumberFits
<uint16_t>(Data
.size(), "STRINGTABLE string size"));
1277 writeInt
<uint16_t>(Data
.size());
1278 for (auto Char
: Data
)
1281 return Error::success();
1284 Error
ResourceFileWriter::dumpAllStringTables() {
1285 for (auto Key
: StringTableData
.BundleList
) {
1286 auto Iter
= StringTableData
.BundleData
.find(Key
);
1287 assert(Iter
!= StringTableData
.BundleData
.end());
1289 // For a moment, revert the context info to moment of bundle declaration.
1290 ContextKeeper
RAII(this);
1291 ObjectData
= Iter
->second
.DeclTimeInfo
;
1293 BundleResource
Res(Iter
->second
);
1294 // Bundle #(k+1) contains keys [16k, 16k + 15].
1295 Res
.setName(Key
.first
+ 1);
1296 RETURN_IF_ERROR(visitStringTableBundle(&Res
));
1298 return Error::success();
1301 // --- UserDefinedResource helpers. --- //
1303 Error
ResourceFileWriter::writeUserDefinedBody(const RCResource
*Base
) {
1304 auto *Res
= cast
<UserDefinedResource
>(Base
);
1306 if (Res
->IsFileResource
)
1307 return appendFile(Res
->FileLoc
);
1309 for (auto &Elem
: Res
->Contents
) {
1312 checkRCInt(Elem
.getInt(), "Number in user-defined resource"));
1313 writeRCInt(Elem
.getInt());
1317 SmallVector
<UTF16
, 128> ProcessedString
;
1320 processString(Elem
.getString(), NullHandlingMethod::UserResource
,
1321 IsLongString
, ProcessedString
, Params
.CodePage
));
1323 for (auto Ch
: ProcessedString
) {
1329 RETURN_IF_ERROR(checkNumberFits
<uint8_t>(
1330 Ch
, "Character in narrow string in user-defined resource"));
1331 writeInt
<uint8_t>(Ch
);
1335 return Error::success();
1338 // --- VersionInfoResourceResource helpers. --- //
1340 Error
ResourceFileWriter::writeVersionInfoBlock(const VersionInfoBlock
&Blk
) {
1341 // Output the header if the block has name.
1342 bool OutputHeader
= Blk
.Name
!= "";
1345 padStream(sizeof(uint32_t));
1347 LengthLoc
= writeInt
<uint16_t>(0);
1348 writeInt
<uint16_t>(0);
1349 writeInt
<uint16_t>(1); // true
1350 RETURN_IF_ERROR(writeCString(Blk
.Name
));
1351 padStream(sizeof(uint32_t));
1354 for (const std::unique_ptr
<VersionInfoStmt
> &Item
: Blk
.Stmts
) {
1355 VersionInfoStmt
*ItemPtr
= Item
.get();
1357 if (auto *BlockPtr
= dyn_cast
<VersionInfoBlock
>(ItemPtr
)) {
1358 RETURN_IF_ERROR(writeVersionInfoBlock(*BlockPtr
));
1362 auto *ValuePtr
= cast
<VersionInfoValue
>(ItemPtr
);
1363 RETURN_IF_ERROR(writeVersionInfoValue(*ValuePtr
));
1367 uint64_t CurLoc
= tell();
1368 writeObjectAt(ulittle16_t(CurLoc
- LengthLoc
), LengthLoc
);
1371 return Error::success();
1374 Error
ResourceFileWriter::writeVersionInfoValue(const VersionInfoValue
&Val
) {
1375 // rc has a peculiar algorithm to output VERSIONINFO VALUEs. Each VALUE
1376 // is a mapping from the key (string) to the value (a sequence of ints or
1377 // a sequence of strings).
1379 // If integers are to be written: width of each integer written depends on
1380 // whether it's been declared 'long' (it's DWORD then) or not (it's WORD).
1381 // ValueLength defined in structure referenced below is then the total
1382 // number of bytes taken by these integers.
1384 // If strings are to be written: characters are always WORDs.
1385 // Moreover, '\0' character is written after the last string, and between
1386 // every two strings separated by comma (if strings are not comma-separated,
1387 // they're simply concatenated). ValueLength is equal to the number of WORDs
1388 // written (that is, half of the bytes written).
1390 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms646994.aspx
1391 bool HasStrings
= false, HasInts
= false;
1392 for (auto &Item
: Val
.Values
)
1393 (Item
.isInt() ? HasInts
: HasStrings
) = true;
1395 assert((HasStrings
|| HasInts
) && "VALUE must have at least one argument");
1396 if (HasStrings
&& HasInts
)
1397 return createError(Twine("VALUE ") + Val
.Key
+
1398 " cannot contain both strings and integers");
1400 padStream(sizeof(uint32_t));
1401 auto LengthLoc
= writeInt
<uint16_t>(0);
1402 auto ValLengthLoc
= writeInt
<uint16_t>(0);
1403 writeInt
<uint16_t>(HasStrings
);
1404 RETURN_IF_ERROR(writeCString(Val
.Key
));
1405 padStream(sizeof(uint32_t));
1407 auto DataLoc
= tell();
1408 for (size_t Id
= 0; Id
< Val
.Values
.size(); ++Id
) {
1409 auto &Item
= Val
.Values
[Id
];
1411 auto Value
= Item
.getInt();
1412 RETURN_IF_ERROR(checkRCInt(Value
, "VERSIONINFO integer value"));
1417 bool WriteTerminator
=
1418 Id
== Val
.Values
.size() - 1 || Val
.HasPrecedingComma
[Id
+ 1];
1419 RETURN_IF_ERROR(writeCString(Item
.getString(), WriteTerminator
));
1422 auto CurLoc
= tell();
1423 auto ValueLength
= CurLoc
- DataLoc
;
1425 assert(ValueLength
% 2 == 0);
1428 writeObjectAt(ulittle16_t(CurLoc
- LengthLoc
), LengthLoc
);
1429 writeObjectAt(ulittle16_t(ValueLength
), ValLengthLoc
);
1430 return Error::success();
1433 template <typename Ty
>
1434 static Ty
getWithDefault(const StringMap
<Ty
> &Map
, StringRef Key
,
1435 const Ty
&Default
) {
1436 auto Iter
= Map
.find(Key
);
1437 if (Iter
!= Map
.end())
1438 return Iter
->getValue();
1442 Error
ResourceFileWriter::writeVersionInfoBody(const RCResource
*Base
) {
1443 auto *Res
= cast
<VersionInfoResource
>(Base
);
1445 const auto &FixedData
= Res
->FixedData
;
1447 struct /* VS_FIXEDFILEINFO */ {
1448 ulittle32_t Signature
= ulittle32_t(0xFEEF04BD);
1449 ulittle32_t StructVersion
= ulittle32_t(0x10000);
1450 // It's weird to have most-significant DWORD first on the little-endian
1451 // machines, but let it be this way.
1452 ulittle32_t FileVersionMS
;
1453 ulittle32_t FileVersionLS
;
1454 ulittle32_t ProductVersionMS
;
1455 ulittle32_t ProductVersionLS
;
1456 ulittle32_t FileFlagsMask
;
1457 ulittle32_t FileFlags
;
1459 ulittle32_t FileType
;
1460 ulittle32_t FileSubtype
;
1461 // MS implementation seems to always set these fields to 0.
1462 ulittle32_t FileDateMS
= ulittle32_t(0);
1463 ulittle32_t FileDateLS
= ulittle32_t(0);
1466 // First, VS_VERSIONINFO.
1467 auto LengthLoc
= writeInt
<uint16_t>(0);
1468 writeInt
<uint16_t>(sizeof(FixedInfo
));
1469 writeInt
<uint16_t>(0);
1470 cantFail(writeCString("VS_VERSION_INFO"));
1471 padStream(sizeof(uint32_t));
1473 using VersionInfoFixed
= VersionInfoResource::VersionInfoFixed
;
1474 auto GetField
= [&](VersionInfoFixed::VersionInfoFixedType Type
) {
1475 static const SmallVector
<uint32_t, 4> DefaultOut
{0, 0, 0, 0};
1476 if (!FixedData
.IsTypePresent
[(int)Type
])
1478 return FixedData
.FixedInfo
[(int)Type
];
1481 auto FileVer
= GetField(VersionInfoFixed::FtFileVersion
);
1482 RETURN_IF_ERROR(checkNumberFits
<uint16_t>(
1483 *std::max_element(FileVer
.begin(), FileVer
.end()), "FILEVERSION fields"));
1484 FixedInfo
.FileVersionMS
= (FileVer
[0] << 16) | FileVer
[1];
1485 FixedInfo
.FileVersionLS
= (FileVer
[2] << 16) | FileVer
[3];
1487 auto ProdVer
= GetField(VersionInfoFixed::FtProductVersion
);
1488 RETURN_IF_ERROR(checkNumberFits
<uint16_t>(
1489 *std::max_element(ProdVer
.begin(), ProdVer
.end()),
1490 "PRODUCTVERSION fields"));
1491 FixedInfo
.ProductVersionMS
= (ProdVer
[0] << 16) | ProdVer
[1];
1492 FixedInfo
.ProductVersionLS
= (ProdVer
[2] << 16) | ProdVer
[3];
1494 FixedInfo
.FileFlagsMask
= GetField(VersionInfoFixed::FtFileFlagsMask
)[0];
1495 FixedInfo
.FileFlags
= GetField(VersionInfoFixed::FtFileFlags
)[0];
1496 FixedInfo
.FileOS
= GetField(VersionInfoFixed::FtFileOS
)[0];
1497 FixedInfo
.FileType
= GetField(VersionInfoFixed::FtFileType
)[0];
1498 FixedInfo
.FileSubtype
= GetField(VersionInfoFixed::FtFileSubtype
)[0];
1500 writeObject(FixedInfo
);
1501 padStream(sizeof(uint32_t));
1503 RETURN_IF_ERROR(writeVersionInfoBlock(Res
->MainBlock
));
1505 // FIXME: check overflow?
1506 writeObjectAt(ulittle16_t(tell() - LengthLoc
), LengthLoc
);
1508 return Error::success();
1511 Expected
<std::unique_ptr
<MemoryBuffer
>>
1512 ResourceFileWriter::loadFile(StringRef File
) const {
1513 SmallString
<128> Path
;
1514 SmallString
<128> Cwd
;
1515 std::unique_ptr
<MemoryBuffer
> Result
;
1517 // 0. The file path is absolute or has a root directory, so we shouldn't
1518 // try to append it on top of other base directories. (An absolute path
1519 // must have a root directory, but e.g. the path "\dir\file" on windows
1520 // isn't considered absolute, but it does have a root directory. As long as
1521 // sys::path::append doesn't handle appending an absolute path or a path
1522 // starting with a root directory on top of a base, we must handle this
1523 // case separately at the top. C++17's path::append handles that case
1524 // properly though, so if using that to append paths below, this early
1525 // exception case could be removed.)
1526 if (sys::path::has_root_directory(File
))
1527 return errorOrToExpected(MemoryBuffer::getFile(
1528 File
, /*IsText=*/false, /*RequiresNullTerminator=*/false));
1530 // 1. The current working directory.
1531 sys::fs::current_path(Cwd
);
1532 Path
.assign(Cwd
.begin(), Cwd
.end());
1533 sys::path::append(Path
, File
);
1534 if (sys::fs::exists(Path
))
1535 return errorOrToExpected(MemoryBuffer::getFile(
1536 Path
, /*IsText=*/false, /*RequiresNullTerminator=*/false));
1538 // 2. The directory of the input resource file, if it is different from the
1539 // current working directory.
1540 StringRef InputFileDir
= sys::path::parent_path(Params
.InputFilePath
);
1541 Path
.assign(InputFileDir
.begin(), InputFileDir
.end());
1542 sys::path::append(Path
, File
);
1543 if (sys::fs::exists(Path
))
1544 return errorOrToExpected(MemoryBuffer::getFile(
1545 Path
, /*IsText=*/false, /*RequiresNullTerminator=*/false));
1547 // 3. All of the include directories specified on the command line.
1548 for (StringRef ForceInclude
: Params
.Include
) {
1549 Path
.assign(ForceInclude
.begin(), ForceInclude
.end());
1550 sys::path::append(Path
, File
);
1551 if (sys::fs::exists(Path
))
1552 return errorOrToExpected(MemoryBuffer::getFile(
1553 Path
, /*IsText=*/false, /*RequiresNullTerminator=*/false));
1556 if (!Params
.NoInclude
) {
1557 if (auto Result
= llvm::sys::Process::FindInEnvPath("INCLUDE", File
))
1558 return errorOrToExpected(MemoryBuffer::getFile(
1559 *Result
, /*IsText=*/false, /*RequiresNullTerminator=*/false));
1562 return make_error
<StringError
>("error : file not found : " + Twine(File
),
1563 inconvertibleErrorCode());