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