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