Run DCE after a LoopFlatten test to reduce spurious output [nfc]
[llvm-project.git] / llvm / tools / llvm-rc / ResourceFileWriter.cpp
blob9738fd49343a6f90f09ef7803fb019025c672773
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"
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)) \
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.starts_with_insensitive("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 // * 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,
148 int CodePage) {
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) {
156 for (char C : Str)
157 Chars.push_back(cp1252ToUnicode((unsigned char)C));
158 } else {
159 // For other, unknown codepages, only allow plain ASCII input.
160 for (char C : Str) {
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);
168 if (!IsString) {
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");
172 Ch = toupper(Ch);
174 Result.swap(Chars);
175 return Error::success();
177 Result.reserve(Chars.size());
178 size_t Pos = 0;
180 auto AddRes = [&Result, NullHandler, IsLongString](UTF16 Char) -> Error {
181 if (!IsLongString) {
182 if (NullHandler == NullHandlingMethod::UserResource) {
183 // Narrow strings in user-defined resources are *not* output in
184 // UTF-16 format.
185 if (Char > 0xFF)
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 {
195 if (!IsLongString) {
196 // Escaped chars in narrow strings have to be interpreted according to
197 // the chosen code page.
198 if (Char > 0xFF)
199 return createError("Non-8-bit escaped char (" + Twine(Char) +
200 ") can't occur in narrow string");
201 if (CodePage == CpUtf8) {
202 if (Char >= 0x80)
203 return createError("Unable to interpret single byte (" + Twine(Char) +
204 ") as UTF-8");
205 } else if (CodePage == CpWin1252) {
206 Char = cp1252ToUnicode(Char);
207 } else {
208 // Unknown/unsupported codepage, only allow ASCII input.
209 if (Char > 0x7F)
210 return createError("Non-ASCII 8-bit codepoint (" + Twine(Char) +
211 ") can't "
212 "occur in a non-Unicode string");
216 return AddRes(Char);
219 while (Pos < Chars.size()) {
220 UTF16 CurChar = Chars[Pos];
221 ++Pos;
223 // Strip double "".
224 if (CurChar == '"') {
225 if (Pos == Chars.size() || Chars[Pos] != '"')
226 return createError("Expected \"\"");
227 ++Pos;
228 RETURN_IF_ERROR(AddRes('"'));
229 continue;
232 if (CurChar == '\\') {
233 UTF16 TypeChar = Chars[Pos];
234 ++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.
239 UTF16 ReadInt = 0;
240 size_t RemainingChars = IsLongString ? 4 : 2;
241 // We don't want to read non-ASCII hex digits. std:: functions past
242 // 0xFF invoke UB.
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]))
255 break;
256 char Digit = tolower(Chars[Pos]);
257 ++Pos;
259 ReadInt <<= 4;
260 if (isdigit(Digit))
261 ReadInt |= Digit - '0';
262 else
263 ReadInt |= Digit - 'a' + 10;
265 --RemainingChars;
268 RETURN_IF_ERROR(AddEscapedChar(ReadInt));
269 continue;
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' &&
278 Chars[Pos] < '8') {
279 ReadInt <<= 3;
280 ReadInt |= Chars[Pos] - '0';
281 --RemainingChars;
282 ++Pos;
285 RETURN_IF_ERROR(AddEscapedChar(ReadInt));
287 continue;
290 switch (TypeChar) {
291 case 'A':
292 case 'a':
293 // Windows '\a' translates into '\b' (Backspace).
294 RETURN_IF_ERROR(AddRes('\b'));
295 break;
297 case 'n': // Somehow, RC doesn't recognize '\N' and '\R'.
298 RETURN_IF_ERROR(AddRes('\n'));
299 break;
301 case 'r':
302 RETURN_IF_ERROR(AddRes('\r'));
303 break;
305 case 'T':
306 case 't':
307 RETURN_IF_ERROR(AddRes('\t'));
308 break;
310 case '\\':
311 RETURN_IF_ERROR(AddRes('\\'));
312 break;
314 case '"':
315 // RC accepts \" only if another " comes afterwards; then, \"" means
316 // a single ".
317 if (Pos == Chars.size() || Chars[Pos] != '"')
318 return createError("Expected \\\"\"");
319 ++Pos;
320 RETURN_IF_ERROR(AddRes('"'));
321 break;
323 default:
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.
327 if (!IsLongString) {
328 RETURN_IF_ERROR(AddRes('\\'));
329 RETURN_IF_ERROR(AddRes(TypeChar));
331 break;
334 continue;
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')
345 Result.resize(Pos);
346 break;
348 case NullHandlingMethod::CutAtDoubleNull:
349 for (size_t Pos = 0; Pos + 1 < Result.size(); ++Pos)
350 if (Result[Pos] == '\0' && Result[Pos + 1] == '\0')
351 Result.resize(Pos);
352 if (Result.size() > 0 && Result.back() == '\0')
353 Result.pop_back();
354 break;
356 case NullHandlingMethod::UserResource:
357 break;
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());
366 return Result;
369 Error ResourceFileWriter::writeCString(StringRef Str, bool WriteTerminator) {
370 SmallVector<UTF16, 128> ProcessedString;
371 bool IsLongString;
372 RETURN_IF_ERROR(processString(Str, NullHandlingMethod::CutAtNull,
373 IsLongString, ProcessedString,
374 Params.CodePage));
375 for (auto Ch : ProcessedString)
376 writeInt<uint16_t>(Ch);
377 if (WriteTerminator)
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) {
387 if (!Value.isInt())
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) {
396 if (Value.isLong())
397 writeInt<uint32_t>(Value);
398 else
399 writeInt<uint16_t>(Value);
402 Error ResourceFileWriter::appendFile(StringRef Filename) {
403 bool IsLong;
404 stripQuotes(Filename, IsLong);
406 auto File = loadFile(Filename);
407 if (!File)
408 return File.takeError();
410 *FS << (*File)->getBuffer();
411 return Error::success();
414 void ResourceFileWriter::padStream(uint64_t Length) {
415 assert(Length > 0);
416 uint64_t Location = tell();
417 Location %= Length;
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) {
424 if (Err)
425 return joinErrors(createError("Error in " + Res->getResourceTypeName() +
426 " statement (ID " + Twine(Res->ResName) +
427 "): "),
428 std::move(Err));
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::visitMenuExResource(const RCResource *Res) {
475 return writeResource(Res, &ResourceFileWriter::writeMenuExBody);
478 Error ResourceFileWriter::visitStringTableResource(const RCResource *Base) {
479 const auto *Res = cast<StringTableResource>(Base);
481 ContextKeeper RAII(this);
482 RETURN_IF_ERROR(Res->applyStmts(this));
484 for (auto &String : Res->Table) {
485 RETURN_IF_ERROR(checkNumberFits<uint16_t>(String.first, "String ID"));
486 uint16_t BundleID = String.first >> 4;
487 StringTableInfo::BundleKey Key(BundleID, ObjectData.LanguageInfo);
488 auto &BundleData = StringTableData.BundleData;
489 auto Iter = BundleData.find(Key);
491 if (Iter == BundleData.end()) {
492 // Need to create a bundle.
493 StringTableData.BundleList.push_back(Key);
494 auto EmplaceResult = BundleData.emplace(
495 Key, StringTableInfo::Bundle(ObjectData, Res->MemoryFlags));
496 assert(EmplaceResult.second && "Could not create a bundle");
497 Iter = EmplaceResult.first;
500 RETURN_IF_ERROR(
501 insertStringIntoBundle(Iter->second, String.first, String.second));
504 return Error::success();
507 Error ResourceFileWriter::visitUserDefinedResource(const RCResource *Res) {
508 return writeResource(Res, &ResourceFileWriter::writeUserDefinedBody);
511 Error ResourceFileWriter::visitVersionInfoResource(const RCResource *Res) {
512 return writeResource(Res, &ResourceFileWriter::writeVersionInfoBody);
515 Error ResourceFileWriter::visitCharacteristicsStmt(
516 const CharacteristicsStmt *Stmt) {
517 ObjectData.Characteristics = Stmt->Value;
518 return Error::success();
521 Error ResourceFileWriter::visitExStyleStmt(const ExStyleStmt *Stmt) {
522 ObjectData.ExStyle = Stmt->Value;
523 return Error::success();
526 Error ResourceFileWriter::visitFontStmt(const FontStmt *Stmt) {
527 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Stmt->Size, "Font size"));
528 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Stmt->Weight, "Font weight"));
529 RETURN_IF_ERROR(checkNumberFits<uint8_t>(Stmt->Charset, "Font charset"));
530 ObjectInfo::FontInfo Font{Stmt->Size, Stmt->Name, Stmt->Weight, Stmt->Italic,
531 Stmt->Charset};
532 ObjectData.Font.emplace(Font);
533 return Error::success();
536 Error ResourceFileWriter::visitLanguageStmt(const LanguageResource *Stmt) {
537 RETURN_IF_ERROR(checkNumberFits(Stmt->Lang, 10, "Primary language ID"));
538 RETURN_IF_ERROR(checkNumberFits(Stmt->SubLang, 6, "Sublanguage ID"));
539 ObjectData.LanguageInfo = Stmt->Lang | (Stmt->SubLang << 10);
540 return Error::success();
543 Error ResourceFileWriter::visitStyleStmt(const StyleStmt *Stmt) {
544 ObjectData.Style = Stmt->Value;
545 return Error::success();
548 Error ResourceFileWriter::visitVersionStmt(const VersionStmt *Stmt) {
549 ObjectData.VersionInfo = Stmt->Value;
550 return Error::success();
553 Error ResourceFileWriter::writeResource(
554 const RCResource *Res,
555 Error (ResourceFileWriter::*BodyWriter)(const RCResource *)) {
556 // We don't know the sizes yet.
557 object::WinResHeaderPrefix HeaderPrefix{ulittle32_t(0U), ulittle32_t(0U)};
558 uint64_t HeaderLoc = writeObject(HeaderPrefix);
560 auto ResType = Res->getResourceType();
561 RETURN_IF_ERROR(checkIntOrString(ResType, "Resource type"));
562 RETURN_IF_ERROR(checkIntOrString(Res->ResName, "Resource ID"));
563 RETURN_IF_ERROR(handleError(writeIdentifier(ResType), Res));
564 RETURN_IF_ERROR(handleError(writeIdentifier(Res->ResName), Res));
566 // Apply the resource-local optional statements.
567 ContextKeeper RAII(this);
568 RETURN_IF_ERROR(handleError(Res->applyStmts(this), Res));
570 padStream(sizeof(uint32_t));
571 object::WinResHeaderSuffix HeaderSuffix{
572 ulittle32_t(0), // DataVersion; seems to always be 0
573 ulittle16_t(Res->MemoryFlags), ulittle16_t(ObjectData.LanguageInfo),
574 ulittle32_t(ObjectData.VersionInfo),
575 ulittle32_t(ObjectData.Characteristics)};
576 writeObject(HeaderSuffix);
578 uint64_t DataLoc = tell();
579 RETURN_IF_ERROR(handleError((this->*BodyWriter)(Res), Res));
580 // RETURN_IF_ERROR(handleError(dumpResource(Ctx)));
582 // Update the sizes.
583 HeaderPrefix.DataSize = tell() - DataLoc;
584 HeaderPrefix.HeaderSize = DataLoc - HeaderLoc;
585 writeObjectAt(HeaderPrefix, HeaderLoc);
586 padStream(sizeof(uint32_t));
588 return Error::success();
591 // --- NullResource helpers. --- //
593 Error ResourceFileWriter::writeNullBody(const RCResource *) {
594 return Error::success();
597 // --- AcceleratorsResource helpers. --- //
599 Error ResourceFileWriter::writeSingleAccelerator(
600 const AcceleratorsResource::Accelerator &Obj, bool IsLastItem) {
601 using Accelerator = AcceleratorsResource::Accelerator;
602 using Opt = Accelerator::Options;
604 struct AccelTableEntry {
605 ulittle16_t Flags;
606 ulittle16_t ANSICode;
607 ulittle16_t Id;
608 uint16_t Padding;
609 } Entry{ulittle16_t(0), ulittle16_t(0), ulittle16_t(0), 0};
611 bool IsASCII = Obj.Flags & Opt::ASCII, IsVirtKey = Obj.Flags & Opt::VIRTKEY;
613 // Remove ASCII flags (which doesn't occur in .res files).
614 Entry.Flags = Obj.Flags & ~Opt::ASCII;
616 if (IsLastItem)
617 Entry.Flags |= 0x80;
619 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Obj.Id, "ACCELERATORS entry ID"));
620 Entry.Id = ulittle16_t(Obj.Id);
622 auto createAccError = [&Obj](const char *Msg) {
623 return createError("Accelerator ID " + Twine(Obj.Id) + ": " + Msg);
626 if (IsASCII && IsVirtKey)
627 return createAccError("Accelerator can't be both ASCII and VIRTKEY");
629 if (!IsVirtKey && (Obj.Flags & (Opt::ALT | Opt::SHIFT | Opt::CONTROL)))
630 return createAccError("Can only apply ALT, SHIFT or CONTROL to VIRTKEY"
631 " accelerators");
633 if (Obj.Event.isInt()) {
634 if (!IsASCII && !IsVirtKey)
635 return createAccError(
636 "Accelerator with a numeric event must be either ASCII"
637 " or VIRTKEY");
639 uint32_t EventVal = Obj.Event.getInt();
640 RETURN_IF_ERROR(
641 checkNumberFits<uint16_t>(EventVal, "Numeric event key ID"));
642 Entry.ANSICode = ulittle16_t(EventVal);
643 writeObject(Entry);
644 return Error::success();
647 StringRef Str = Obj.Event.getString();
648 bool IsWide;
649 stripQuotes(Str, IsWide);
651 if (Str.size() == 0 || Str.size() > 2)
652 return createAccError(
653 "Accelerator string events should have length 1 or 2");
655 if (Str[0] == '^') {
656 if (Str.size() == 1)
657 return createAccError("No character following '^' in accelerator event");
658 if (IsVirtKey)
659 return createAccError(
660 "VIRTKEY accelerator events can't be preceded by '^'");
662 char Ch = Str[1];
663 if (Ch >= 'a' && Ch <= 'z')
664 Entry.ANSICode = ulittle16_t(Ch - 'a' + 1);
665 else if (Ch >= 'A' && Ch <= 'Z')
666 Entry.ANSICode = ulittle16_t(Ch - 'A' + 1);
667 else
668 return createAccError("Control character accelerator event should be"
669 " alphabetic");
671 writeObject(Entry);
672 return Error::success();
675 if (Str.size() == 2)
676 return createAccError("Event string should be one-character, possibly"
677 " preceded by '^'");
679 uint8_t EventCh = Str[0];
680 // The original tool just warns in this situation. We chose to fail.
681 if (IsVirtKey && !isalnum(EventCh))
682 return createAccError("Non-alphanumeric characters cannot describe virtual"
683 " keys");
684 if (EventCh > 0x7F)
685 return createAccError("Non-ASCII description of accelerator");
687 if (IsVirtKey)
688 EventCh = toupper(EventCh);
689 Entry.ANSICode = ulittle16_t(EventCh);
690 writeObject(Entry);
691 return Error::success();
694 Error ResourceFileWriter::writeAcceleratorsBody(const RCResource *Base) {
695 auto *Res = cast<AcceleratorsResource>(Base);
696 size_t AcceleratorId = 0;
697 for (auto &Acc : Res->Accelerators) {
698 ++AcceleratorId;
699 RETURN_IF_ERROR(
700 writeSingleAccelerator(Acc, AcceleratorId == Res->Accelerators.size()));
702 return Error::success();
705 // --- BitmapResource helpers. --- //
707 Error ResourceFileWriter::writeBitmapBody(const RCResource *Base) {
708 StringRef Filename = cast<BitmapResource>(Base)->BitmapLoc;
709 bool IsLong;
710 stripQuotes(Filename, IsLong);
712 auto File = loadFile(Filename);
713 if (!File)
714 return File.takeError();
716 StringRef Buffer = (*File)->getBuffer();
718 // Skip the 14 byte BITMAPFILEHEADER.
719 constexpr size_t BITMAPFILEHEADER_size = 14;
720 if (Buffer.size() < BITMAPFILEHEADER_size || Buffer[0] != 'B' ||
721 Buffer[1] != 'M')
722 return createError("Incorrect bitmap file.");
724 *FS << Buffer.substr(BITMAPFILEHEADER_size);
725 return Error::success();
728 // --- CursorResource and IconResource helpers. --- //
730 // ICONRESDIR structure. Describes a single icon in resource group.
732 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648016.aspx
733 struct IconResDir {
734 uint8_t Width;
735 uint8_t Height;
736 uint8_t ColorCount;
737 uint8_t Reserved;
740 // CURSORDIR structure. Describes a single cursor in resource group.
742 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648011(v=vs.85).aspx
743 struct CursorDir {
744 ulittle16_t Width;
745 ulittle16_t Height;
748 // RESDIRENTRY structure, stripped from the last item. Stripping made
749 // for compatibility with RESDIR.
751 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026(v=vs.85).aspx
752 struct ResourceDirEntryStart {
753 union {
754 CursorDir Cursor; // Used in CURSOR resources.
755 IconResDir Icon; // Used in .ico and .cur files, and ICON resources.
757 ulittle16_t Planes; // HotspotX (.cur files but not CURSOR resource).
758 ulittle16_t BitCount; // HotspotY (.cur files but not CURSOR resource).
759 ulittle32_t Size;
760 // ulittle32_t ImageOffset; // Offset to image data (ICONDIRENTRY only).
761 // ulittle16_t IconID; // Resource icon ID (RESDIR only).
764 // BITMAPINFOHEADER structure. Describes basic information about the bitmap
765 // being read.
767 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
768 struct BitmapInfoHeader {
769 ulittle32_t Size;
770 ulittle32_t Width;
771 ulittle32_t Height;
772 ulittle16_t Planes;
773 ulittle16_t BitCount;
774 ulittle32_t Compression;
775 ulittle32_t SizeImage;
776 ulittle32_t XPelsPerMeter;
777 ulittle32_t YPelsPerMeter;
778 ulittle32_t ClrUsed;
779 ulittle32_t ClrImportant;
782 // Group icon directory header. Called ICONDIR in .ico/.cur files and
783 // NEWHEADER in .res files.
785 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648023(v=vs.85).aspx
786 struct GroupIconDir {
787 ulittle16_t Reserved; // Always 0.
788 ulittle16_t ResType; // 1 for icons, 2 for cursors.
789 ulittle16_t ResCount; // Number of items.
792 enum class IconCursorGroupType { Icon, Cursor };
794 class SingleIconCursorResource : public RCResource {
795 public:
796 IconCursorGroupType Type;
797 const ResourceDirEntryStart &Header;
798 ArrayRef<uint8_t> Image;
800 SingleIconCursorResource(IconCursorGroupType ResourceType,
801 const ResourceDirEntryStart &HeaderEntry,
802 ArrayRef<uint8_t> ImageData, uint16_t Flags)
803 : RCResource(Flags), Type(ResourceType), Header(HeaderEntry),
804 Image(ImageData) {}
806 Twine getResourceTypeName() const override { return "Icon/cursor image"; }
807 IntOrString getResourceType() const override {
808 return Type == IconCursorGroupType::Icon ? RkSingleIcon : RkSingleCursor;
810 ResourceKind getKind() const override { return RkSingleCursorOrIconRes; }
811 static bool classof(const RCResource *Res) {
812 return Res->getKind() == RkSingleCursorOrIconRes;
816 class IconCursorGroupResource : public RCResource {
817 public:
818 IconCursorGroupType Type;
819 GroupIconDir Header;
820 std::vector<ResourceDirEntryStart> ItemEntries;
822 IconCursorGroupResource(IconCursorGroupType ResourceType,
823 const GroupIconDir &HeaderData,
824 std::vector<ResourceDirEntryStart> &&Entries)
825 : Type(ResourceType), Header(HeaderData),
826 ItemEntries(std::move(Entries)) {}
828 Twine getResourceTypeName() const override { return "Icon/cursor group"; }
829 IntOrString getResourceType() const override {
830 return Type == IconCursorGroupType::Icon ? RkIconGroup : RkCursorGroup;
832 ResourceKind getKind() const override { return RkCursorOrIconGroupRes; }
833 static bool classof(const RCResource *Res) {
834 return Res->getKind() == RkCursorOrIconGroupRes;
838 Error ResourceFileWriter::writeSingleIconOrCursorBody(const RCResource *Base) {
839 auto *Res = cast<SingleIconCursorResource>(Base);
840 if (Res->Type == IconCursorGroupType::Cursor) {
841 // In case of cursors, two WORDS are appended to the beginning
842 // of the resource: HotspotX (Planes in RESDIRENTRY),
843 // and HotspotY (BitCount).
845 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026.aspx
846 // (Remarks section).
847 writeObject(Res->Header.Planes);
848 writeObject(Res->Header.BitCount);
851 writeObject(Res->Image);
852 return Error::success();
855 Error ResourceFileWriter::writeIconOrCursorGroupBody(const RCResource *Base) {
856 auto *Res = cast<IconCursorGroupResource>(Base);
857 writeObject(Res->Header);
858 for (auto Item : Res->ItemEntries) {
859 writeObject(Item);
860 writeInt(IconCursorID++);
862 return Error::success();
865 Error ResourceFileWriter::visitSingleIconOrCursor(const RCResource *Res) {
866 return writeResource(Res, &ResourceFileWriter::writeSingleIconOrCursorBody);
869 Error ResourceFileWriter::visitIconOrCursorGroup(const RCResource *Res) {
870 return writeResource(Res, &ResourceFileWriter::writeIconOrCursorGroupBody);
873 Error ResourceFileWriter::visitIconOrCursorResource(const RCResource *Base) {
874 IconCursorGroupType Type;
875 StringRef FileStr;
876 IntOrString ResName = Base->ResName;
878 if (auto *IconRes = dyn_cast<IconResource>(Base)) {
879 FileStr = IconRes->IconLoc;
880 Type = IconCursorGroupType::Icon;
881 } else {
882 auto *CursorRes = cast<CursorResource>(Base);
883 FileStr = CursorRes->CursorLoc;
884 Type = IconCursorGroupType::Cursor;
887 bool IsLong;
888 stripQuotes(FileStr, IsLong);
889 auto File = loadFile(FileStr);
891 if (!File)
892 return File.takeError();
894 BinaryStreamReader Reader((*File)->getBuffer(), llvm::endianness::little);
896 // Read the file headers.
897 // - At the beginning, ICONDIR/NEWHEADER header.
898 // - Then, a number of RESDIR headers follow. These contain offsets
899 // to data.
900 const GroupIconDir *Header;
902 RETURN_IF_ERROR(Reader.readObject(Header));
903 if (Header->Reserved != 0)
904 return createError("Incorrect icon/cursor Reserved field; should be 0.");
905 uint16_t NeededType = Type == IconCursorGroupType::Icon ? 1 : 2;
906 if (Header->ResType != NeededType)
907 return createError("Incorrect icon/cursor ResType field; should be " +
908 Twine(NeededType) + ".");
910 uint16_t NumItems = Header->ResCount;
912 // Read single ico/cur headers.
913 std::vector<ResourceDirEntryStart> ItemEntries;
914 ItemEntries.reserve(NumItems);
915 std::vector<uint32_t> ItemOffsets(NumItems);
916 for (size_t ID = 0; ID < NumItems; ++ID) {
917 const ResourceDirEntryStart *Object;
918 RETURN_IF_ERROR(Reader.readObject(Object));
919 ItemEntries.push_back(*Object);
920 RETURN_IF_ERROR(Reader.readInteger(ItemOffsets[ID]));
923 // Now write each icon/cursors one by one. At first, all the contents
924 // without ICO/CUR header. This is described by SingleIconCursorResource.
925 for (size_t ID = 0; ID < NumItems; ++ID) {
926 // Load the fragment of file.
927 Reader.setOffset(ItemOffsets[ID]);
928 ArrayRef<uint8_t> Image;
929 RETURN_IF_ERROR(Reader.readArray(Image, ItemEntries[ID].Size));
930 SingleIconCursorResource SingleRes(Type, ItemEntries[ID], Image,
931 Base->MemoryFlags);
932 SingleRes.setName(IconCursorID + ID);
933 RETURN_IF_ERROR(visitSingleIconOrCursor(&SingleRes));
936 // Now, write all the headers concatenated into a separate resource.
937 for (size_t ID = 0; ID < NumItems; ++ID) {
938 // We need to rewrite the cursor headers, and fetch actual values
939 // for Planes/BitCount.
940 const auto &OldHeader = ItemEntries[ID];
941 ResourceDirEntryStart NewHeader = OldHeader;
943 if (Type == IconCursorGroupType::Cursor) {
944 NewHeader.Cursor.Width = OldHeader.Icon.Width;
945 // Each cursor in fact stores two bitmaps, one under another.
946 // Height provided in cursor definition describes the height of the
947 // cursor, whereas the value existing in resource definition describes
948 // the height of the bitmap. Therefore, we need to double this height.
949 NewHeader.Cursor.Height = OldHeader.Icon.Height * 2;
951 // Two WORDs were written at the beginning of the resource (hotspot
952 // location). This is reflected in Size field.
953 NewHeader.Size += 2 * sizeof(uint16_t);
956 // Now, we actually need to read the bitmap header to find
957 // the number of planes and the number of bits per pixel.
958 Reader.setOffset(ItemOffsets[ID]);
959 const BitmapInfoHeader *BMPHeader;
960 RETURN_IF_ERROR(Reader.readObject(BMPHeader));
961 if (BMPHeader->Size == sizeof(BitmapInfoHeader)) {
962 NewHeader.Planes = BMPHeader->Planes;
963 NewHeader.BitCount = BMPHeader->BitCount;
964 } else {
965 // A PNG .ico file.
966 // https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473
967 // "The image must be in 32bpp"
968 NewHeader.Planes = 1;
969 NewHeader.BitCount = 32;
972 ItemEntries[ID] = NewHeader;
975 IconCursorGroupResource HeaderRes(Type, *Header, std::move(ItemEntries));
976 HeaderRes.setName(ResName);
977 if (Base->MemoryFlags & MfPreload) {
978 HeaderRes.MemoryFlags |= MfPreload;
979 HeaderRes.MemoryFlags &= ~MfPure;
981 RETURN_IF_ERROR(visitIconOrCursorGroup(&HeaderRes));
983 return Error::success();
986 // --- DialogResource helpers. --- //
988 Error ResourceFileWriter::writeSingleDialogControl(const Control &Ctl,
989 bool IsExtended) {
990 // Each control should be aligned to DWORD.
991 padStream(sizeof(uint32_t));
993 auto TypeInfo = Control::SupportedCtls.lookup(Ctl.Type);
994 IntWithNotMask CtlStyle(TypeInfo.Style);
995 CtlStyle |= Ctl.Style.value_or(RCInt(0));
996 uint32_t CtlExtStyle = Ctl.ExtStyle.value_or(0);
998 // DIALOG(EX) item header prefix.
999 if (!IsExtended) {
1000 struct {
1001 ulittle32_t Style;
1002 ulittle32_t ExtStyle;
1003 } Prefix{ulittle32_t(CtlStyle.getValue()), ulittle32_t(CtlExtStyle)};
1004 writeObject(Prefix);
1005 } else {
1006 struct {
1007 ulittle32_t HelpID;
1008 ulittle32_t ExtStyle;
1009 ulittle32_t Style;
1010 } Prefix{ulittle32_t(Ctl.HelpID.value_or(0)), ulittle32_t(CtlExtStyle),
1011 ulittle32_t(CtlStyle.getValue())};
1012 writeObject(Prefix);
1015 // Common fixed-length part.
1016 RETURN_IF_ERROR(checkSignedNumberFits<int16_t>(
1017 Ctl.X, "Dialog control x-coordinate", true));
1018 RETURN_IF_ERROR(checkSignedNumberFits<int16_t>(
1019 Ctl.Y, "Dialog control y-coordinate", true));
1020 RETURN_IF_ERROR(
1021 checkSignedNumberFits<int16_t>(Ctl.Width, "Dialog control width", false));
1022 RETURN_IF_ERROR(checkSignedNumberFits<int16_t>(
1023 Ctl.Height, "Dialog control height", false));
1024 struct {
1025 ulittle16_t X;
1026 ulittle16_t Y;
1027 ulittle16_t Width;
1028 ulittle16_t Height;
1029 } Middle{ulittle16_t(Ctl.X), ulittle16_t(Ctl.Y), ulittle16_t(Ctl.Width),
1030 ulittle16_t(Ctl.Height)};
1031 writeObject(Middle);
1033 // ID; it's 16-bit in DIALOG and 32-bit in DIALOGEX.
1034 if (!IsExtended) {
1035 // It's common to use -1, i.e. UINT32_MAX, for controls one doesn't
1036 // want to refer to later.
1037 if (Ctl.ID != static_cast<uint32_t>(-1))
1038 RETURN_IF_ERROR(checkNumberFits<uint16_t>(
1039 Ctl.ID, "Control ID in simple DIALOG resource"));
1040 writeInt<uint16_t>(Ctl.ID);
1041 } else {
1042 writeInt<uint32_t>(Ctl.ID);
1045 // Window class - either 0xFFFF + 16-bit integer or a string.
1046 RETURN_IF_ERROR(writeIntOrString(Ctl.Class));
1048 // Element caption/reference ID. ID is preceded by 0xFFFF.
1049 RETURN_IF_ERROR(checkIntOrString(Ctl.Title, "Control reference ID"));
1050 RETURN_IF_ERROR(writeIntOrString(Ctl.Title));
1052 // # bytes of extra creation data count. Don't pass any.
1053 writeInt<uint16_t>(0);
1055 return Error::success();
1058 Error ResourceFileWriter::writeDialogBody(const RCResource *Base) {
1059 auto *Res = cast<DialogResource>(Base);
1061 // Default style: WS_POPUP | WS_BORDER | WS_SYSMENU.
1062 const uint32_t DefaultStyle = 0x80880000;
1063 const uint32_t StyleFontFlag = 0x40;
1064 const uint32_t StyleCaptionFlag = 0x00C00000;
1066 uint32_t UsedStyle = ObjectData.Style.value_or(DefaultStyle);
1067 if (ObjectData.Font)
1068 UsedStyle |= StyleFontFlag;
1069 else
1070 UsedStyle &= ~StyleFontFlag;
1072 // Actually, in case of empty (but existent) caption, the examined field
1073 // is equal to "\"\"". That's why empty captions are still noticed.
1074 if (ObjectData.Caption != "")
1075 UsedStyle |= StyleCaptionFlag;
1077 const uint16_t DialogExMagic = 0xFFFF;
1078 uint32_t ExStyle = ObjectData.ExStyle.value_or(0);
1080 // Write DIALOG(EX) header prefix. These are pretty different.
1081 if (!Res->IsExtended) {
1082 // We cannot let the higher word of DefaultStyle be equal to 0xFFFF.
1083 // In such a case, whole object (in .res file) is equivalent to a
1084 // DIALOGEX. It might lead to access violation/segmentation fault in
1085 // resource readers. For example,
1086 // 1 DIALOG 0, 0, 0, 65432
1087 // STYLE 0xFFFF0001 {}
1088 // would be compiled to a DIALOGEX with 65432 controls.
1089 if ((UsedStyle >> 16) == DialogExMagic)
1090 return createError("16 higher bits of DIALOG resource style cannot be"
1091 " equal to 0xFFFF");
1093 struct {
1094 ulittle32_t Style;
1095 ulittle32_t ExtStyle;
1096 } Prefix{ulittle32_t(UsedStyle),
1097 ulittle32_t(ExStyle)};
1099 writeObject(Prefix);
1100 } else {
1101 struct {
1102 ulittle16_t Version;
1103 ulittle16_t Magic;
1104 ulittle32_t HelpID;
1105 ulittle32_t ExtStyle;
1106 ulittle32_t Style;
1107 } Prefix{ulittle16_t(1), ulittle16_t(DialogExMagic),
1108 ulittle32_t(Res->HelpID), ulittle32_t(ExStyle), ulittle32_t(UsedStyle)};
1110 writeObject(Prefix);
1113 // Now, a common part. First, fixed-length fields.
1114 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Res->Controls.size(),
1115 "Number of dialog controls"));
1116 RETURN_IF_ERROR(
1117 checkSignedNumberFits<int16_t>(Res->X, "Dialog x-coordinate", true));
1118 RETURN_IF_ERROR(
1119 checkSignedNumberFits<int16_t>(Res->Y, "Dialog y-coordinate", true));
1120 RETURN_IF_ERROR(
1121 checkSignedNumberFits<int16_t>(Res->Width, "Dialog width", false));
1122 RETURN_IF_ERROR(
1123 checkSignedNumberFits<int16_t>(Res->Height, "Dialog height", false));
1124 struct {
1125 ulittle16_t Count;
1126 ulittle16_t PosX;
1127 ulittle16_t PosY;
1128 ulittle16_t DialogWidth;
1129 ulittle16_t DialogHeight;
1130 } Middle{ulittle16_t(Res->Controls.size()), ulittle16_t(Res->X),
1131 ulittle16_t(Res->Y), ulittle16_t(Res->Width),
1132 ulittle16_t(Res->Height)};
1133 writeObject(Middle);
1135 // MENU field. As of now, we don't keep them in the state and can peacefully
1136 // think there is no menu attached to the dialog.
1137 writeInt<uint16_t>(0);
1139 // Window CLASS field.
1140 RETURN_IF_ERROR(writeIntOrString(ObjectData.Class));
1142 // Window title or a single word equal to 0.
1143 RETURN_IF_ERROR(writeCString(ObjectData.Caption));
1145 // If there *is* a window font declared, output its data.
1146 auto &Font = ObjectData.Font;
1147 if (Font) {
1148 writeInt<uint16_t>(Font->Size);
1149 // Additional description occurs only in DIALOGEX.
1150 if (Res->IsExtended) {
1151 writeInt<uint16_t>(Font->Weight);
1152 writeInt<uint8_t>(Font->IsItalic);
1153 writeInt<uint8_t>(Font->Charset);
1155 RETURN_IF_ERROR(writeCString(Font->Typeface));
1158 auto handleCtlError = [&](Error &&Err, const Control &Ctl) -> Error {
1159 if (!Err)
1160 return Error::success();
1161 return joinErrors(createError("Error in " + Twine(Ctl.Type) +
1162 " control (ID " + Twine(Ctl.ID) + "):"),
1163 std::move(Err));
1166 for (auto &Ctl : Res->Controls)
1167 RETURN_IF_ERROR(
1168 handleCtlError(writeSingleDialogControl(Ctl, Res->IsExtended), Ctl));
1170 return Error::success();
1173 // --- HTMLResource helpers. --- //
1175 Error ResourceFileWriter::writeHTMLBody(const RCResource *Base) {
1176 return appendFile(cast<HTMLResource>(Base)->HTMLLoc);
1179 // --- MenuResource helpers. --- //
1181 Error ResourceFileWriter::writeMenuDefinition(
1182 const std::unique_ptr<MenuDefinition> &Def, uint16_t Flags) {
1183 // https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-menuitemtemplate
1184 assert(Def);
1185 const MenuDefinition *DefPtr = Def.get();
1187 if (auto *MenuItemPtr = dyn_cast<MenuItem>(DefPtr)) {
1188 writeInt<uint16_t>(Flags);
1189 // Some resource files use -1, i.e. UINT32_MAX, for empty menu items.
1190 if (MenuItemPtr->Id != static_cast<uint32_t>(-1))
1191 RETURN_IF_ERROR(
1192 checkNumberFits<uint16_t>(MenuItemPtr->Id, "MENUITEM action ID"));
1193 writeInt<uint16_t>(MenuItemPtr->Id);
1194 RETURN_IF_ERROR(writeCString(MenuItemPtr->Name));
1195 return Error::success();
1198 if (isa<MenuSeparator>(DefPtr)) {
1199 writeInt<uint16_t>(Flags);
1200 writeInt<uint32_t>(0);
1201 return Error::success();
1204 auto *PopupPtr = cast<PopupItem>(DefPtr);
1205 writeInt<uint16_t>(Flags);
1206 RETURN_IF_ERROR(writeCString(PopupPtr->Name));
1207 return writeMenuDefinitionList(PopupPtr->SubItems);
1210 Error ResourceFileWriter::writeMenuExDefinition(
1211 const std::unique_ptr<MenuDefinition> &Def, uint16_t Flags) {
1212 // https://learn.microsoft.com/en-us/windows/win32/menurc/menuex-template-item
1213 assert(Def);
1214 const MenuDefinition *DefPtr = Def.get();
1216 padStream(sizeof(uint32_t));
1217 if (auto *MenuItemPtr = dyn_cast<MenuExItem>(DefPtr)) {
1218 writeInt<uint32_t>(MenuItemPtr->Type);
1219 writeInt<uint32_t>(MenuItemPtr->State);
1220 writeInt<uint32_t>(MenuItemPtr->Id);
1221 writeInt<uint16_t>(Flags);
1222 padStream(sizeof(uint16_t));
1223 RETURN_IF_ERROR(writeCString(MenuItemPtr->Name));
1224 return Error::success();
1227 auto *PopupPtr = cast<PopupExItem>(DefPtr);
1228 writeInt<uint32_t>(PopupPtr->Type);
1229 writeInt<uint32_t>(PopupPtr->State);
1230 writeInt<uint32_t>(PopupPtr->Id);
1231 writeInt<uint16_t>(Flags);
1232 padStream(sizeof(uint16_t));
1233 RETURN_IF_ERROR(writeCString(PopupPtr->Name));
1234 writeInt<uint32_t>(PopupPtr->HelpId);
1235 return writeMenuExDefinitionList(PopupPtr->SubItems);
1238 Error ResourceFileWriter::writeMenuDefinitionList(
1239 const MenuDefinitionList &List) {
1240 for (auto &Def : List.Definitions) {
1241 uint16_t Flags = Def->getResFlags();
1242 // Last element receives an additional 0x80 flag.
1243 const uint16_t LastElementFlag = 0x0080;
1244 if (&Def == &List.Definitions.back())
1245 Flags |= LastElementFlag;
1247 RETURN_IF_ERROR(writeMenuDefinition(Def, Flags));
1249 return Error::success();
1252 Error ResourceFileWriter::writeMenuExDefinitionList(
1253 const MenuDefinitionList &List) {
1254 for (auto &Def : List.Definitions) {
1255 uint16_t Flags = Def->getResFlags();
1256 // Last element receives an additional 0x80 flag.
1257 const uint16_t LastElementFlag = 0x0080;
1258 if (&Def == &List.Definitions.back())
1259 Flags |= LastElementFlag;
1261 RETURN_IF_ERROR(writeMenuExDefinition(Def, Flags));
1263 return Error::success();
1266 Error ResourceFileWriter::writeMenuBody(const RCResource *Base) {
1267 // At first, MENUHEADER structure. In fact, these are two WORDs equal to 0.
1268 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648018.aspx
1269 writeInt<uint32_t>(0);
1271 return writeMenuDefinitionList(cast<MenuResource>(Base)->Elements);
1274 Error ResourceFileWriter::writeMenuExBody(const RCResource *Base) {
1275 // At first, MENUEX_TEMPLATE_HEADER structure.
1276 // Ref:
1277 // https://learn.microsoft.com/en-us/windows/win32/menurc/menuex-template-header
1278 writeInt<uint16_t>(1);
1279 writeInt<uint16_t>(4);
1280 writeInt<uint32_t>(0);
1282 return writeMenuExDefinitionList(cast<MenuExResource>(Base)->Elements);
1285 // --- StringTableResource helpers. --- //
1287 class BundleResource : public RCResource {
1288 public:
1289 using BundleType = ResourceFileWriter::StringTableInfo::Bundle;
1290 BundleType Bundle;
1292 BundleResource(const BundleType &StrBundle)
1293 : RCResource(StrBundle.MemoryFlags), Bundle(StrBundle) {}
1294 IntOrString getResourceType() const override { return 6; }
1296 ResourceKind getKind() const override { return RkStringTableBundle; }
1297 static bool classof(const RCResource *Res) {
1298 return Res->getKind() == RkStringTableBundle;
1300 Twine getResourceTypeName() const override { return "STRINGTABLE"; }
1303 Error ResourceFileWriter::visitStringTableBundle(const RCResource *Res) {
1304 return writeResource(Res, &ResourceFileWriter::writeStringTableBundleBody);
1307 Error ResourceFileWriter::insertStringIntoBundle(
1308 StringTableInfo::Bundle &Bundle, uint16_t StringID,
1309 const std::vector<StringRef> &String) {
1310 uint16_t StringLoc = StringID & 15;
1311 if (Bundle.Data[StringLoc])
1312 return createError("Multiple STRINGTABLE strings located under ID " +
1313 Twine(StringID));
1314 Bundle.Data[StringLoc] = String;
1315 return Error::success();
1318 Error ResourceFileWriter::writeStringTableBundleBody(const RCResource *Base) {
1319 auto *Res = cast<BundleResource>(Base);
1320 for (size_t ID = 0; ID < Res->Bundle.Data.size(); ++ID) {
1321 // The string format is a tiny bit different here. We
1322 // first output the size of the string, and then the string itself
1323 // (which is not null-terminated).
1324 SmallVector<UTF16, 128> Data;
1325 if (Res->Bundle.Data[ID]) {
1326 bool IsLongString;
1327 for (StringRef S : *Res->Bundle.Data[ID])
1328 RETURN_IF_ERROR(processString(S, NullHandlingMethod::CutAtDoubleNull,
1329 IsLongString, Data, Params.CodePage));
1330 if (AppendNull)
1331 Data.push_back('\0');
1333 RETURN_IF_ERROR(
1334 checkNumberFits<uint16_t>(Data.size(), "STRINGTABLE string size"));
1335 writeInt<uint16_t>(Data.size());
1336 for (auto Char : Data)
1337 writeInt(Char);
1339 return Error::success();
1342 Error ResourceFileWriter::dumpAllStringTables() {
1343 for (auto Key : StringTableData.BundleList) {
1344 auto Iter = StringTableData.BundleData.find(Key);
1345 assert(Iter != StringTableData.BundleData.end());
1347 // For a moment, revert the context info to moment of bundle declaration.
1348 ContextKeeper RAII(this);
1349 ObjectData = Iter->second.DeclTimeInfo;
1351 BundleResource Res(Iter->second);
1352 // Bundle #(k+1) contains keys [16k, 16k + 15].
1353 Res.setName(Key.first + 1);
1354 RETURN_IF_ERROR(visitStringTableBundle(&Res));
1356 return Error::success();
1359 // --- UserDefinedResource helpers. --- //
1361 Error ResourceFileWriter::writeUserDefinedBody(const RCResource *Base) {
1362 auto *Res = cast<UserDefinedResource>(Base);
1364 if (Res->IsFileResource)
1365 return appendFile(Res->FileLoc);
1367 for (auto &Elem : Res->Contents) {
1368 if (Elem.isInt()) {
1369 RETURN_IF_ERROR(
1370 checkRCInt(Elem.getInt(), "Number in user-defined resource"));
1371 writeRCInt(Elem.getInt());
1372 continue;
1375 SmallVector<UTF16, 128> ProcessedString;
1376 bool IsLongString;
1377 RETURN_IF_ERROR(
1378 processString(Elem.getString(), NullHandlingMethod::UserResource,
1379 IsLongString, ProcessedString, Params.CodePage));
1381 for (auto Ch : ProcessedString) {
1382 if (IsLongString) {
1383 writeInt(Ch);
1384 continue;
1387 RETURN_IF_ERROR(checkNumberFits<uint8_t>(
1388 Ch, "Character in narrow string in user-defined resource"));
1389 writeInt<uint8_t>(Ch);
1393 return Error::success();
1396 // --- VersionInfoResourceResource helpers. --- //
1398 Error ResourceFileWriter::writeVersionInfoBlock(const VersionInfoBlock &Blk) {
1399 // Output the header if the block has name.
1400 bool OutputHeader = Blk.Name != "";
1401 uint64_t LengthLoc;
1403 padStream(sizeof(uint32_t));
1404 if (OutputHeader) {
1405 LengthLoc = writeInt<uint16_t>(0);
1406 writeInt<uint16_t>(0);
1407 writeInt<uint16_t>(1); // true
1408 RETURN_IF_ERROR(writeCString(Blk.Name));
1409 padStream(sizeof(uint32_t));
1412 for (const std::unique_ptr<VersionInfoStmt> &Item : Blk.Stmts) {
1413 VersionInfoStmt *ItemPtr = Item.get();
1415 if (auto *BlockPtr = dyn_cast<VersionInfoBlock>(ItemPtr)) {
1416 RETURN_IF_ERROR(writeVersionInfoBlock(*BlockPtr));
1417 continue;
1420 auto *ValuePtr = cast<VersionInfoValue>(ItemPtr);
1421 RETURN_IF_ERROR(writeVersionInfoValue(*ValuePtr));
1424 if (OutputHeader) {
1425 uint64_t CurLoc = tell();
1426 writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc);
1429 return Error::success();
1432 Error ResourceFileWriter::writeVersionInfoValue(const VersionInfoValue &Val) {
1433 // rc has a peculiar algorithm to output VERSIONINFO VALUEs. Each VALUE
1434 // is a mapping from the key (string) to the value (a sequence of ints or
1435 // a sequence of strings).
1437 // If integers are to be written: width of each integer written depends on
1438 // whether it's been declared 'long' (it's DWORD then) or not (it's WORD).
1439 // ValueLength defined in structure referenced below is then the total
1440 // number of bytes taken by these integers.
1442 // If strings are to be written: characters are always WORDs.
1443 // Moreover, '\0' character is written after the last string, and between
1444 // every two strings separated by comma (if strings are not comma-separated,
1445 // they're simply concatenated). ValueLength is equal to the number of WORDs
1446 // written (that is, half of the bytes written).
1448 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms646994.aspx
1449 bool HasStrings = false, HasInts = false;
1450 for (auto &Item : Val.Values)
1451 (Item.isInt() ? HasInts : HasStrings) = true;
1453 assert((HasStrings || HasInts) && "VALUE must have at least one argument");
1454 if (HasStrings && HasInts)
1455 return createError(Twine("VALUE ") + Val.Key +
1456 " cannot contain both strings and integers");
1458 padStream(sizeof(uint32_t));
1459 auto LengthLoc = writeInt<uint16_t>(0);
1460 auto ValLengthLoc = writeInt<uint16_t>(0);
1461 writeInt<uint16_t>(HasStrings);
1462 RETURN_IF_ERROR(writeCString(Val.Key));
1463 padStream(sizeof(uint32_t));
1465 auto DataLoc = tell();
1466 for (size_t Id = 0; Id < Val.Values.size(); ++Id) {
1467 auto &Item = Val.Values[Id];
1468 if (Item.isInt()) {
1469 auto Value = Item.getInt();
1470 RETURN_IF_ERROR(checkRCInt(Value, "VERSIONINFO integer value"));
1471 writeRCInt(Value);
1472 continue;
1475 bool WriteTerminator =
1476 Id == Val.Values.size() - 1 || Val.HasPrecedingComma[Id + 1];
1477 RETURN_IF_ERROR(writeCString(Item.getString(), WriteTerminator));
1480 auto CurLoc = tell();
1481 auto ValueLength = CurLoc - DataLoc;
1482 if (HasStrings) {
1483 assert(ValueLength % 2 == 0);
1484 ValueLength /= 2;
1486 writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc);
1487 writeObjectAt(ulittle16_t(ValueLength), ValLengthLoc);
1488 return Error::success();
1491 template <typename Ty>
1492 static Ty getWithDefault(const StringMap<Ty> &Map, StringRef Key,
1493 const Ty &Default) {
1494 auto Iter = Map.find(Key);
1495 if (Iter != Map.end())
1496 return Iter->getValue();
1497 return Default;
1500 Error ResourceFileWriter::writeVersionInfoBody(const RCResource *Base) {
1501 auto *Res = cast<VersionInfoResource>(Base);
1503 const auto &FixedData = Res->FixedData;
1505 struct /* VS_FIXEDFILEINFO */ {
1506 ulittle32_t Signature = ulittle32_t(0xFEEF04BD);
1507 ulittle32_t StructVersion = ulittle32_t(0x10000);
1508 // It's weird to have most-significant DWORD first on the little-endian
1509 // machines, but let it be this way.
1510 ulittle32_t FileVersionMS;
1511 ulittle32_t FileVersionLS;
1512 ulittle32_t ProductVersionMS;
1513 ulittle32_t ProductVersionLS;
1514 ulittle32_t FileFlagsMask;
1515 ulittle32_t FileFlags;
1516 ulittle32_t FileOS;
1517 ulittle32_t FileType;
1518 ulittle32_t FileSubtype;
1519 // MS implementation seems to always set these fields to 0.
1520 ulittle32_t FileDateMS = ulittle32_t(0);
1521 ulittle32_t FileDateLS = ulittle32_t(0);
1522 } FixedInfo;
1524 // First, VS_VERSIONINFO.
1525 auto LengthLoc = writeInt<uint16_t>(0);
1526 writeInt<uint16_t>(sizeof(FixedInfo));
1527 writeInt<uint16_t>(0);
1528 cantFail(writeCString("VS_VERSION_INFO"));
1529 padStream(sizeof(uint32_t));
1531 using VersionInfoFixed = VersionInfoResource::VersionInfoFixed;
1532 auto GetField = [&](VersionInfoFixed::VersionInfoFixedType Type) {
1533 static const SmallVector<uint32_t, 4> DefaultOut{0, 0, 0, 0};
1534 if (!FixedData.IsTypePresent[(int)Type])
1535 return DefaultOut;
1536 return FixedData.FixedInfo[(int)Type];
1539 auto FileVer = GetField(VersionInfoFixed::FtFileVersion);
1540 RETURN_IF_ERROR(checkNumberFits<uint16_t>(
1541 *std::max_element(FileVer.begin(), FileVer.end()), "FILEVERSION fields"));
1542 FixedInfo.FileVersionMS = (FileVer[0] << 16) | FileVer[1];
1543 FixedInfo.FileVersionLS = (FileVer[2] << 16) | FileVer[3];
1545 auto ProdVer = GetField(VersionInfoFixed::FtProductVersion);
1546 RETURN_IF_ERROR(checkNumberFits<uint16_t>(
1547 *std::max_element(ProdVer.begin(), ProdVer.end()),
1548 "PRODUCTVERSION fields"));
1549 FixedInfo.ProductVersionMS = (ProdVer[0] << 16) | ProdVer[1];
1550 FixedInfo.ProductVersionLS = (ProdVer[2] << 16) | ProdVer[3];
1552 FixedInfo.FileFlagsMask = GetField(VersionInfoFixed::FtFileFlagsMask)[0];
1553 FixedInfo.FileFlags = GetField(VersionInfoFixed::FtFileFlags)[0];
1554 FixedInfo.FileOS = GetField(VersionInfoFixed::FtFileOS)[0];
1555 FixedInfo.FileType = GetField(VersionInfoFixed::FtFileType)[0];
1556 FixedInfo.FileSubtype = GetField(VersionInfoFixed::FtFileSubtype)[0];
1558 writeObject(FixedInfo);
1559 padStream(sizeof(uint32_t));
1561 RETURN_IF_ERROR(writeVersionInfoBlock(Res->MainBlock));
1563 // FIXME: check overflow?
1564 writeObjectAt(ulittle16_t(tell() - LengthLoc), LengthLoc);
1566 return Error::success();
1569 Expected<std::unique_ptr<MemoryBuffer>>
1570 ResourceFileWriter::loadFile(StringRef File) const {
1571 SmallString<128> Path;
1572 SmallString<128> Cwd;
1573 std::unique_ptr<MemoryBuffer> Result;
1575 // 0. The file path is absolute or has a root directory, so we shouldn't
1576 // try to append it on top of other base directories. (An absolute path
1577 // must have a root directory, but e.g. the path "\dir\file" on windows
1578 // isn't considered absolute, but it does have a root directory. As long as
1579 // sys::path::append doesn't handle appending an absolute path or a path
1580 // starting with a root directory on top of a base, we must handle this
1581 // case separately at the top. C++17's path::append handles that case
1582 // properly though, so if using that to append paths below, this early
1583 // exception case could be removed.)
1584 if (sys::path::has_root_directory(File))
1585 return errorOrToExpected(MemoryBuffer::getFile(
1586 File, /*IsText=*/false, /*RequiresNullTerminator=*/false));
1588 // 1. The current working directory.
1589 sys::fs::current_path(Cwd);
1590 Path.assign(Cwd.begin(), Cwd.end());
1591 sys::path::append(Path, File);
1592 if (sys::fs::exists(Path))
1593 return errorOrToExpected(MemoryBuffer::getFile(
1594 Path, /*IsText=*/false, /*RequiresNullTerminator=*/false));
1596 // 2. The directory of the input resource file, if it is different from the
1597 // current working directory.
1598 StringRef InputFileDir = sys::path::parent_path(Params.InputFilePath);
1599 Path.assign(InputFileDir.begin(), InputFileDir.end());
1600 sys::path::append(Path, File);
1601 if (sys::fs::exists(Path))
1602 return errorOrToExpected(MemoryBuffer::getFile(
1603 Path, /*IsText=*/false, /*RequiresNullTerminator=*/false));
1605 // 3. All of the include directories specified on the command line.
1606 for (StringRef ForceInclude : Params.Include) {
1607 Path.assign(ForceInclude.begin(), ForceInclude.end());
1608 sys::path::append(Path, File);
1609 if (sys::fs::exists(Path))
1610 return errorOrToExpected(MemoryBuffer::getFile(
1611 Path, /*IsText=*/false, /*RequiresNullTerminator=*/false));
1614 if (!Params.NoInclude) {
1615 if (auto Result = llvm::sys::Process::FindInEnvPath("INCLUDE", File))
1616 return errorOrToExpected(MemoryBuffer::getFile(
1617 *Result, /*IsText=*/false, /*RequiresNullTerminator=*/false));
1620 return make_error<StringError>("error : file not found : " + Twine(File),
1621 inconvertibleErrorCode());
1624 } // namespace rc
1625 } // namespace llvm