[CMake] Allow overriding MSVC_DIA_SDK_DIR via CMake
[llvm-core.git] / tools / llvm-rc / ResourceFileWriter.cpp
blob44b3fe143ce89e59445fe68b6d532a22e3e4fb97
1 //===-- ResourceFileWriter.cpp --------------------------------*- C++-*-===//
2 //
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
6 //
7 //===---------------------------------------------------------------------===//
8 //
9 // This implements the visitor serializing resources to a .res stream.
11 //===---------------------------------------------------------------------===//
13 #include "ResourceFileWriter.h"
15 #include "llvm/Object/WindowsResource.h"
16 #include "llvm/Support/ConvertUTF.h"
17 #include "llvm/Support/Endian.h"
18 #include "llvm/Support/EndianStream.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)) \
28 return Err;
30 namespace llvm {
31 namespace rc {
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.
36 class ContextKeeper {
37 ResourceFileWriter *FileWriter;
38 ResourceFileWriter::ObjectInfo SavedInfo;
40 public:
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,
69 bool CanBeNegative) {
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) {
86 if (Number.isLong())
87 return Error::success();
88 return checkNumberFits<uint16_t>(Number, FieldName);
91 static Error checkIntOrString(IntOrString Value, const Twine &FieldName) {
92 if (!Value.isInt())
93 return Error::success();
94 return checkNumberFits<uint16_t>(Value.getInt(), FieldName);
97 static bool stripQuotes(StringRef &Str, bool &IsLongString) {
98 if (!Str.contains('"'))
99 return false;
101 // Just take the contents of the string, checking if it's been marked long.
102 IsLongString = Str.startswith_lower("L");
103 if (IsLongString)
104 Str = Str.drop_front();
106 bool StripSuccess = Str.consume_front("\"") && Str.consume_back("\"");
107 (void)StripSuccess;
108 assert(StripSuccess && "Strings should be enclosed in quotes.");
109 return true;
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];
121 return C;
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 // * String the string boundary quotes.
142 // * Squash "" to a single ".
143 // * Replace the escape sequences with their processed version.
144 // For identifiers, this is no-op.
145 static Error processString(StringRef Str, NullHandlingMethod NullHandler,
146 bool &IsLongString, SmallVectorImpl<UTF16> &Result,
147 int CodePage) {
148 bool IsString = stripQuotes(Str, IsLongString);
149 SmallVector<UTF16, 128> Chars;
151 // Convert the input bytes according to the chosen codepage.
152 if (CodePage == CpUtf8) {
153 convertUTF8ToUTF16String(Str, Chars);
154 } else if (CodePage == CpWin1252) {
155 for (char C : Str)
156 Chars.push_back(cp1252ToUnicode((unsigned char)C));
157 } else {
158 // For other, unknown codepages, only allow plain ASCII input.
159 for (char C : Str) {
160 if ((unsigned char)C > 0x7F)
161 return createError("Non-ASCII 8-bit codepoint (" + Twine(C) +
162 ") can't be interpreted in the current codepage");
163 Chars.push_back((unsigned char)C);
167 if (!IsString) {
168 // It's an identifier if it's not a string. Make all characters uppercase.
169 for (UTF16 &Ch : Chars) {
170 assert(Ch <= 0x7F && "We didn't allow identifiers to be non-ASCII");
171 Ch = toupper(Ch);
173 Result.swap(Chars);
174 return Error::success();
176 Result.reserve(Chars.size());
177 size_t Pos = 0;
179 auto AddRes = [&Result, NullHandler, IsLongString](UTF16 Char) -> Error {
180 if (!IsLongString) {
181 if (NullHandler == NullHandlingMethod::UserResource) {
182 // Narrow strings in user-defined resources are *not* output in
183 // UTF-16 format.
184 if (Char > 0xFF)
185 return createError("Non-8-bit codepoint (" + Twine(Char) +
186 ") can't occur in a user-defined narrow string");
190 Result.push_back(Char);
191 return Error::success();
193 auto AddEscapedChar = [AddRes, IsLongString, CodePage](UTF16 Char) -> Error {
194 if (!IsLongString) {
195 // Escaped chars in narrow strings have to be interpreted according to
196 // the chosen code page.
197 if (Char > 0xFF)
198 return createError("Non-8-bit escaped char (" + Twine(Char) +
199 ") can't occur in narrow string");
200 if (CodePage == CpUtf8) {
201 if (Char >= 0x80)
202 return createError("Unable to interpret single byte (" + Twine(Char) +
203 ") as UTF-8");
204 } else if (CodePage == CpWin1252) {
205 Char = cp1252ToUnicode(Char);
206 } else {
207 // Unknown/unsupported codepage, only allow ASCII input.
208 if (Char > 0x7F)
209 return createError("Non-ASCII 8-bit codepoint (" + Twine(Char) +
210 ") can't "
211 "occur in a non-Unicode string");
215 return AddRes(Char);
218 while (Pos < Chars.size()) {
219 UTF16 CurChar = Chars[Pos];
220 ++Pos;
222 // Strip double "".
223 if (CurChar == '"') {
224 if (Pos == Chars.size() || Chars[Pos] != '"')
225 return createError("Expected \"\"");
226 ++Pos;
227 RETURN_IF_ERROR(AddRes('"'));
228 continue;
231 if (CurChar == '\\') {
232 UTF16 TypeChar = Chars[Pos];
233 ++Pos;
235 if (TypeChar == 'x' || TypeChar == 'X') {
236 // Read a hex number. Max number of characters to read differs between
237 // narrow and wide strings.
238 UTF16 ReadInt = 0;
239 size_t RemainingChars = IsLongString ? 4 : 2;
240 // We don't want to read non-ASCII hex digits. std:: functions past
241 // 0xFF invoke UB.
243 // FIXME: actually, Microsoft version probably doesn't check this
244 // condition and uses their Unicode version of 'isxdigit'. However,
245 // there are some hex-digit Unicode character outside of ASCII, and
246 // some of these are actually accepted by rc.exe, the notable example
247 // being fullwidth forms (U+FF10..U+FF19 etc.) These can be written
248 // instead of ASCII digits in \x... escape sequence and get accepted.
249 // However, the resulting hexcodes seem totally unpredictable.
250 // We think it's infeasible to try to reproduce this behavior, nor to
251 // put effort in order to detect it.
252 while (RemainingChars && Pos < Chars.size() && Chars[Pos] < 0x80) {
253 if (!isxdigit(Chars[Pos]))
254 break;
255 char Digit = tolower(Chars[Pos]);
256 ++Pos;
258 ReadInt <<= 4;
259 if (isdigit(Digit))
260 ReadInt |= Digit - '0';
261 else
262 ReadInt |= Digit - 'a' + 10;
264 --RemainingChars;
267 RETURN_IF_ERROR(AddEscapedChar(ReadInt));
268 continue;
271 if (TypeChar >= '0' && TypeChar < '8') {
272 // Read an octal number. Note that we've already read the first digit.
273 UTF16 ReadInt = TypeChar - '0';
274 size_t RemainingChars = IsLongString ? 6 : 2;
276 while (RemainingChars && Pos < Chars.size() && Chars[Pos] >= '0' &&
277 Chars[Pos] < '8') {
278 ReadInt <<= 3;
279 ReadInt |= Chars[Pos] - '0';
280 --RemainingChars;
281 ++Pos;
284 RETURN_IF_ERROR(AddEscapedChar(ReadInt));
286 continue;
289 switch (TypeChar) {
290 case 'A':
291 case 'a':
292 // Windows '\a' translates into '\b' (Backspace).
293 RETURN_IF_ERROR(AddRes('\b'));
294 break;
296 case 'n': // Somehow, RC doesn't recognize '\N' and '\R'.
297 RETURN_IF_ERROR(AddRes('\n'));
298 break;
300 case 'r':
301 RETURN_IF_ERROR(AddRes('\r'));
302 break;
304 case 'T':
305 case 't':
306 RETURN_IF_ERROR(AddRes('\t'));
307 break;
309 case '\\':
310 RETURN_IF_ERROR(AddRes('\\'));
311 break;
313 case '"':
314 // RC accepts \" only if another " comes afterwards; then, \"" means
315 // a single ".
316 if (Pos == Chars.size() || Chars[Pos] != '"')
317 return createError("Expected \\\"\"");
318 ++Pos;
319 RETURN_IF_ERROR(AddRes('"'));
320 break;
322 default:
323 // If TypeChar means nothing, \ is should be output to stdout with
324 // following char. However, rc.exe consumes these characters when
325 // dealing with wide strings.
326 if (!IsLongString) {
327 RETURN_IF_ERROR(AddRes('\\'));
328 RETURN_IF_ERROR(AddRes(TypeChar));
330 break;
333 continue;
336 // If nothing interesting happens, just output the character.
337 RETURN_IF_ERROR(AddRes(CurChar));
340 switch (NullHandler) {
341 case NullHandlingMethod::CutAtNull:
342 for (size_t Pos = 0; Pos < Result.size(); ++Pos)
343 if (Result[Pos] == '\0')
344 Result.resize(Pos);
345 break;
347 case NullHandlingMethod::CutAtDoubleNull:
348 for (size_t Pos = 0; Pos + 1 < Result.size(); ++Pos)
349 if (Result[Pos] == '\0' && Result[Pos + 1] == '\0')
350 Result.resize(Pos);
351 if (Result.size() > 0 && Result.back() == '\0')
352 Result.pop_back();
353 break;
355 case NullHandlingMethod::UserResource:
356 break;
359 return Error::success();
362 uint64_t ResourceFileWriter::writeObject(const ArrayRef<uint8_t> Data) {
363 uint64_t Result = tell();
364 FS->write((const char *)Data.begin(), Data.size());
365 return Result;
368 Error ResourceFileWriter::writeCString(StringRef Str, bool WriteTerminator) {
369 SmallVector<UTF16, 128> ProcessedString;
370 bool IsLongString;
371 RETURN_IF_ERROR(processString(Str, NullHandlingMethod::CutAtNull,
372 IsLongString, ProcessedString,
373 Params.CodePage));
374 for (auto Ch : ProcessedString)
375 writeInt<uint16_t>(Ch);
376 if (WriteTerminator)
377 writeInt<uint16_t>(0);
378 return Error::success();
381 Error ResourceFileWriter::writeIdentifier(const IntOrString &Ident) {
382 return writeIntOrString(Ident);
385 Error ResourceFileWriter::writeIntOrString(const IntOrString &Value) {
386 if (!Value.isInt())
387 return writeCString(Value.getString());
389 writeInt<uint16_t>(0xFFFF);
390 writeInt<uint16_t>(Value.getInt());
391 return Error::success();
394 void ResourceFileWriter::writeRCInt(RCInt Value) {
395 if (Value.isLong())
396 writeInt<uint32_t>(Value);
397 else
398 writeInt<uint16_t>(Value);
401 Error ResourceFileWriter::appendFile(StringRef Filename) {
402 bool IsLong;
403 stripQuotes(Filename, IsLong);
405 auto File = loadFile(Filename);
406 if (!File)
407 return File.takeError();
409 *FS << (*File)->getBuffer();
410 return Error::success();
413 void ResourceFileWriter::padStream(uint64_t Length) {
414 assert(Length > 0);
415 uint64_t Location = tell();
416 Location %= Length;
417 uint64_t Pad = (Length - Location) % Length;
418 for (uint64_t i = 0; i < Pad; ++i)
419 writeInt<uint8_t>(0);
422 Error ResourceFileWriter::handleError(Error Err, const RCResource *Res) {
423 if (Err)
424 return joinErrors(createError("Error in " + Res->getResourceTypeName() +
425 " statement (ID " + Twine(Res->ResName) +
426 "): "),
427 std::move(Err));
428 return Error::success();
431 Error ResourceFileWriter::visitNullResource(const RCResource *Res) {
432 return writeResource(Res, &ResourceFileWriter::writeNullBody);
435 Error ResourceFileWriter::visitAcceleratorsResource(const RCResource *Res) {
436 return writeResource(Res, &ResourceFileWriter::writeAcceleratorsBody);
439 Error ResourceFileWriter::visitBitmapResource(const RCResource *Res) {
440 return writeResource(Res, &ResourceFileWriter::writeBitmapBody);
443 Error ResourceFileWriter::visitCursorResource(const RCResource *Res) {
444 return handleError(visitIconOrCursorResource(Res), Res);
447 Error ResourceFileWriter::visitDialogResource(const RCResource *Res) {
448 return writeResource(Res, &ResourceFileWriter::writeDialogBody);
451 Error ResourceFileWriter::visitIconResource(const RCResource *Res) {
452 return handleError(visitIconOrCursorResource(Res), Res);
455 Error ResourceFileWriter::visitCaptionStmt(const CaptionStmt *Stmt) {
456 ObjectData.Caption = Stmt->Value;
457 return Error::success();
460 Error ResourceFileWriter::visitClassStmt(const ClassStmt *Stmt) {
461 ObjectData.Class = Stmt->Value;
462 return Error::success();
465 Error ResourceFileWriter::visitHTMLResource(const RCResource *Res) {
466 return writeResource(Res, &ResourceFileWriter::writeHTMLBody);
469 Error ResourceFileWriter::visitMenuResource(const RCResource *Res) {
470 return writeResource(Res, &ResourceFileWriter::writeMenuBody);
473 Error ResourceFileWriter::visitStringTableResource(const RCResource *Base) {
474 const auto *Res = cast<StringTableResource>(Base);
476 ContextKeeper RAII(this);
477 RETURN_IF_ERROR(Res->applyStmts(this));
479 for (auto &String : Res->Table) {
480 RETURN_IF_ERROR(checkNumberFits<uint16_t>(String.first, "String ID"));
481 uint16_t BundleID = String.first >> 4;
482 StringTableInfo::BundleKey Key(BundleID, ObjectData.LanguageInfo);
483 auto &BundleData = StringTableData.BundleData;
484 auto Iter = BundleData.find(Key);
486 if (Iter == BundleData.end()) {
487 // Need to create a bundle.
488 StringTableData.BundleList.push_back(Key);
489 auto EmplaceResult = BundleData.emplace(
490 Key, StringTableInfo::Bundle(ObjectData, Res->MemoryFlags));
491 assert(EmplaceResult.second && "Could not create a bundle");
492 Iter = EmplaceResult.first;
495 RETURN_IF_ERROR(
496 insertStringIntoBundle(Iter->second, String.first, String.second));
499 return Error::success();
502 Error ResourceFileWriter::visitUserDefinedResource(const RCResource *Res) {
503 return writeResource(Res, &ResourceFileWriter::writeUserDefinedBody);
506 Error ResourceFileWriter::visitVersionInfoResource(const RCResource *Res) {
507 return writeResource(Res, &ResourceFileWriter::writeVersionInfoBody);
510 Error ResourceFileWriter::visitCharacteristicsStmt(
511 const CharacteristicsStmt *Stmt) {
512 ObjectData.Characteristics = Stmt->Value;
513 return Error::success();
516 Error ResourceFileWriter::visitExStyleStmt(const ExStyleStmt *Stmt) {
517 ObjectData.ExStyle = Stmt->Value;
518 return Error::success();
521 Error ResourceFileWriter::visitFontStmt(const FontStmt *Stmt) {
522 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Stmt->Size, "Font size"));
523 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Stmt->Weight, "Font weight"));
524 RETURN_IF_ERROR(checkNumberFits<uint8_t>(Stmt->Charset, "Font charset"));
525 ObjectInfo::FontInfo Font{Stmt->Size, Stmt->Name, Stmt->Weight, Stmt->Italic,
526 Stmt->Charset};
527 ObjectData.Font.emplace(Font);
528 return Error::success();
531 Error ResourceFileWriter::visitLanguageStmt(const LanguageResource *Stmt) {
532 RETURN_IF_ERROR(checkNumberFits(Stmt->Lang, 10, "Primary language ID"));
533 RETURN_IF_ERROR(checkNumberFits(Stmt->SubLang, 6, "Sublanguage ID"));
534 ObjectData.LanguageInfo = Stmt->Lang | (Stmt->SubLang << 10);
535 return Error::success();
538 Error ResourceFileWriter::visitStyleStmt(const StyleStmt *Stmt) {
539 ObjectData.Style = Stmt->Value;
540 return Error::success();
543 Error ResourceFileWriter::visitVersionStmt(const VersionStmt *Stmt) {
544 ObjectData.VersionInfo = Stmt->Value;
545 return Error::success();
548 Error ResourceFileWriter::writeResource(
549 const RCResource *Res,
550 Error (ResourceFileWriter::*BodyWriter)(const RCResource *)) {
551 // We don't know the sizes yet.
552 object::WinResHeaderPrefix HeaderPrefix{ulittle32_t(0U), ulittle32_t(0U)};
553 uint64_t HeaderLoc = writeObject(HeaderPrefix);
555 auto ResType = Res->getResourceType();
556 RETURN_IF_ERROR(checkIntOrString(ResType, "Resource type"));
557 RETURN_IF_ERROR(checkIntOrString(Res->ResName, "Resource ID"));
558 RETURN_IF_ERROR(handleError(writeIdentifier(ResType), Res));
559 RETURN_IF_ERROR(handleError(writeIdentifier(Res->ResName), Res));
561 // Apply the resource-local optional statements.
562 ContextKeeper RAII(this);
563 RETURN_IF_ERROR(handleError(Res->applyStmts(this), Res));
565 padStream(sizeof(uint32_t));
566 object::WinResHeaderSuffix HeaderSuffix{
567 ulittle32_t(0), // DataVersion; seems to always be 0
568 ulittle16_t(Res->MemoryFlags), ulittle16_t(ObjectData.LanguageInfo),
569 ulittle32_t(ObjectData.VersionInfo),
570 ulittle32_t(ObjectData.Characteristics)};
571 writeObject(HeaderSuffix);
573 uint64_t DataLoc = tell();
574 RETURN_IF_ERROR(handleError((this->*BodyWriter)(Res), Res));
575 // RETURN_IF_ERROR(handleError(dumpResource(Ctx)));
577 // Update the sizes.
578 HeaderPrefix.DataSize = tell() - DataLoc;
579 HeaderPrefix.HeaderSize = DataLoc - HeaderLoc;
580 writeObjectAt(HeaderPrefix, HeaderLoc);
581 padStream(sizeof(uint32_t));
583 return Error::success();
586 // --- NullResource helpers. --- //
588 Error ResourceFileWriter::writeNullBody(const RCResource *) {
589 return Error::success();
592 // --- AcceleratorsResource helpers. --- //
594 Error ResourceFileWriter::writeSingleAccelerator(
595 const AcceleratorsResource::Accelerator &Obj, bool IsLastItem) {
596 using Accelerator = AcceleratorsResource::Accelerator;
597 using Opt = Accelerator::Options;
599 struct AccelTableEntry {
600 ulittle16_t Flags;
601 ulittle16_t ANSICode;
602 ulittle16_t Id;
603 uint16_t Padding;
604 } Entry{ulittle16_t(0), ulittle16_t(0), ulittle16_t(0), 0};
606 bool IsASCII = Obj.Flags & Opt::ASCII, IsVirtKey = Obj.Flags & Opt::VIRTKEY;
608 // Remove ASCII flags (which doesn't occur in .res files).
609 Entry.Flags = Obj.Flags & ~Opt::ASCII;
611 if (IsLastItem)
612 Entry.Flags |= 0x80;
614 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Obj.Id, "ACCELERATORS entry ID"));
615 Entry.Id = ulittle16_t(Obj.Id);
617 auto createAccError = [&Obj](const char *Msg) {
618 return createError("Accelerator ID " + Twine(Obj.Id) + ": " + Msg);
621 if (IsASCII && IsVirtKey)
622 return createAccError("Accelerator can't be both ASCII and VIRTKEY");
624 if (!IsVirtKey && (Obj.Flags & (Opt::ALT | Opt::SHIFT | Opt::CONTROL)))
625 return createAccError("Can only apply ALT, SHIFT or CONTROL to VIRTKEY"
626 " accelerators");
628 if (Obj.Event.isInt()) {
629 if (!IsASCII && !IsVirtKey)
630 return createAccError(
631 "Accelerator with a numeric event must be either ASCII"
632 " or VIRTKEY");
634 uint32_t EventVal = Obj.Event.getInt();
635 RETURN_IF_ERROR(
636 checkNumberFits<uint16_t>(EventVal, "Numeric event key ID"));
637 Entry.ANSICode = ulittle16_t(EventVal);
638 writeObject(Entry);
639 return Error::success();
642 StringRef Str = Obj.Event.getString();
643 bool IsWide;
644 stripQuotes(Str, IsWide);
646 if (Str.size() == 0 || Str.size() > 2)
647 return createAccError(
648 "Accelerator string events should have length 1 or 2");
650 if (Str[0] == '^') {
651 if (Str.size() == 1)
652 return createAccError("No character following '^' in accelerator event");
653 if (IsVirtKey)
654 return createAccError(
655 "VIRTKEY accelerator events can't be preceded by '^'");
657 char Ch = Str[1];
658 if (Ch >= 'a' && Ch <= 'z')
659 Entry.ANSICode = ulittle16_t(Ch - 'a' + 1);
660 else if (Ch >= 'A' && Ch <= 'Z')
661 Entry.ANSICode = ulittle16_t(Ch - 'A' + 1);
662 else
663 return createAccError("Control character accelerator event should be"
664 " alphabetic");
666 writeObject(Entry);
667 return Error::success();
670 if (Str.size() == 2)
671 return createAccError("Event string should be one-character, possibly"
672 " preceded by '^'");
674 uint8_t EventCh = Str[0];
675 // The original tool just warns in this situation. We chose to fail.
676 if (IsVirtKey && !isalnum(EventCh))
677 return createAccError("Non-alphanumeric characters cannot describe virtual"
678 " keys");
679 if (EventCh > 0x7F)
680 return createAccError("Non-ASCII description of accelerator");
682 if (IsVirtKey)
683 EventCh = toupper(EventCh);
684 Entry.ANSICode = ulittle16_t(EventCh);
685 writeObject(Entry);
686 return Error::success();
689 Error ResourceFileWriter::writeAcceleratorsBody(const RCResource *Base) {
690 auto *Res = cast<AcceleratorsResource>(Base);
691 size_t AcceleratorId = 0;
692 for (auto &Acc : Res->Accelerators) {
693 ++AcceleratorId;
694 RETURN_IF_ERROR(
695 writeSingleAccelerator(Acc, AcceleratorId == Res->Accelerators.size()));
697 return Error::success();
700 // --- BitmapResource helpers. --- //
702 Error ResourceFileWriter::writeBitmapBody(const RCResource *Base) {
703 StringRef Filename = cast<BitmapResource>(Base)->BitmapLoc;
704 bool IsLong;
705 stripQuotes(Filename, IsLong);
707 auto File = loadFile(Filename);
708 if (!File)
709 return File.takeError();
711 StringRef Buffer = (*File)->getBuffer();
713 // Skip the 14 byte BITMAPFILEHEADER.
714 constexpr size_t BITMAPFILEHEADER_size = 14;
715 if (Buffer.size() < BITMAPFILEHEADER_size || Buffer[0] != 'B' ||
716 Buffer[1] != 'M')
717 return createError("Incorrect bitmap file.");
719 *FS << Buffer.substr(BITMAPFILEHEADER_size);
720 return Error::success();
723 // --- CursorResource and IconResource helpers. --- //
725 // ICONRESDIR structure. Describes a single icon in resource group.
727 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648016.aspx
728 struct IconResDir {
729 uint8_t Width;
730 uint8_t Height;
731 uint8_t ColorCount;
732 uint8_t Reserved;
735 // CURSORDIR structure. Describes a single cursor in resource group.
737 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648011(v=vs.85).aspx
738 struct CursorDir {
739 ulittle16_t Width;
740 ulittle16_t Height;
743 // RESDIRENTRY structure, stripped from the last item. Stripping made
744 // for compatibility with RESDIR.
746 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026(v=vs.85).aspx
747 struct ResourceDirEntryStart {
748 union {
749 CursorDir Cursor; // Used in CURSOR resources.
750 IconResDir Icon; // Used in .ico and .cur files, and ICON resources.
752 ulittle16_t Planes; // HotspotX (.cur files but not CURSOR resource).
753 ulittle16_t BitCount; // HotspotY (.cur files but not CURSOR resource).
754 ulittle32_t Size;
755 // ulittle32_t ImageOffset; // Offset to image data (ICONDIRENTRY only).
756 // ulittle16_t IconID; // Resource icon ID (RESDIR only).
759 // BITMAPINFOHEADER structure. Describes basic information about the bitmap
760 // being read.
762 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
763 struct BitmapInfoHeader {
764 ulittle32_t Size;
765 ulittle32_t Width;
766 ulittle32_t Height;
767 ulittle16_t Planes;
768 ulittle16_t BitCount;
769 ulittle32_t Compression;
770 ulittle32_t SizeImage;
771 ulittle32_t XPelsPerMeter;
772 ulittle32_t YPelsPerMeter;
773 ulittle32_t ClrUsed;
774 ulittle32_t ClrImportant;
777 // Group icon directory header. Called ICONDIR in .ico/.cur files and
778 // NEWHEADER in .res files.
780 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648023(v=vs.85).aspx
781 struct GroupIconDir {
782 ulittle16_t Reserved; // Always 0.
783 ulittle16_t ResType; // 1 for icons, 2 for cursors.
784 ulittle16_t ResCount; // Number of items.
787 enum class IconCursorGroupType { Icon, Cursor };
789 class SingleIconCursorResource : public RCResource {
790 public:
791 IconCursorGroupType Type;
792 const ResourceDirEntryStart &Header;
793 ArrayRef<uint8_t> Image;
795 SingleIconCursorResource(IconCursorGroupType ResourceType,
796 const ResourceDirEntryStart &HeaderEntry,
797 ArrayRef<uint8_t> ImageData, uint16_t Flags)
798 : RCResource(Flags), Type(ResourceType), Header(HeaderEntry),
799 Image(ImageData) {}
801 Twine getResourceTypeName() const override { return "Icon/cursor image"; }
802 IntOrString getResourceType() const override {
803 return Type == IconCursorGroupType::Icon ? RkSingleIcon : RkSingleCursor;
805 ResourceKind getKind() const override { return RkSingleCursorOrIconRes; }
806 static bool classof(const RCResource *Res) {
807 return Res->getKind() == RkSingleCursorOrIconRes;
811 class IconCursorGroupResource : public RCResource {
812 public:
813 IconCursorGroupType Type;
814 GroupIconDir Header;
815 std::vector<ResourceDirEntryStart> ItemEntries;
817 IconCursorGroupResource(IconCursorGroupType ResourceType,
818 const GroupIconDir &HeaderData,
819 std::vector<ResourceDirEntryStart> &&Entries)
820 : Type(ResourceType), Header(HeaderData),
821 ItemEntries(std::move(Entries)) {}
823 Twine getResourceTypeName() const override { return "Icon/cursor group"; }
824 IntOrString getResourceType() const override {
825 return Type == IconCursorGroupType::Icon ? RkIconGroup : RkCursorGroup;
827 ResourceKind getKind() const override { return RkCursorOrIconGroupRes; }
828 static bool classof(const RCResource *Res) {
829 return Res->getKind() == RkCursorOrIconGroupRes;
833 Error ResourceFileWriter::writeSingleIconOrCursorBody(const RCResource *Base) {
834 auto *Res = cast<SingleIconCursorResource>(Base);
835 if (Res->Type == IconCursorGroupType::Cursor) {
836 // In case of cursors, two WORDS are appended to the beginning
837 // of the resource: HotspotX (Planes in RESDIRENTRY),
838 // and HotspotY (BitCount).
840 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026.aspx
841 // (Remarks section).
842 writeObject(Res->Header.Planes);
843 writeObject(Res->Header.BitCount);
846 writeObject(Res->Image);
847 return Error::success();
850 Error ResourceFileWriter::writeIconOrCursorGroupBody(const RCResource *Base) {
851 auto *Res = cast<IconCursorGroupResource>(Base);
852 writeObject(Res->Header);
853 for (auto Item : Res->ItemEntries) {
854 writeObject(Item);
855 writeInt(IconCursorID++);
857 return Error::success();
860 Error ResourceFileWriter::visitSingleIconOrCursor(const RCResource *Res) {
861 return writeResource(Res, &ResourceFileWriter::writeSingleIconOrCursorBody);
864 Error ResourceFileWriter::visitIconOrCursorGroup(const RCResource *Res) {
865 return writeResource(Res, &ResourceFileWriter::writeIconOrCursorGroupBody);
868 Error ResourceFileWriter::visitIconOrCursorResource(const RCResource *Base) {
869 IconCursorGroupType Type;
870 StringRef FileStr;
871 IntOrString ResName = Base->ResName;
873 if (auto *IconRes = dyn_cast<IconResource>(Base)) {
874 FileStr = IconRes->IconLoc;
875 Type = IconCursorGroupType::Icon;
876 } else {
877 auto *CursorRes = dyn_cast<CursorResource>(Base);
878 FileStr = CursorRes->CursorLoc;
879 Type = IconCursorGroupType::Cursor;
882 bool IsLong;
883 stripQuotes(FileStr, IsLong);
884 auto File = loadFile(FileStr);
886 if (!File)
887 return File.takeError();
889 BinaryStreamReader Reader((*File)->getBuffer(), support::little);
891 // Read the file headers.
892 // - At the beginning, ICONDIR/NEWHEADER header.
893 // - Then, a number of RESDIR headers follow. These contain offsets
894 // to data.
895 const GroupIconDir *Header;
897 RETURN_IF_ERROR(Reader.readObject(Header));
898 if (Header->Reserved != 0)
899 return createError("Incorrect icon/cursor Reserved field; should be 0.");
900 uint16_t NeededType = Type == IconCursorGroupType::Icon ? 1 : 2;
901 if (Header->ResType != NeededType)
902 return createError("Incorrect icon/cursor ResType field; should be " +
903 Twine(NeededType) + ".");
905 uint16_t NumItems = Header->ResCount;
907 // Read single ico/cur headers.
908 std::vector<ResourceDirEntryStart> ItemEntries;
909 ItemEntries.reserve(NumItems);
910 std::vector<uint32_t> ItemOffsets(NumItems);
911 for (size_t ID = 0; ID < NumItems; ++ID) {
912 const ResourceDirEntryStart *Object;
913 RETURN_IF_ERROR(Reader.readObject(Object));
914 ItemEntries.push_back(*Object);
915 RETURN_IF_ERROR(Reader.readInteger(ItemOffsets[ID]));
918 // Now write each icon/cursors one by one. At first, all the contents
919 // without ICO/CUR header. This is described by SingleIconCursorResource.
920 for (size_t ID = 0; ID < NumItems; ++ID) {
921 // Load the fragment of file.
922 Reader.setOffset(ItemOffsets[ID]);
923 ArrayRef<uint8_t> Image;
924 RETURN_IF_ERROR(Reader.readArray(Image, ItemEntries[ID].Size));
925 SingleIconCursorResource SingleRes(Type, ItemEntries[ID], Image,
926 Base->MemoryFlags);
927 SingleRes.setName(IconCursorID + ID);
928 RETURN_IF_ERROR(visitSingleIconOrCursor(&SingleRes));
931 // Now, write all the headers concatenated into a separate resource.
932 for (size_t ID = 0; ID < NumItems; ++ID) {
933 // We need to rewrite the cursor headers, and fetch actual values
934 // for Planes/BitCount.
935 const auto &OldHeader = ItemEntries[ID];
936 ResourceDirEntryStart NewHeader = OldHeader;
938 if (Type == IconCursorGroupType::Cursor) {
939 NewHeader.Cursor.Width = OldHeader.Icon.Width;
940 // Each cursor in fact stores two bitmaps, one under another.
941 // Height provided in cursor definition describes the height of the
942 // cursor, whereas the value existing in resource definition describes
943 // the height of the bitmap. Therefore, we need to double this height.
944 NewHeader.Cursor.Height = OldHeader.Icon.Height * 2;
946 // Two WORDs were written at the beginning of the resource (hotspot
947 // location). This is reflected in Size field.
948 NewHeader.Size += 2 * sizeof(uint16_t);
951 // Now, we actually need to read the bitmap header to find
952 // the number of planes and the number of bits per pixel.
953 Reader.setOffset(ItemOffsets[ID]);
954 const BitmapInfoHeader *BMPHeader;
955 RETURN_IF_ERROR(Reader.readObject(BMPHeader));
956 if (BMPHeader->Size == sizeof(BitmapInfoHeader)) {
957 NewHeader.Planes = BMPHeader->Planes;
958 NewHeader.BitCount = BMPHeader->BitCount;
959 } else {
960 // A PNG .ico file.
961 // https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473
962 // "The image must be in 32bpp"
963 NewHeader.Planes = 1;
964 NewHeader.BitCount = 32;
967 ItemEntries[ID] = NewHeader;
970 IconCursorGroupResource HeaderRes(Type, *Header, std::move(ItemEntries));
971 HeaderRes.setName(ResName);
972 if (Base->MemoryFlags & MfPreload) {
973 HeaderRes.MemoryFlags |= MfPreload;
974 HeaderRes.MemoryFlags &= ~MfPure;
976 RETURN_IF_ERROR(visitIconOrCursorGroup(&HeaderRes));
978 return Error::success();
981 // --- DialogResource helpers. --- //
983 Error ResourceFileWriter::writeSingleDialogControl(const Control &Ctl,
984 bool IsExtended) {
985 // Each control should be aligned to DWORD.
986 padStream(sizeof(uint32_t));
988 auto TypeInfo = Control::SupportedCtls.lookup(Ctl.Type);
989 IntWithNotMask CtlStyle(TypeInfo.Style);
990 CtlStyle |= Ctl.Style.getValueOr(RCInt(0));
991 uint32_t CtlExtStyle = Ctl.ExtStyle.getValueOr(0);
993 // DIALOG(EX) item header prefix.
994 if (!IsExtended) {
995 struct {
996 ulittle32_t Style;
997 ulittle32_t ExtStyle;
998 } Prefix{ulittle32_t(CtlStyle.getValue()), ulittle32_t(CtlExtStyle)};
999 writeObject(Prefix);
1000 } else {
1001 struct {
1002 ulittle32_t HelpID;
1003 ulittle32_t ExtStyle;
1004 ulittle32_t Style;
1005 } Prefix{ulittle32_t(Ctl.HelpID.getValueOr(0)), ulittle32_t(CtlExtStyle),
1006 ulittle32_t(CtlStyle.getValue())};
1007 writeObject(Prefix);
1010 // Common fixed-length part.
1011 RETURN_IF_ERROR(checkSignedNumberFits<int16_t>(
1012 Ctl.X, "Dialog control x-coordinate", true));
1013 RETURN_IF_ERROR(checkSignedNumberFits<int16_t>(
1014 Ctl.Y, "Dialog control y-coordinate", true));
1015 RETURN_IF_ERROR(
1016 checkSignedNumberFits<int16_t>(Ctl.Width, "Dialog control width", false));
1017 RETURN_IF_ERROR(checkSignedNumberFits<int16_t>(
1018 Ctl.Height, "Dialog control height", false));
1019 struct {
1020 ulittle16_t X;
1021 ulittle16_t Y;
1022 ulittle16_t Width;
1023 ulittle16_t Height;
1024 } Middle{ulittle16_t(Ctl.X), ulittle16_t(Ctl.Y), ulittle16_t(Ctl.Width),
1025 ulittle16_t(Ctl.Height)};
1026 writeObject(Middle);
1028 // ID; it's 16-bit in DIALOG and 32-bit in DIALOGEX.
1029 if (!IsExtended) {
1030 // It's common to use -1, i.e. UINT32_MAX, for controls one doesn't
1031 // want to refer to later.
1032 if (Ctl.ID != static_cast<uint32_t>(-1))
1033 RETURN_IF_ERROR(checkNumberFits<uint16_t>(
1034 Ctl.ID, "Control ID in simple DIALOG resource"));
1035 writeInt<uint16_t>(Ctl.ID);
1036 } else {
1037 writeInt<uint32_t>(Ctl.ID);
1040 // Window class - either 0xFFFF + 16-bit integer or a string.
1041 RETURN_IF_ERROR(writeIntOrString(Ctl.Class));
1043 // Element caption/reference ID. ID is preceded by 0xFFFF.
1044 RETURN_IF_ERROR(checkIntOrString(Ctl.Title, "Control reference ID"));
1045 RETURN_IF_ERROR(writeIntOrString(Ctl.Title));
1047 // # bytes of extra creation data count. Don't pass any.
1048 writeInt<uint16_t>(0);
1050 return Error::success();
1053 Error ResourceFileWriter::writeDialogBody(const RCResource *Base) {
1054 auto *Res = cast<DialogResource>(Base);
1056 // Default style: WS_POPUP | WS_BORDER | WS_SYSMENU.
1057 const uint32_t DefaultStyle = 0x80880000;
1058 const uint32_t StyleFontFlag = 0x40;
1059 const uint32_t StyleCaptionFlag = 0x00C00000;
1061 uint32_t UsedStyle = ObjectData.Style.getValueOr(DefaultStyle);
1062 if (ObjectData.Font)
1063 UsedStyle |= StyleFontFlag;
1064 else
1065 UsedStyle &= ~StyleFontFlag;
1067 // Actually, in case of empty (but existent) caption, the examined field
1068 // is equal to "\"\"". That's why empty captions are still noticed.
1069 if (ObjectData.Caption != "")
1070 UsedStyle |= StyleCaptionFlag;
1072 const uint16_t DialogExMagic = 0xFFFF;
1073 uint32_t ExStyle = ObjectData.ExStyle.getValueOr(0);
1075 // Write DIALOG(EX) header prefix. These are pretty different.
1076 if (!Res->IsExtended) {
1077 // We cannot let the higher word of DefaultStyle be equal to 0xFFFF.
1078 // In such a case, whole object (in .res file) is equivalent to a
1079 // DIALOGEX. It might lead to access violation/segmentation fault in
1080 // resource readers. For example,
1081 // 1 DIALOG 0, 0, 0, 65432
1082 // STYLE 0xFFFF0001 {}
1083 // would be compiled to a DIALOGEX with 65432 controls.
1084 if ((UsedStyle >> 16) == DialogExMagic)
1085 return createError("16 higher bits of DIALOG resource style cannot be"
1086 " equal to 0xFFFF");
1088 struct {
1089 ulittle32_t Style;
1090 ulittle32_t ExtStyle;
1091 } Prefix{ulittle32_t(UsedStyle),
1092 ulittle32_t(ExStyle)};
1094 writeObject(Prefix);
1095 } else {
1096 struct {
1097 ulittle16_t Version;
1098 ulittle16_t Magic;
1099 ulittle32_t HelpID;
1100 ulittle32_t ExtStyle;
1101 ulittle32_t Style;
1102 } Prefix{ulittle16_t(1), ulittle16_t(DialogExMagic),
1103 ulittle32_t(Res->HelpID), ulittle32_t(ExStyle), ulittle32_t(UsedStyle)};
1105 writeObject(Prefix);
1108 // Now, a common part. First, fixed-length fields.
1109 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Res->Controls.size(),
1110 "Number of dialog controls"));
1111 RETURN_IF_ERROR(
1112 checkSignedNumberFits<int16_t>(Res->X, "Dialog x-coordinate", true));
1113 RETURN_IF_ERROR(
1114 checkSignedNumberFits<int16_t>(Res->Y, "Dialog y-coordinate", true));
1115 RETURN_IF_ERROR(
1116 checkSignedNumberFits<int16_t>(Res->Width, "Dialog width", false));
1117 RETURN_IF_ERROR(
1118 checkSignedNumberFits<int16_t>(Res->Height, "Dialog height", false));
1119 struct {
1120 ulittle16_t Count;
1121 ulittle16_t PosX;
1122 ulittle16_t PosY;
1123 ulittle16_t DialogWidth;
1124 ulittle16_t DialogHeight;
1125 } Middle{ulittle16_t(Res->Controls.size()), ulittle16_t(Res->X),
1126 ulittle16_t(Res->Y), ulittle16_t(Res->Width),
1127 ulittle16_t(Res->Height)};
1128 writeObject(Middle);
1130 // MENU field. As of now, we don't keep them in the state and can peacefully
1131 // think there is no menu attached to the dialog.
1132 writeInt<uint16_t>(0);
1134 // Window CLASS field.
1135 RETURN_IF_ERROR(writeIntOrString(ObjectData.Class));
1137 // Window title or a single word equal to 0.
1138 RETURN_IF_ERROR(writeCString(ObjectData.Caption));
1140 // If there *is* a window font declared, output its data.
1141 auto &Font = ObjectData.Font;
1142 if (Font) {
1143 writeInt<uint16_t>(Font->Size);
1144 // Additional description occurs only in DIALOGEX.
1145 if (Res->IsExtended) {
1146 writeInt<uint16_t>(Font->Weight);
1147 writeInt<uint8_t>(Font->IsItalic);
1148 writeInt<uint8_t>(Font->Charset);
1150 RETURN_IF_ERROR(writeCString(Font->Typeface));
1153 auto handleCtlError = [&](Error &&Err, const Control &Ctl) -> Error {
1154 if (!Err)
1155 return Error::success();
1156 return joinErrors(createError("Error in " + Twine(Ctl.Type) +
1157 " control (ID " + Twine(Ctl.ID) + "):"),
1158 std::move(Err));
1161 for (auto &Ctl : Res->Controls)
1162 RETURN_IF_ERROR(
1163 handleCtlError(writeSingleDialogControl(Ctl, Res->IsExtended), Ctl));
1165 return Error::success();
1168 // --- HTMLResource helpers. --- //
1170 Error ResourceFileWriter::writeHTMLBody(const RCResource *Base) {
1171 return appendFile(cast<HTMLResource>(Base)->HTMLLoc);
1174 // --- MenuResource helpers. --- //
1176 Error ResourceFileWriter::writeMenuDefinition(
1177 const std::unique_ptr<MenuDefinition> &Def, uint16_t Flags) {
1178 assert(Def);
1179 const MenuDefinition *DefPtr = Def.get();
1181 if (auto *MenuItemPtr = dyn_cast<MenuItem>(DefPtr)) {
1182 writeInt<uint16_t>(Flags);
1183 RETURN_IF_ERROR(
1184 checkNumberFits<uint16_t>(MenuItemPtr->Id, "MENUITEM action ID"));
1185 writeInt<uint16_t>(MenuItemPtr->Id);
1186 RETURN_IF_ERROR(writeCString(MenuItemPtr->Name));
1187 return Error::success();
1190 if (isa<MenuSeparator>(DefPtr)) {
1191 writeInt<uint16_t>(Flags);
1192 writeInt<uint32_t>(0);
1193 return Error::success();
1196 auto *PopupPtr = cast<PopupItem>(DefPtr);
1197 writeInt<uint16_t>(Flags);
1198 RETURN_IF_ERROR(writeCString(PopupPtr->Name));
1199 return writeMenuDefinitionList(PopupPtr->SubItems);
1202 Error ResourceFileWriter::writeMenuDefinitionList(
1203 const MenuDefinitionList &List) {
1204 for (auto &Def : List.Definitions) {
1205 uint16_t Flags = Def->getResFlags();
1206 // Last element receives an additional 0x80 flag.
1207 const uint16_t LastElementFlag = 0x0080;
1208 if (&Def == &List.Definitions.back())
1209 Flags |= LastElementFlag;
1211 RETURN_IF_ERROR(writeMenuDefinition(Def, Flags));
1213 return Error::success();
1216 Error ResourceFileWriter::writeMenuBody(const RCResource *Base) {
1217 // At first, MENUHEADER structure. In fact, these are two WORDs equal to 0.
1218 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648018.aspx
1219 writeInt<uint32_t>(0);
1221 return writeMenuDefinitionList(cast<MenuResource>(Base)->Elements);
1224 // --- StringTableResource helpers. --- //
1226 class BundleResource : public RCResource {
1227 public:
1228 using BundleType = ResourceFileWriter::StringTableInfo::Bundle;
1229 BundleType Bundle;
1231 BundleResource(const BundleType &StrBundle)
1232 : RCResource(StrBundle.MemoryFlags), Bundle(StrBundle) {}
1233 IntOrString getResourceType() const override { return 6; }
1235 ResourceKind getKind() const override { return RkStringTableBundle; }
1236 static bool classof(const RCResource *Res) {
1237 return Res->getKind() == RkStringTableBundle;
1239 Twine getResourceTypeName() const override { return "STRINGTABLE"; }
1242 Error ResourceFileWriter::visitStringTableBundle(const RCResource *Res) {
1243 return writeResource(Res, &ResourceFileWriter::writeStringTableBundleBody);
1246 Error ResourceFileWriter::insertStringIntoBundle(
1247 StringTableInfo::Bundle &Bundle, uint16_t StringID, StringRef String) {
1248 uint16_t StringLoc = StringID & 15;
1249 if (Bundle.Data[StringLoc])
1250 return createError("Multiple STRINGTABLE strings located under ID " +
1251 Twine(StringID));
1252 Bundle.Data[StringLoc] = String;
1253 return Error::success();
1256 Error ResourceFileWriter::writeStringTableBundleBody(const RCResource *Base) {
1257 auto *Res = cast<BundleResource>(Base);
1258 for (size_t ID = 0; ID < Res->Bundle.Data.size(); ++ID) {
1259 // The string format is a tiny bit different here. We
1260 // first output the size of the string, and then the string itself
1261 // (which is not null-terminated).
1262 bool IsLongString;
1263 SmallVector<UTF16, 128> Data;
1264 RETURN_IF_ERROR(processString(Res->Bundle.Data[ID].getValueOr(StringRef()),
1265 NullHandlingMethod::CutAtDoubleNull,
1266 IsLongString, Data, Params.CodePage));
1267 if (AppendNull && Res->Bundle.Data[ID])
1268 Data.push_back('\0');
1269 RETURN_IF_ERROR(
1270 checkNumberFits<uint16_t>(Data.size(), "STRINGTABLE string size"));
1271 writeInt<uint16_t>(Data.size());
1272 for (auto Char : Data)
1273 writeInt(Char);
1275 return Error::success();
1278 Error ResourceFileWriter::dumpAllStringTables() {
1279 for (auto Key : StringTableData.BundleList) {
1280 auto Iter = StringTableData.BundleData.find(Key);
1281 assert(Iter != StringTableData.BundleData.end());
1283 // For a moment, revert the context info to moment of bundle declaration.
1284 ContextKeeper RAII(this);
1285 ObjectData = Iter->second.DeclTimeInfo;
1287 BundleResource Res(Iter->second);
1288 // Bundle #(k+1) contains keys [16k, 16k + 15].
1289 Res.setName(Key.first + 1);
1290 RETURN_IF_ERROR(visitStringTableBundle(&Res));
1292 return Error::success();
1295 // --- UserDefinedResource helpers. --- //
1297 Error ResourceFileWriter::writeUserDefinedBody(const RCResource *Base) {
1298 auto *Res = cast<UserDefinedResource>(Base);
1300 if (Res->IsFileResource)
1301 return appendFile(Res->FileLoc);
1303 for (auto &Elem : Res->Contents) {
1304 if (Elem.isInt()) {
1305 RETURN_IF_ERROR(
1306 checkRCInt(Elem.getInt(), "Number in user-defined resource"));
1307 writeRCInt(Elem.getInt());
1308 continue;
1311 SmallVector<UTF16, 128> ProcessedString;
1312 bool IsLongString;
1313 RETURN_IF_ERROR(
1314 processString(Elem.getString(), NullHandlingMethod::UserResource,
1315 IsLongString, ProcessedString, Params.CodePage));
1317 for (auto Ch : ProcessedString) {
1318 if (IsLongString) {
1319 writeInt(Ch);
1320 continue;
1323 RETURN_IF_ERROR(checkNumberFits<uint8_t>(
1324 Ch, "Character in narrow string in user-defined resource"));
1325 writeInt<uint8_t>(Ch);
1329 return Error::success();
1332 // --- VersionInfoResourceResource helpers. --- //
1334 Error ResourceFileWriter::writeVersionInfoBlock(const VersionInfoBlock &Blk) {
1335 // Output the header if the block has name.
1336 bool OutputHeader = Blk.Name != "";
1337 uint64_t LengthLoc;
1339 padStream(sizeof(uint32_t));
1340 if (OutputHeader) {
1341 LengthLoc = writeInt<uint16_t>(0);
1342 writeInt<uint16_t>(0);
1343 writeInt<uint16_t>(1); // true
1344 RETURN_IF_ERROR(writeCString(Blk.Name));
1345 padStream(sizeof(uint32_t));
1348 for (const std::unique_ptr<VersionInfoStmt> &Item : Blk.Stmts) {
1349 VersionInfoStmt *ItemPtr = Item.get();
1351 if (auto *BlockPtr = dyn_cast<VersionInfoBlock>(ItemPtr)) {
1352 RETURN_IF_ERROR(writeVersionInfoBlock(*BlockPtr));
1353 continue;
1356 auto *ValuePtr = cast<VersionInfoValue>(ItemPtr);
1357 RETURN_IF_ERROR(writeVersionInfoValue(*ValuePtr));
1360 if (OutputHeader) {
1361 uint64_t CurLoc = tell();
1362 writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc);
1365 return Error::success();
1368 Error ResourceFileWriter::writeVersionInfoValue(const VersionInfoValue &Val) {
1369 // rc has a peculiar algorithm to output VERSIONINFO VALUEs. Each VALUE
1370 // is a mapping from the key (string) to the value (a sequence of ints or
1371 // a sequence of strings).
1373 // If integers are to be written: width of each integer written depends on
1374 // whether it's been declared 'long' (it's DWORD then) or not (it's WORD).
1375 // ValueLength defined in structure referenced below is then the total
1376 // number of bytes taken by these integers.
1378 // If strings are to be written: characters are always WORDs.
1379 // Moreover, '\0' character is written after the last string, and between
1380 // every two strings separated by comma (if strings are not comma-separated,
1381 // they're simply concatenated). ValueLength is equal to the number of WORDs
1382 // written (that is, half of the bytes written).
1384 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms646994.aspx
1385 bool HasStrings = false, HasInts = false;
1386 for (auto &Item : Val.Values)
1387 (Item.isInt() ? HasInts : HasStrings) = true;
1389 assert((HasStrings || HasInts) && "VALUE must have at least one argument");
1390 if (HasStrings && HasInts)
1391 return createError(Twine("VALUE ") + Val.Key +
1392 " cannot contain both strings and integers");
1394 padStream(sizeof(uint32_t));
1395 auto LengthLoc = writeInt<uint16_t>(0);
1396 auto ValLengthLoc = writeInt<uint16_t>(0);
1397 writeInt<uint16_t>(HasStrings);
1398 RETURN_IF_ERROR(writeCString(Val.Key));
1399 padStream(sizeof(uint32_t));
1401 auto DataLoc = tell();
1402 for (size_t Id = 0; Id < Val.Values.size(); ++Id) {
1403 auto &Item = Val.Values[Id];
1404 if (Item.isInt()) {
1405 auto Value = Item.getInt();
1406 RETURN_IF_ERROR(checkRCInt(Value, "VERSIONINFO integer value"));
1407 writeRCInt(Value);
1408 continue;
1411 bool WriteTerminator =
1412 Id == Val.Values.size() - 1 || Val.HasPrecedingComma[Id + 1];
1413 RETURN_IF_ERROR(writeCString(Item.getString(), WriteTerminator));
1416 auto CurLoc = tell();
1417 auto ValueLength = CurLoc - DataLoc;
1418 if (HasStrings) {
1419 assert(ValueLength % 2 == 0);
1420 ValueLength /= 2;
1422 writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc);
1423 writeObjectAt(ulittle16_t(ValueLength), ValLengthLoc);
1424 return Error::success();
1427 template <typename Ty>
1428 static Ty getWithDefault(const StringMap<Ty> &Map, StringRef Key,
1429 const Ty &Default) {
1430 auto Iter = Map.find(Key);
1431 if (Iter != Map.end())
1432 return Iter->getValue();
1433 return Default;
1436 Error ResourceFileWriter::writeVersionInfoBody(const RCResource *Base) {
1437 auto *Res = cast<VersionInfoResource>(Base);
1439 const auto &FixedData = Res->FixedData;
1441 struct /* VS_FIXEDFILEINFO */ {
1442 ulittle32_t Signature = ulittle32_t(0xFEEF04BD);
1443 ulittle32_t StructVersion = ulittle32_t(0x10000);
1444 // It's weird to have most-significant DWORD first on the little-endian
1445 // machines, but let it be this way.
1446 ulittle32_t FileVersionMS;
1447 ulittle32_t FileVersionLS;
1448 ulittle32_t ProductVersionMS;
1449 ulittle32_t ProductVersionLS;
1450 ulittle32_t FileFlagsMask;
1451 ulittle32_t FileFlags;
1452 ulittle32_t FileOS;
1453 ulittle32_t FileType;
1454 ulittle32_t FileSubtype;
1455 // MS implementation seems to always set these fields to 0.
1456 ulittle32_t FileDateMS = ulittle32_t(0);
1457 ulittle32_t FileDateLS = ulittle32_t(0);
1458 } FixedInfo;
1460 // First, VS_VERSIONINFO.
1461 auto LengthLoc = writeInt<uint16_t>(0);
1462 writeInt<uint16_t>(sizeof(FixedInfo));
1463 writeInt<uint16_t>(0);
1464 cantFail(writeCString("VS_VERSION_INFO"));
1465 padStream(sizeof(uint32_t));
1467 using VersionInfoFixed = VersionInfoResource::VersionInfoFixed;
1468 auto GetField = [&](VersionInfoFixed::VersionInfoFixedType Type) {
1469 static const SmallVector<uint32_t, 4> DefaultOut{0, 0, 0, 0};
1470 if (!FixedData.IsTypePresent[(int)Type])
1471 return DefaultOut;
1472 return FixedData.FixedInfo[(int)Type];
1475 auto FileVer = GetField(VersionInfoFixed::FtFileVersion);
1476 RETURN_IF_ERROR(checkNumberFits<uint16_t>(
1477 *std::max_element(FileVer.begin(), FileVer.end()), "FILEVERSION fields"));
1478 FixedInfo.FileVersionMS = (FileVer[0] << 16) | FileVer[1];
1479 FixedInfo.FileVersionLS = (FileVer[2] << 16) | FileVer[3];
1481 auto ProdVer = GetField(VersionInfoFixed::FtProductVersion);
1482 RETURN_IF_ERROR(checkNumberFits<uint16_t>(
1483 *std::max_element(ProdVer.begin(), ProdVer.end()),
1484 "PRODUCTVERSION fields"));
1485 FixedInfo.ProductVersionMS = (ProdVer[0] << 16) | ProdVer[1];
1486 FixedInfo.ProductVersionLS = (ProdVer[2] << 16) | ProdVer[3];
1488 FixedInfo.FileFlagsMask = GetField(VersionInfoFixed::FtFileFlagsMask)[0];
1489 FixedInfo.FileFlags = GetField(VersionInfoFixed::FtFileFlags)[0];
1490 FixedInfo.FileOS = GetField(VersionInfoFixed::FtFileOS)[0];
1491 FixedInfo.FileType = GetField(VersionInfoFixed::FtFileType)[0];
1492 FixedInfo.FileSubtype = GetField(VersionInfoFixed::FtFileSubtype)[0];
1494 writeObject(FixedInfo);
1495 padStream(sizeof(uint32_t));
1497 RETURN_IF_ERROR(writeVersionInfoBlock(Res->MainBlock));
1499 // FIXME: check overflow?
1500 writeObjectAt(ulittle16_t(tell() - LengthLoc), LengthLoc);
1502 return Error::success();
1505 Expected<std::unique_ptr<MemoryBuffer>>
1506 ResourceFileWriter::loadFile(StringRef File) const {
1507 SmallString<128> Path;
1508 SmallString<128> Cwd;
1509 std::unique_ptr<MemoryBuffer> Result;
1511 // 0. The file path is absolute and the file exists.
1512 if (sys::path::is_absolute(File))
1513 return errorOrToExpected(MemoryBuffer::getFile(File, -1, false));
1515 // 1. The current working directory.
1516 sys::fs::current_path(Cwd);
1517 Path.assign(Cwd.begin(), Cwd.end());
1518 sys::path::append(Path, File);
1519 if (sys::fs::exists(Path))
1520 return errorOrToExpected(MemoryBuffer::getFile(Path, -1, false));
1522 // 2. The directory of the input resource file, if it is different from the
1523 // current working directory.
1524 StringRef InputFileDir = sys::path::parent_path(Params.InputFilePath);
1525 Path.assign(InputFileDir.begin(), InputFileDir.end());
1526 sys::path::append(Path, File);
1527 if (sys::fs::exists(Path))
1528 return errorOrToExpected(MemoryBuffer::getFile(Path, -1, false));
1530 // 3. All of the include directories specified on the command line.
1531 for (StringRef ForceInclude : Params.Include) {
1532 Path.assign(ForceInclude.begin(), ForceInclude.end());
1533 sys::path::append(Path, File);
1534 if (sys::fs::exists(Path))
1535 return errorOrToExpected(MemoryBuffer::getFile(Path, -1, false));
1538 if (auto Result =
1539 llvm::sys::Process::FindInEnvPath("INCLUDE", File, Params.NoInclude))
1540 return errorOrToExpected(MemoryBuffer::getFile(*Result, -1, false));
1542 return make_error<StringError>("error : file not found : " + Twine(File),
1543 inconvertibleErrorCode());
1546 } // namespace rc
1547 } // namespace llvm