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