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