xref: /llvm-project/llvm/tools/llvm-rc/ResourceFileWriter.cpp (revision 8b356f496b18d89d58820494b0f806b38822ebb9)
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.
10 //
11 //===---------------------------------------------------------------------===//
12 
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"
22 
23 using namespace llvm::support;
24 
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;
29 
30 namespace llvm {
31 namespace rc {
32 
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;
39 
40 public:
41   ContextKeeper(ResourceFileWriter *V)
42       : FileWriter(V), SavedInfo(V->ObjectData) {}
43   ~ContextKeeper() { FileWriter->ObjectData = SavedInfo; }
44 };
45 
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));
49 }
50 
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);
59 }
60 
61 template <typename FitType>
62 static Error checkNumberFits(uint32_t Number, const Twine &FieldName) {
63   return checkNumberFits(Number, sizeof(FitType) * 8, FieldName);
64 }
65 
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);
77 
78   if (!CanBeNegative && SignedNum < 0)
79     return createError(FieldName + " (" + Twine(SignedNum) +
80                        ") cannot be negative.");
81 
82   return Error::success();
83 }
84 
85 static Error checkRCInt(RCInt Number, const Twine &FieldName) {
86   if (Number.isLong())
87     return Error::success();
88   return checkNumberFits<uint16_t>(Number, FieldName);
89 }
90 
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);
95 }
96 
97 static bool stripQuotes(StringRef &Str, bool &IsLongString) {
98   if (!Str.contains('"'))
99     return false;
100 
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();
105 
106   bool StripSuccess = Str.consume_front("\"") && Str.consume_back("\"");
107   (void)StripSuccess;
108   assert(StripSuccess && "Strings should be enclosed in quotes.");
109   return true;
110 }
111 
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,
118   };
119   if (C >= 0x80 && C <= 0x9F)
120     return Map80[C - 0x80];
121   return C;
122 }
123 
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.
133 
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'.
138 };
139 
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;
151 
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);
165     }
166   }
167 
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);
173     }
174     Result.swap(Chars);
175     return Error::success();
176   }
177   Result.reserve(Chars.size());
178   size_t Pos = 0;
179 
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");
188       }
189     }
190 
191     Result.push_back(Char);
192     return Error::success();
193   };
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");
213       }
214     }
215 
216     return AddRes(Char);
217   };
218 
219   while (Pos < Chars.size()) {
220     UTF16 CurChar = Chars[Pos];
221     ++Pos;
222 
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;
230     }
231 
232     if (CurChar == '\\') {
233       UTF16 TypeChar = Chars[Pos];
234       ++Pos;
235 
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.
243         //
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;
258 
259           ReadInt <<= 4;
260           if (isdigit(Digit))
261             ReadInt |= Digit - '0';
262           else
263             ReadInt |= Digit - 'a' + 10;
264 
265           --RemainingChars;
266         }
267 
268         RETURN_IF_ERROR(AddEscapedChar(ReadInt));
269         continue;
270       }
271 
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;
276 
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;
283         }
284 
285         RETURN_IF_ERROR(AddEscapedChar(ReadInt));
286 
287         continue;
288       }
289 
290       switch (TypeChar) {
291       case 'A':
292       case 'a':
293         // Windows '\a' translates into '\b' (Backspace).
294         RETURN_IF_ERROR(AddRes('\b'));
295         break;
296 
297       case 'n': // Somehow, RC doesn't recognize '\N' and '\R'.
298         RETURN_IF_ERROR(AddRes('\n'));
299         break;
300 
301       case 'r':
302         RETURN_IF_ERROR(AddRes('\r'));
303         break;
304 
305       case 'T':
306       case 't':
307         RETURN_IF_ERROR(AddRes('\t'));
308         break;
309 
310       case '\\':
311         RETURN_IF_ERROR(AddRes('\\'));
312         break;
313 
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;
322 
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));
330         }
331         break;
332       }
333 
334       continue;
335     }
336 
337     // If nothing interesting happens, just output the character.
338     RETURN_IF_ERROR(AddRes(CurChar));
339   }
340 
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;
347 
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;
355 
356   case NullHandlingMethod::UserResource:
357     break;
358   }
359 
360   return Error::success();
361 }
362 
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;
367 }
368 
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();
380 }
381 
382 Error ResourceFileWriter::writeIdentifier(const IntOrString &Ident) {
383   return writeIntOrString(Ident);
384 }
385 
386 Error ResourceFileWriter::writeIntOrString(const IntOrString &Value) {
387   if (!Value.isInt())
388     return writeCString(Value.getString());
389 
390   writeInt<uint16_t>(0xFFFF);
391   writeInt<uint16_t>(Value.getInt());
392   return Error::success();
393 }
394 
395 void ResourceFileWriter::writeRCInt(RCInt Value) {
396   if (Value.isLong())
397     writeInt<uint32_t>(Value);
398   else
399     writeInt<uint16_t>(Value);
400 }
401 
402 Error ResourceFileWriter::appendFile(StringRef Filename) {
403   bool IsLong;
404   stripQuotes(Filename, IsLong);
405 
406   auto File = loadFile(Filename);
407   if (!File)
408     return File.takeError();
409 
410   *FS << (*File)->getBuffer();
411   return Error::success();
412 }
413 
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);
421 }
422 
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();
430 }
431 
432 Error ResourceFileWriter::visitNullResource(const RCResource *Res) {
433   return writeResource(Res, &ResourceFileWriter::writeNullBody);
434 }
435 
436 Error ResourceFileWriter::visitAcceleratorsResource(const RCResource *Res) {
437   return writeResource(Res, &ResourceFileWriter::writeAcceleratorsBody);
438 }
439 
440 Error ResourceFileWriter::visitBitmapResource(const RCResource *Res) {
441   return writeResource(Res, &ResourceFileWriter::writeBitmapBody);
442 }
443 
444 Error ResourceFileWriter::visitCursorResource(const RCResource *Res) {
445   return handleError(visitIconOrCursorResource(Res), Res);
446 }
447 
448 Error ResourceFileWriter::visitDialogResource(const RCResource *Res) {
449   return writeResource(Res, &ResourceFileWriter::writeDialogBody);
450 }
451 
452 Error ResourceFileWriter::visitIconResource(const RCResource *Res) {
453   return handleError(visitIconOrCursorResource(Res), Res);
454 }
455 
456 Error ResourceFileWriter::visitCaptionStmt(const CaptionStmt *Stmt) {
457   ObjectData.Caption = Stmt->Value;
458   return Error::success();
459 }
460 
461 Error ResourceFileWriter::visitClassStmt(const ClassStmt *Stmt) {
462   ObjectData.Class = Stmt->Value;
463   return Error::success();
464 }
465 
466 Error ResourceFileWriter::visitHTMLResource(const RCResource *Res) {
467   return writeResource(Res, &ResourceFileWriter::writeHTMLBody);
468 }
469 
470 Error ResourceFileWriter::visitMenuResource(const RCResource *Res) {
471   return writeResource(Res, &ResourceFileWriter::writeMenuBody);
472 }
473 
474 Error ResourceFileWriter::visitMenuExResource(const RCResource *Res) {
475   return writeResource(Res, &ResourceFileWriter::writeMenuExBody);
476 }
477 
478 Error ResourceFileWriter::visitStringTableResource(const RCResource *Base) {
479   const auto *Res = cast<StringTableResource>(Base);
480 
481   ContextKeeper RAII(this);
482   RETURN_IF_ERROR(Res->applyStmts(this));
483 
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);
490 
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;
498     }
499 
500     RETURN_IF_ERROR(
501         insertStringIntoBundle(Iter->second, String.first, String.second));
502   }
503 
504   return Error::success();
505 }
506 
507 Error ResourceFileWriter::visitUserDefinedResource(const RCResource *Res) {
508   return writeResource(Res, &ResourceFileWriter::writeUserDefinedBody);
509 }
510 
511 Error ResourceFileWriter::visitVersionInfoResource(const RCResource *Res) {
512   return writeResource(Res, &ResourceFileWriter::writeVersionInfoBody);
513 }
514 
515 Error ResourceFileWriter::visitCharacteristicsStmt(
516     const CharacteristicsStmt *Stmt) {
517   ObjectData.Characteristics = Stmt->Value;
518   return Error::success();
519 }
520 
521 Error ResourceFileWriter::visitExStyleStmt(const ExStyleStmt *Stmt) {
522   ObjectData.ExStyle = Stmt->Value;
523   return Error::success();
524 }
525 
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();
534 }
535 
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();
541 }
542 
543 Error ResourceFileWriter::visitStyleStmt(const StyleStmt *Stmt) {
544   ObjectData.Style = Stmt->Value;
545   return Error::success();
546 }
547 
548 Error ResourceFileWriter::visitVersionStmt(const VersionStmt *Stmt) {
549   ObjectData.VersionInfo = Stmt->Value;
550   return Error::success();
551 }
552 
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);
559 
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));
565 
566   // Apply the resource-local optional statements.
567   ContextKeeper RAII(this);
568   RETURN_IF_ERROR(handleError(Res->applyStmts(this), Res));
569 
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);
577 
578   uint64_t DataLoc = tell();
579   RETURN_IF_ERROR(handleError((this->*BodyWriter)(Res), Res));
580   // RETURN_IF_ERROR(handleError(dumpResource(Ctx)));
581 
582   // Update the sizes.
583   HeaderPrefix.DataSize = tell() - DataLoc;
584   HeaderPrefix.HeaderSize = DataLoc - HeaderLoc;
585   writeObjectAt(HeaderPrefix, HeaderLoc);
586   padStream(sizeof(uint32_t));
587 
588   return Error::success();
589 }
590 
591 // --- NullResource helpers. --- //
592 
593 Error ResourceFileWriter::writeNullBody(const RCResource *) {
594   return Error::success();
595 }
596 
597 // --- AcceleratorsResource helpers. --- //
598 
599 Error ResourceFileWriter::writeSingleAccelerator(
600     const AcceleratorsResource::Accelerator &Obj, bool IsLastItem) {
601   using Accelerator = AcceleratorsResource::Accelerator;
602   using Opt = Accelerator::Options;
603 
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};
610 
611   bool IsASCII = Obj.Flags & Opt::ASCII, IsVirtKey = Obj.Flags & Opt::VIRTKEY;
612 
613   // Remove ASCII flags (which doesn't occur in .res files).
614   Entry.Flags = Obj.Flags & ~Opt::ASCII;
615 
616   if (IsLastItem)
617     Entry.Flags |= 0x80;
618 
619   RETURN_IF_ERROR(checkNumberFits<uint16_t>(Obj.Id, "ACCELERATORS entry ID"));
620   Entry.Id = ulittle16_t(Obj.Id);
621 
622   auto createAccError = [&Obj](const char *Msg) {
623     return createError("Accelerator ID " + Twine(Obj.Id) + ": " + Msg);
624   };
625 
626   if (IsASCII && IsVirtKey)
627     return createAccError("Accelerator can't be both ASCII and VIRTKEY");
628 
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");
632 
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");
638 
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();
645   }
646 
647   StringRef Str = Obj.Event.getString();
648   bool IsWide;
649   stripQuotes(Str, IsWide);
650 
651   if (Str.size() == 0 || Str.size() > 2)
652     return createAccError(
653         "Accelerator string events should have length 1 or 2");
654 
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 '^'");
661 
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");
670 
671     writeObject(Entry);
672     return Error::success();
673   }
674 
675   if (Str.size() == 2)
676     return createAccError("Event string should be one-character, possibly"
677                           " preceded by '^'");
678 
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");
686 
687   if (IsVirtKey)
688     EventCh = toupper(EventCh);
689   Entry.ANSICode = ulittle16_t(EventCh);
690   writeObject(Entry);
691   return Error::success();
692 }
693 
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()));
701   }
702   return Error::success();
703 }
704 
705 // --- BitmapResource helpers. --- //
706 
707 Error ResourceFileWriter::writeBitmapBody(const RCResource *Base) {
708   StringRef Filename = cast<BitmapResource>(Base)->BitmapLoc;
709   bool IsLong;
710   stripQuotes(Filename, IsLong);
711 
712   auto File = loadFile(Filename);
713   if (!File)
714     return File.takeError();
715 
716   StringRef Buffer = (*File)->getBuffer();
717 
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.");
723 
724   *FS << Buffer.substr(BITMAPFILEHEADER_size);
725   return Error::success();
726 }
727 
728 // --- CursorResource and IconResource helpers. --- //
729 
730 // ICONRESDIR structure. Describes a single icon in resource group.
731 //
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;
738 };
739 
740 // CURSORDIR structure. Describes a single cursor in resource group.
741 //
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;
746 };
747 
748 // RESDIRENTRY structure, stripped from the last item. Stripping made
749 // for compatibility with RESDIR.
750 //
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.
756   };
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).
762 };
763 
764 // BITMAPINFOHEADER structure. Describes basic information about the bitmap
765 // being read.
766 //
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;
780 };
781 
782 // Group icon directory header. Called ICONDIR in .ico/.cur files and
783 // NEWHEADER in .res files.
784 //
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.
790 };
791 
792 enum class IconCursorGroupType { Icon, Cursor };
793 
794 class SingleIconCursorResource : public RCResource {
795 public:
796   IconCursorGroupType Type;
797   const ResourceDirEntryStart &Header;
798   ArrayRef<uint8_t> Image;
799 
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) {}
805 
806   Twine getResourceTypeName() const override { return "Icon/cursor image"; }
807   IntOrString getResourceType() const override {
808     return Type == IconCursorGroupType::Icon ? RkSingleIcon : RkSingleCursor;
809   }
810   ResourceKind getKind() const override { return RkSingleCursorOrIconRes; }
811   static bool classof(const RCResource *Res) {
812     return Res->getKind() == RkSingleCursorOrIconRes;
813   }
814 };
815 
816 class IconCursorGroupResource : public RCResource {
817 public:
818   IconCursorGroupType Type;
819   GroupIconDir Header;
820   std::vector<ResourceDirEntryStart> ItemEntries;
821 
822   IconCursorGroupResource(IconCursorGroupType ResourceType,
823                           const GroupIconDir &HeaderData,
824                           std::vector<ResourceDirEntryStart> &&Entries)
825       : Type(ResourceType), Header(HeaderData),
826         ItemEntries(std::move(Entries)) {}
827 
828   Twine getResourceTypeName() const override { return "Icon/cursor group"; }
829   IntOrString getResourceType() const override {
830     return Type == IconCursorGroupType::Icon ? RkIconGroup : RkCursorGroup;
831   }
832   ResourceKind getKind() const override { return RkCursorOrIconGroupRes; }
833   static bool classof(const RCResource *Res) {
834     return Res->getKind() == RkCursorOrIconGroupRes;
835   }
836 };
837 
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).
844     //
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);
849   }
850 
851   writeObject(Res->Image);
852   return Error::success();
853 }
854 
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++);
861   }
862   return Error::success();
863 }
864 
865 Error ResourceFileWriter::visitSingleIconOrCursor(const RCResource *Res) {
866   return writeResource(Res, &ResourceFileWriter::writeSingleIconOrCursorBody);
867 }
868 
869 Error ResourceFileWriter::visitIconOrCursorGroup(const RCResource *Res) {
870   return writeResource(Res, &ResourceFileWriter::writeIconOrCursorGroupBody);
871 }
872 
873 Error ResourceFileWriter::visitIconOrCursorResource(const RCResource *Base) {
874   IconCursorGroupType Type;
875   StringRef FileStr;
876   IntOrString ResName = Base->ResName;
877 
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;
885   }
886 
887   bool IsLong;
888   stripQuotes(FileStr, IsLong);
889   auto File = loadFile(FileStr);
890 
891   if (!File)
892     return File.takeError();
893 
894   BinaryStreamReader Reader((*File)->getBuffer(), support::little);
895 
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;
901 
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) + ".");
909 
910   uint16_t NumItems = Header->ResCount;
911 
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]));
921   }
922 
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));
934   }
935 
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;
942 
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;
950 
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);
954     }
955 
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;
970     }
971 
972     ItemEntries[ID] = NewHeader;
973   }
974 
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;
980   }
981   RETURN_IF_ERROR(visitIconOrCursorGroup(&HeaderRes));
982 
983   return Error::success();
984 }
985 
986 // --- DialogResource helpers. --- //
987 
988 Error ResourceFileWriter::writeSingleDialogControl(const Control &Ctl,
989                                                    bool IsExtended) {
990   // Each control should be aligned to DWORD.
991   padStream(sizeof(uint32_t));
992 
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);
997 
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);
1013   }
1014 
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);
1032 
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);
1043   }
1044 
1045   // Window class - either 0xFFFF + 16-bit integer or a string.
1046   RETURN_IF_ERROR(writeIntOrString(Ctl.Class));
1047 
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));
1051 
1052   // # bytes of extra creation data count. Don't pass any.
1053   writeInt<uint16_t>(0);
1054 
1055   return Error::success();
1056 }
1057 
1058 Error ResourceFileWriter::writeDialogBody(const RCResource *Base) {
1059   auto *Res = cast<DialogResource>(Base);
1060 
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;
1065 
1066   uint32_t UsedStyle = ObjectData.Style.value_or(DefaultStyle);
1067   if (ObjectData.Font)
1068     UsedStyle |= StyleFontFlag;
1069   else
1070     UsedStyle &= ~StyleFontFlag;
1071 
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;
1076 
1077   const uint16_t DialogExMagic = 0xFFFF;
1078   uint32_t ExStyle = ObjectData.ExStyle.value_or(0);
1079 
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");
1092 
1093     struct {
1094       ulittle32_t Style;
1095       ulittle32_t ExtStyle;
1096     } Prefix{ulittle32_t(UsedStyle),
1097              ulittle32_t(ExStyle)};
1098 
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)};
1109 
1110     writeObject(Prefix);
1111   }
1112 
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);
1134 
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);
1138 
1139   // Window CLASS field.
1140   RETURN_IF_ERROR(writeIntOrString(ObjectData.Class));
1141 
1142   // Window title or a single word equal to 0.
1143   RETURN_IF_ERROR(writeCString(ObjectData.Caption));
1144 
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);
1154     }
1155     RETURN_IF_ERROR(writeCString(Font->Typeface));
1156   }
1157 
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));
1164   };
1165 
1166   for (auto &Ctl : Res->Controls)
1167     RETURN_IF_ERROR(
1168         handleCtlError(writeSingleDialogControl(Ctl, Res->IsExtended), Ctl));
1169 
1170   return Error::success();
1171 }
1172 
1173 // --- HTMLResource helpers. --- //
1174 
1175 Error ResourceFileWriter::writeHTMLBody(const RCResource *Base) {
1176   return appendFile(cast<HTMLResource>(Base)->HTMLLoc);
1177 }
1178 
1179 // --- MenuResource helpers. --- //
1180 
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();
1186 
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();
1196   }
1197 
1198   if (isa<MenuSeparator>(DefPtr)) {
1199     writeInt<uint16_t>(Flags);
1200     writeInt<uint32_t>(0);
1201     return Error::success();
1202   }
1203 
1204   auto *PopupPtr = cast<PopupItem>(DefPtr);
1205   writeInt<uint16_t>(Flags);
1206   RETURN_IF_ERROR(writeCString(PopupPtr->Name));
1207   return writeMenuDefinitionList(PopupPtr->SubItems);
1208 }
1209 
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();
1215 
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();
1225   }
1226 
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);
1236 }
1237 
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;
1246 
1247     RETURN_IF_ERROR(writeMenuDefinition(Def, Flags));
1248   }
1249   return Error::success();
1250 }
1251 
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;
1260 
1261     RETURN_IF_ERROR(writeMenuExDefinition(Def, Flags));
1262   }
1263   return Error::success();
1264 }
1265 
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);
1270 
1271   return writeMenuDefinitionList(cast<MenuResource>(Base)->Elements);
1272 }
1273 
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);
1281 
1282   return writeMenuExDefinitionList(cast<MenuExResource>(Base)->Elements);
1283 }
1284 
1285 // --- StringTableResource helpers. --- //
1286 
1287 class BundleResource : public RCResource {
1288 public:
1289   using BundleType = ResourceFileWriter::StringTableInfo::Bundle;
1290   BundleType Bundle;
1291 
1292   BundleResource(const BundleType &StrBundle)
1293       : RCResource(StrBundle.MemoryFlags), Bundle(StrBundle) {}
1294   IntOrString getResourceType() const override { return 6; }
1295 
1296   ResourceKind getKind() const override { return RkStringTableBundle; }
1297   static bool classof(const RCResource *Res) {
1298     return Res->getKind() == RkStringTableBundle;
1299   }
1300   Twine getResourceTypeName() const override { return "STRINGTABLE"; }
1301 };
1302 
1303 Error ResourceFileWriter::visitStringTableBundle(const RCResource *Res) {
1304   return writeResource(Res, &ResourceFileWriter::writeStringTableBundleBody);
1305 }
1306 
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();
1316 }
1317 
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');
1332     }
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);
1338   }
1339   return Error::success();
1340 }
1341 
1342 Error ResourceFileWriter::dumpAllStringTables() {
1343   for (auto Key : StringTableData.BundleList) {
1344     auto Iter = StringTableData.BundleData.find(Key);
1345     assert(Iter != StringTableData.BundleData.end());
1346 
1347     // For a moment, revert the context info to moment of bundle declaration.
1348     ContextKeeper RAII(this);
1349     ObjectData = Iter->second.DeclTimeInfo;
1350 
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));
1355   }
1356   return Error::success();
1357 }
1358 
1359 // --- UserDefinedResource helpers. --- //
1360 
1361 Error ResourceFileWriter::writeUserDefinedBody(const RCResource *Base) {
1362   auto *Res = cast<UserDefinedResource>(Base);
1363 
1364   if (Res->IsFileResource)
1365     return appendFile(Res->FileLoc);
1366 
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;
1373     }
1374 
1375     SmallVector<UTF16, 128> ProcessedString;
1376     bool IsLongString;
1377     RETURN_IF_ERROR(
1378         processString(Elem.getString(), NullHandlingMethod::UserResource,
1379                       IsLongString, ProcessedString, Params.CodePage));
1380 
1381     for (auto Ch : ProcessedString) {
1382       if (IsLongString) {
1383         writeInt(Ch);
1384         continue;
1385       }
1386 
1387       RETURN_IF_ERROR(checkNumberFits<uint8_t>(
1388           Ch, "Character in narrow string in user-defined resource"));
1389       writeInt<uint8_t>(Ch);
1390     }
1391   }
1392 
1393   return Error::success();
1394 }
1395 
1396 // --- VersionInfoResourceResource helpers. --- //
1397 
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;
1402 
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));
1410   }
1411 
1412   for (const std::unique_ptr<VersionInfoStmt> &Item : Blk.Stmts) {
1413     VersionInfoStmt *ItemPtr = Item.get();
1414 
1415     if (auto *BlockPtr = dyn_cast<VersionInfoBlock>(ItemPtr)) {
1416       RETURN_IF_ERROR(writeVersionInfoBlock(*BlockPtr));
1417       continue;
1418     }
1419 
1420     auto *ValuePtr = cast<VersionInfoValue>(ItemPtr);
1421     RETURN_IF_ERROR(writeVersionInfoValue(*ValuePtr));
1422   }
1423 
1424   if (OutputHeader) {
1425     uint64_t CurLoc = tell();
1426     writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc);
1427   }
1428 
1429   return Error::success();
1430 }
1431 
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).
1436   //
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.
1441   //
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).
1447   //
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;
1452 
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");
1457 
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));
1464 
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;
1473     }
1474 
1475     bool WriteTerminator =
1476         Id == Val.Values.size() - 1 || Val.HasPrecedingComma[Id + 1];
1477     RETURN_IF_ERROR(writeCString(Item.getString(), WriteTerminator));
1478   }
1479 
1480   auto CurLoc = tell();
1481   auto ValueLength = CurLoc - DataLoc;
1482   if (HasStrings) {
1483     assert(ValueLength % 2 == 0);
1484     ValueLength /= 2;
1485   }
1486   writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc);
1487   writeObjectAt(ulittle16_t(ValueLength), ValLengthLoc);
1488   return Error::success();
1489 }
1490 
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;
1498 }
1499 
1500 Error ResourceFileWriter::writeVersionInfoBody(const RCResource *Base) {
1501   auto *Res = cast<VersionInfoResource>(Base);
1502 
1503   const auto &FixedData = Res->FixedData;
1504 
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;
1523 
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));
1530 
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];
1537   };
1538 
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];
1544 
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];
1551 
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];
1557 
1558   writeObject(FixedInfo);
1559   padStream(sizeof(uint32_t));
1560 
1561   RETURN_IF_ERROR(writeVersionInfoBlock(Res->MainBlock));
1562 
1563   // FIXME: check overflow?
1564   writeObjectAt(ulittle16_t(tell() - LengthLoc), LengthLoc);
1565 
1566   return Error::success();
1567 }
1568 
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;
1574 
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));
1587 
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));
1595 
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));
1604 
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));
1612   }
1613 
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));
1618   }
1619 
1620   return make_error<StringError>("error : file not found : " + Twine(File),
1621                                  inconvertibleErrorCode());
1622 }
1623 
1624 } // namespace rc
1625 } // namespace llvm
1626