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::visitHTMLResource(const RCResource *Res) { 462 return writeResource(Res, &ResourceFileWriter::writeHTMLBody); 463 } 464 465 Error ResourceFileWriter::visitMenuResource(const RCResource *Res) { 466 return writeResource(Res, &ResourceFileWriter::writeMenuBody); 467 } 468 469 Error ResourceFileWriter::visitStringTableResource(const RCResource *Base) { 470 const auto *Res = cast<StringTableResource>(Base); 471 472 ContextKeeper RAII(this); 473 RETURN_IF_ERROR(Res->applyStmts(this)); 474 475 for (auto &String : Res->Table) { 476 RETURN_IF_ERROR(checkNumberFits<uint16_t>(String.first, "String ID")); 477 uint16_t BundleID = String.first >> 4; 478 StringTableInfo::BundleKey Key(BundleID, ObjectData.LanguageInfo); 479 auto &BundleData = StringTableData.BundleData; 480 auto Iter = BundleData.find(Key); 481 482 if (Iter == BundleData.end()) { 483 // Need to create a bundle. 484 StringTableData.BundleList.push_back(Key); 485 auto EmplaceResult = BundleData.emplace( 486 Key, StringTableInfo::Bundle(ObjectData, Res->MemoryFlags)); 487 assert(EmplaceResult.second && "Could not create a bundle"); 488 Iter = EmplaceResult.first; 489 } 490 491 RETURN_IF_ERROR( 492 insertStringIntoBundle(Iter->second, String.first, String.second)); 493 } 494 495 return Error::success(); 496 } 497 498 Error ResourceFileWriter::visitUserDefinedResource(const RCResource *Res) { 499 return writeResource(Res, &ResourceFileWriter::writeUserDefinedBody); 500 } 501 502 Error ResourceFileWriter::visitVersionInfoResource(const RCResource *Res) { 503 return writeResource(Res, &ResourceFileWriter::writeVersionInfoBody); 504 } 505 506 Error ResourceFileWriter::visitCharacteristicsStmt( 507 const CharacteristicsStmt *Stmt) { 508 ObjectData.Characteristics = Stmt->Value; 509 return Error::success(); 510 } 511 512 Error ResourceFileWriter::visitFontStmt(const FontStmt *Stmt) { 513 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Stmt->Size, "Font size")); 514 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Stmt->Weight, "Font weight")); 515 RETURN_IF_ERROR(checkNumberFits<uint8_t>(Stmt->Charset, "Font charset")); 516 ObjectInfo::FontInfo Font{Stmt->Size, Stmt->Name, Stmt->Weight, Stmt->Italic, 517 Stmt->Charset}; 518 ObjectData.Font.emplace(Font); 519 return Error::success(); 520 } 521 522 Error ResourceFileWriter::visitLanguageStmt(const LanguageResource *Stmt) { 523 RETURN_IF_ERROR(checkNumberFits(Stmt->Lang, 10, "Primary language ID")); 524 RETURN_IF_ERROR(checkNumberFits(Stmt->SubLang, 6, "Sublanguage ID")); 525 ObjectData.LanguageInfo = Stmt->Lang | (Stmt->SubLang << 10); 526 return Error::success(); 527 } 528 529 Error ResourceFileWriter::visitStyleStmt(const StyleStmt *Stmt) { 530 ObjectData.Style = Stmt->Value; 531 return Error::success(); 532 } 533 534 Error ResourceFileWriter::visitVersionStmt(const VersionStmt *Stmt) { 535 ObjectData.VersionInfo = Stmt->Value; 536 return Error::success(); 537 } 538 539 Error ResourceFileWriter::writeResource( 540 const RCResource *Res, 541 Error (ResourceFileWriter::*BodyWriter)(const RCResource *)) { 542 // We don't know the sizes yet. 543 object::WinResHeaderPrefix HeaderPrefix{ulittle32_t(0U), ulittle32_t(0U)}; 544 uint64_t HeaderLoc = writeObject(HeaderPrefix); 545 546 auto ResType = Res->getResourceType(); 547 RETURN_IF_ERROR(checkIntOrString(ResType, "Resource type")); 548 RETURN_IF_ERROR(checkIntOrString(Res->ResName, "Resource ID")); 549 RETURN_IF_ERROR(handleError(writeIdentifier(ResType), Res)); 550 RETURN_IF_ERROR(handleError(writeIdentifier(Res->ResName), Res)); 551 552 // Apply the resource-local optional statements. 553 ContextKeeper RAII(this); 554 RETURN_IF_ERROR(handleError(Res->applyStmts(this), Res)); 555 556 padStream(sizeof(uint32_t)); 557 object::WinResHeaderSuffix HeaderSuffix{ 558 ulittle32_t(0), // DataVersion; seems to always be 0 559 ulittle16_t(Res->MemoryFlags), ulittle16_t(ObjectData.LanguageInfo), 560 ulittle32_t(ObjectData.VersionInfo), 561 ulittle32_t(ObjectData.Characteristics)}; 562 writeObject(HeaderSuffix); 563 564 uint64_t DataLoc = tell(); 565 RETURN_IF_ERROR(handleError((this->*BodyWriter)(Res), Res)); 566 // RETURN_IF_ERROR(handleError(dumpResource(Ctx))); 567 568 // Update the sizes. 569 HeaderPrefix.DataSize = tell() - DataLoc; 570 HeaderPrefix.HeaderSize = DataLoc - HeaderLoc; 571 writeObjectAt(HeaderPrefix, HeaderLoc); 572 padStream(sizeof(uint32_t)); 573 574 return Error::success(); 575 } 576 577 // --- NullResource helpers. --- // 578 579 Error ResourceFileWriter::writeNullBody(const RCResource *) { 580 return Error::success(); 581 } 582 583 // --- AcceleratorsResource helpers. --- // 584 585 Error ResourceFileWriter::writeSingleAccelerator( 586 const AcceleratorsResource::Accelerator &Obj, bool IsLastItem) { 587 using Accelerator = AcceleratorsResource::Accelerator; 588 using Opt = Accelerator::Options; 589 590 struct AccelTableEntry { 591 ulittle16_t Flags; 592 ulittle16_t ANSICode; 593 ulittle16_t Id; 594 uint16_t Padding; 595 } Entry{ulittle16_t(0), ulittle16_t(0), ulittle16_t(0), 0}; 596 597 bool IsASCII = Obj.Flags & Opt::ASCII, IsVirtKey = Obj.Flags & Opt::VIRTKEY; 598 599 // Remove ASCII flags (which doesn't occur in .res files). 600 Entry.Flags = Obj.Flags & ~Opt::ASCII; 601 602 if (IsLastItem) 603 Entry.Flags |= 0x80; 604 605 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Obj.Id, "ACCELERATORS entry ID")); 606 Entry.Id = ulittle16_t(Obj.Id); 607 608 auto createAccError = [&Obj](const char *Msg) { 609 return createError("Accelerator ID " + Twine(Obj.Id) + ": " + Msg); 610 }; 611 612 if (IsASCII && IsVirtKey) 613 return createAccError("Accelerator can't be both ASCII and VIRTKEY"); 614 615 if (!IsVirtKey && (Obj.Flags & (Opt::ALT | Opt::SHIFT | Opt::CONTROL))) 616 return createAccError("Can only apply ALT, SHIFT or CONTROL to VIRTKEY" 617 " accelerators"); 618 619 if (Obj.Event.isInt()) { 620 if (!IsASCII && !IsVirtKey) 621 return createAccError( 622 "Accelerator with a numeric event must be either ASCII" 623 " or VIRTKEY"); 624 625 uint32_t EventVal = Obj.Event.getInt(); 626 RETURN_IF_ERROR( 627 checkNumberFits<uint16_t>(EventVal, "Numeric event key ID")); 628 Entry.ANSICode = ulittle16_t(EventVal); 629 writeObject(Entry); 630 return Error::success(); 631 } 632 633 StringRef Str = Obj.Event.getString(); 634 bool IsWide; 635 stripQuotes(Str, IsWide); 636 637 if (Str.size() == 0 || Str.size() > 2) 638 return createAccError( 639 "Accelerator string events should have length 1 or 2"); 640 641 if (Str[0] == '^') { 642 if (Str.size() == 1) 643 return createAccError("No character following '^' in accelerator event"); 644 if (IsVirtKey) 645 return createAccError( 646 "VIRTKEY accelerator events can't be preceded by '^'"); 647 648 char Ch = Str[1]; 649 if (Ch >= 'a' && Ch <= 'z') 650 Entry.ANSICode = ulittle16_t(Ch - 'a' + 1); 651 else if (Ch >= 'A' && Ch <= 'Z') 652 Entry.ANSICode = ulittle16_t(Ch - 'A' + 1); 653 else 654 return createAccError("Control character accelerator event should be" 655 " alphabetic"); 656 657 writeObject(Entry); 658 return Error::success(); 659 } 660 661 if (Str.size() == 2) 662 return createAccError("Event string should be one-character, possibly" 663 " preceded by '^'"); 664 665 uint8_t EventCh = Str[0]; 666 // The original tool just warns in this situation. We chose to fail. 667 if (IsVirtKey && !isalnum(EventCh)) 668 return createAccError("Non-alphanumeric characters cannot describe virtual" 669 " keys"); 670 if (EventCh > 0x7F) 671 return createAccError("Non-ASCII description of accelerator"); 672 673 if (IsVirtKey) 674 EventCh = toupper(EventCh); 675 Entry.ANSICode = ulittle16_t(EventCh); 676 writeObject(Entry); 677 return Error::success(); 678 } 679 680 Error ResourceFileWriter::writeAcceleratorsBody(const RCResource *Base) { 681 auto *Res = cast<AcceleratorsResource>(Base); 682 size_t AcceleratorId = 0; 683 for (auto &Acc : Res->Accelerators) { 684 ++AcceleratorId; 685 RETURN_IF_ERROR( 686 writeSingleAccelerator(Acc, AcceleratorId == Res->Accelerators.size())); 687 } 688 return Error::success(); 689 } 690 691 // --- BitmapResource helpers. --- // 692 693 Error ResourceFileWriter::writeBitmapBody(const RCResource *Base) { 694 StringRef Filename = cast<BitmapResource>(Base)->BitmapLoc; 695 bool IsLong; 696 stripQuotes(Filename, IsLong); 697 698 auto File = loadFile(Filename); 699 if (!File) 700 return File.takeError(); 701 702 StringRef Buffer = (*File)->getBuffer(); 703 704 // Skip the 14 byte BITMAPFILEHEADER. 705 constexpr size_t BITMAPFILEHEADER_size = 14; 706 if (Buffer.size() < BITMAPFILEHEADER_size || Buffer[0] != 'B' || 707 Buffer[1] != 'M') 708 return createError("Incorrect bitmap file."); 709 710 *FS << Buffer.substr(BITMAPFILEHEADER_size); 711 return Error::success(); 712 } 713 714 // --- CursorResource and IconResource helpers. --- // 715 716 // ICONRESDIR structure. Describes a single icon in resouce group. 717 // 718 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648016.aspx 719 struct IconResDir { 720 uint8_t Width; 721 uint8_t Height; 722 uint8_t ColorCount; 723 uint8_t Reserved; 724 }; 725 726 // CURSORDIR structure. Describes a single cursor in resource group. 727 // 728 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648011(v=vs.85).aspx 729 struct CursorDir { 730 ulittle16_t Width; 731 ulittle16_t Height; 732 }; 733 734 // RESDIRENTRY structure, stripped from the last item. Stripping made 735 // for compatibility with RESDIR. 736 // 737 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026(v=vs.85).aspx 738 struct ResourceDirEntryStart { 739 union { 740 CursorDir Cursor; // Used in CURSOR resources. 741 IconResDir Icon; // Used in .ico and .cur files, and ICON resources. 742 }; 743 ulittle16_t Planes; // HotspotX (.cur files but not CURSOR resource). 744 ulittle16_t BitCount; // HotspotY (.cur files but not CURSOR resource). 745 ulittle32_t Size; 746 // ulittle32_t ImageOffset; // Offset to image data (ICONDIRENTRY only). 747 // ulittle16_t IconID; // Resource icon ID (RESDIR only). 748 }; 749 750 // BITMAPINFOHEADER structure. Describes basic information about the bitmap 751 // being read. 752 // 753 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx 754 struct BitmapInfoHeader { 755 ulittle32_t Size; 756 ulittle32_t Width; 757 ulittle32_t Height; 758 ulittle16_t Planes; 759 ulittle16_t BitCount; 760 ulittle32_t Compression; 761 ulittle32_t SizeImage; 762 ulittle32_t XPelsPerMeter; 763 ulittle32_t YPelsPerMeter; 764 ulittle32_t ClrUsed; 765 ulittle32_t ClrImportant; 766 }; 767 768 // Group icon directory header. Called ICONDIR in .ico/.cur files and 769 // NEWHEADER in .res files. 770 // 771 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648023(v=vs.85).aspx 772 struct GroupIconDir { 773 ulittle16_t Reserved; // Always 0. 774 ulittle16_t ResType; // 1 for icons, 2 for cursors. 775 ulittle16_t ResCount; // Number of items. 776 }; 777 778 enum class IconCursorGroupType { Icon, Cursor }; 779 780 class SingleIconCursorResource : public RCResource { 781 public: 782 IconCursorGroupType Type; 783 const ResourceDirEntryStart &Header; 784 ArrayRef<uint8_t> Image; 785 786 SingleIconCursorResource(IconCursorGroupType ResourceType, 787 const ResourceDirEntryStart &HeaderEntry, 788 ArrayRef<uint8_t> ImageData, uint16_t Flags) 789 : RCResource(Flags), Type(ResourceType), Header(HeaderEntry), 790 Image(ImageData) {} 791 792 Twine getResourceTypeName() const override { return "Icon/cursor image"; } 793 IntOrString getResourceType() const override { 794 return Type == IconCursorGroupType::Icon ? RkSingleIcon : RkSingleCursor; 795 } 796 ResourceKind getKind() const override { return RkSingleCursorOrIconRes; } 797 static bool classof(const RCResource *Res) { 798 return Res->getKind() == RkSingleCursorOrIconRes; 799 } 800 }; 801 802 class IconCursorGroupResource : public RCResource { 803 public: 804 IconCursorGroupType Type; 805 GroupIconDir Header; 806 std::vector<ResourceDirEntryStart> ItemEntries; 807 808 IconCursorGroupResource(IconCursorGroupType ResourceType, 809 const GroupIconDir &HeaderData, 810 std::vector<ResourceDirEntryStart> &&Entries) 811 : Type(ResourceType), Header(HeaderData), 812 ItemEntries(std::move(Entries)) {} 813 814 Twine getResourceTypeName() const override { return "Icon/cursor group"; } 815 IntOrString getResourceType() const override { 816 return Type == IconCursorGroupType::Icon ? RkIconGroup : RkCursorGroup; 817 } 818 ResourceKind getKind() const override { return RkCursorOrIconGroupRes; } 819 static bool classof(const RCResource *Res) { 820 return Res->getKind() == RkCursorOrIconGroupRes; 821 } 822 }; 823 824 Error ResourceFileWriter::writeSingleIconOrCursorBody(const RCResource *Base) { 825 auto *Res = cast<SingleIconCursorResource>(Base); 826 if (Res->Type == IconCursorGroupType::Cursor) { 827 // In case of cursors, two WORDS are appended to the beginning 828 // of the resource: HotspotX (Planes in RESDIRENTRY), 829 // and HotspotY (BitCount). 830 // 831 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026.aspx 832 // (Remarks section). 833 writeObject(Res->Header.Planes); 834 writeObject(Res->Header.BitCount); 835 } 836 837 writeObject(Res->Image); 838 return Error::success(); 839 } 840 841 Error ResourceFileWriter::writeIconOrCursorGroupBody(const RCResource *Base) { 842 auto *Res = cast<IconCursorGroupResource>(Base); 843 writeObject(Res->Header); 844 for (auto Item : Res->ItemEntries) { 845 writeObject(Item); 846 writeInt(IconCursorID++); 847 } 848 return Error::success(); 849 } 850 851 Error ResourceFileWriter::visitSingleIconOrCursor(const RCResource *Res) { 852 return writeResource(Res, &ResourceFileWriter::writeSingleIconOrCursorBody); 853 } 854 855 Error ResourceFileWriter::visitIconOrCursorGroup(const RCResource *Res) { 856 return writeResource(Res, &ResourceFileWriter::writeIconOrCursorGroupBody); 857 } 858 859 Error ResourceFileWriter::visitIconOrCursorResource(const RCResource *Base) { 860 IconCursorGroupType Type; 861 StringRef FileStr; 862 IntOrString ResName = Base->ResName; 863 864 if (auto *IconRes = dyn_cast<IconResource>(Base)) { 865 FileStr = IconRes->IconLoc; 866 Type = IconCursorGroupType::Icon; 867 } else { 868 auto *CursorRes = dyn_cast<CursorResource>(Base); 869 FileStr = CursorRes->CursorLoc; 870 Type = IconCursorGroupType::Cursor; 871 } 872 873 bool IsLong; 874 stripQuotes(FileStr, IsLong); 875 auto File = loadFile(FileStr); 876 877 if (!File) 878 return File.takeError(); 879 880 BinaryStreamReader Reader((*File)->getBuffer(), support::little); 881 882 // Read the file headers. 883 // - At the beginning, ICONDIR/NEWHEADER header. 884 // - Then, a number of RESDIR headers follow. These contain offsets 885 // to data. 886 const GroupIconDir *Header; 887 888 RETURN_IF_ERROR(Reader.readObject(Header)); 889 if (Header->Reserved != 0) 890 return createError("Incorrect icon/cursor Reserved field; should be 0."); 891 uint16_t NeededType = Type == IconCursorGroupType::Icon ? 1 : 2; 892 if (Header->ResType != NeededType) 893 return createError("Incorrect icon/cursor ResType field; should be " + 894 Twine(NeededType) + "."); 895 896 uint16_t NumItems = Header->ResCount; 897 898 // Read single ico/cur headers. 899 std::vector<ResourceDirEntryStart> ItemEntries; 900 ItemEntries.reserve(NumItems); 901 std::vector<uint32_t> ItemOffsets(NumItems); 902 for (size_t ID = 0; ID < NumItems; ++ID) { 903 const ResourceDirEntryStart *Object; 904 RETURN_IF_ERROR(Reader.readObject(Object)); 905 ItemEntries.push_back(*Object); 906 RETURN_IF_ERROR(Reader.readInteger(ItemOffsets[ID])); 907 } 908 909 // Now write each icon/cursors one by one. At first, all the contents 910 // without ICO/CUR header. This is described by SingleIconCursorResource. 911 for (size_t ID = 0; ID < NumItems; ++ID) { 912 // Load the fragment of file. 913 Reader.setOffset(ItemOffsets[ID]); 914 ArrayRef<uint8_t> Image; 915 RETURN_IF_ERROR(Reader.readArray(Image, ItemEntries[ID].Size)); 916 SingleIconCursorResource SingleRes(Type, ItemEntries[ID], Image, 917 Base->MemoryFlags); 918 SingleRes.setName(IconCursorID + ID); 919 RETURN_IF_ERROR(visitSingleIconOrCursor(&SingleRes)); 920 } 921 922 // Now, write all the headers concatenated into a separate resource. 923 for (size_t ID = 0; ID < NumItems; ++ID) { 924 // We need to rewrite the cursor headers, and fetch actual values 925 // for Planes/BitCount. 926 const auto &OldHeader = ItemEntries[ID]; 927 ResourceDirEntryStart NewHeader = OldHeader; 928 929 if (Type == IconCursorGroupType::Cursor) { 930 NewHeader.Cursor.Width = OldHeader.Icon.Width; 931 // Each cursor in fact stores two bitmaps, one under another. 932 // Height provided in cursor definition describes the height of the 933 // cursor, whereas the value existing in resource definition describes 934 // the height of the bitmap. Therefore, we need to double this height. 935 NewHeader.Cursor.Height = OldHeader.Icon.Height * 2; 936 937 // Two WORDs were written at the beginning of the resource (hotspot 938 // location). This is reflected in Size field. 939 NewHeader.Size += 2 * sizeof(uint16_t); 940 } 941 942 // Now, we actually need to read the bitmap header to find 943 // the number of planes and the number of bits per pixel. 944 Reader.setOffset(ItemOffsets[ID]); 945 const BitmapInfoHeader *BMPHeader; 946 RETURN_IF_ERROR(Reader.readObject(BMPHeader)); 947 if (BMPHeader->Size == sizeof(BitmapInfoHeader)) { 948 NewHeader.Planes = BMPHeader->Planes; 949 NewHeader.BitCount = BMPHeader->BitCount; 950 } else { 951 // A PNG .ico file. 952 // https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473 953 // "The image must be in 32bpp" 954 NewHeader.Planes = 1; 955 NewHeader.BitCount = 32; 956 } 957 958 ItemEntries[ID] = NewHeader; 959 } 960 961 IconCursorGroupResource HeaderRes(Type, *Header, std::move(ItemEntries)); 962 HeaderRes.setName(ResName); 963 if (Base->MemoryFlags & MfPreload) { 964 HeaderRes.MemoryFlags |= MfPreload; 965 HeaderRes.MemoryFlags &= ~MfPure; 966 } 967 RETURN_IF_ERROR(visitIconOrCursorGroup(&HeaderRes)); 968 969 return Error::success(); 970 } 971 972 // --- DialogResource helpers. --- // 973 974 Error ResourceFileWriter::writeSingleDialogControl(const Control &Ctl, 975 bool IsExtended) { 976 // Each control should be aligned to DWORD. 977 padStream(sizeof(uint32_t)); 978 979 auto TypeInfo = Control::SupportedCtls.lookup(Ctl.Type); 980 uint32_t CtlStyle = TypeInfo.Style | Ctl.Style.getValueOr(0); 981 uint32_t CtlExtStyle = Ctl.ExtStyle.getValueOr(0); 982 983 // DIALOG(EX) item header prefix. 984 if (!IsExtended) { 985 struct { 986 ulittle32_t Style; 987 ulittle32_t ExtStyle; 988 } Prefix{ulittle32_t(CtlStyle), ulittle32_t(CtlExtStyle)}; 989 writeObject(Prefix); 990 } else { 991 struct { 992 ulittle32_t HelpID; 993 ulittle32_t ExtStyle; 994 ulittle32_t Style; 995 } Prefix{ulittle32_t(Ctl.HelpID.getValueOr(0)), ulittle32_t(CtlExtStyle), 996 ulittle32_t(CtlStyle)}; 997 writeObject(Prefix); 998 } 999 1000 // Common fixed-length part. 1001 RETURN_IF_ERROR(checkSignedNumberFits<int16_t>( 1002 Ctl.X, "Dialog control x-coordinate", true)); 1003 RETURN_IF_ERROR(checkSignedNumberFits<int16_t>( 1004 Ctl.Y, "Dialog control y-coordinate", true)); 1005 RETURN_IF_ERROR( 1006 checkSignedNumberFits<int16_t>(Ctl.Width, "Dialog control width", false)); 1007 RETURN_IF_ERROR(checkSignedNumberFits<int16_t>( 1008 Ctl.Height, "Dialog control height", false)); 1009 struct { 1010 ulittle16_t X; 1011 ulittle16_t Y; 1012 ulittle16_t Width; 1013 ulittle16_t Height; 1014 } Middle{ulittle16_t(Ctl.X), ulittle16_t(Ctl.Y), ulittle16_t(Ctl.Width), 1015 ulittle16_t(Ctl.Height)}; 1016 writeObject(Middle); 1017 1018 // ID; it's 16-bit in DIALOG and 32-bit in DIALOGEX. 1019 if (!IsExtended) { 1020 // It's common to use -1, i.e. UINT32_MAX, for controls one doesn't 1021 // want to refer to later. 1022 if (Ctl.ID != static_cast<uint32_t>(-1)) 1023 RETURN_IF_ERROR(checkNumberFits<uint16_t>( 1024 Ctl.ID, "Control ID in simple DIALOG resource")); 1025 writeInt<uint16_t>(Ctl.ID); 1026 } else { 1027 writeInt<uint32_t>(Ctl.ID); 1028 } 1029 1030 // Window class - either 0xFFFF + 16-bit integer or a string. 1031 RETURN_IF_ERROR(writeIntOrString(Ctl.Class)); 1032 1033 // Element caption/reference ID. ID is preceded by 0xFFFF. 1034 RETURN_IF_ERROR(checkIntOrString(Ctl.Title, "Control reference ID")); 1035 RETURN_IF_ERROR(writeIntOrString(Ctl.Title)); 1036 1037 // # bytes of extra creation data count. Don't pass any. 1038 writeInt<uint16_t>(0); 1039 1040 return Error::success(); 1041 } 1042 1043 Error ResourceFileWriter::writeDialogBody(const RCResource *Base) { 1044 auto *Res = cast<DialogResource>(Base); 1045 1046 // Default style: WS_POPUP | WS_BORDER | WS_SYSMENU. 1047 const uint32_t DefaultStyle = 0x80880000; 1048 const uint32_t StyleFontFlag = 0x40; 1049 const uint32_t StyleCaptionFlag = 0x00C00000; 1050 1051 uint32_t UsedStyle = ObjectData.Style.getValueOr(DefaultStyle); 1052 if (ObjectData.Font) 1053 UsedStyle |= StyleFontFlag; 1054 else 1055 UsedStyle &= ~StyleFontFlag; 1056 1057 // Actually, in case of empty (but existent) caption, the examined field 1058 // is equal to "\"\"". That's why empty captions are still noticed. 1059 if (ObjectData.Caption != "") 1060 UsedStyle |= StyleCaptionFlag; 1061 1062 const uint16_t DialogExMagic = 0xFFFF; 1063 1064 // Write DIALOG(EX) header prefix. These are pretty different. 1065 if (!Res->IsExtended) { 1066 // We cannot let the higher word of DefaultStyle be equal to 0xFFFF. 1067 // In such a case, whole object (in .res file) is equivalent to a 1068 // DIALOGEX. It might lead to access violation/segmentation fault in 1069 // resource readers. For example, 1070 // 1 DIALOG 0, 0, 0, 65432 1071 // STYLE 0xFFFF0001 {} 1072 // would be compiled to a DIALOGEX with 65432 controls. 1073 if ((UsedStyle >> 16) == DialogExMagic) 1074 return createError("16 higher bits of DIALOG resource style cannot be" 1075 " equal to 0xFFFF"); 1076 1077 struct { 1078 ulittle32_t Style; 1079 ulittle32_t ExtStyle; 1080 } Prefix{ulittle32_t(UsedStyle), 1081 ulittle32_t(0)}; // As of now, we don't keep EXSTYLE. 1082 1083 writeObject(Prefix); 1084 } else { 1085 struct { 1086 ulittle16_t Version; 1087 ulittle16_t Magic; 1088 ulittle32_t HelpID; 1089 ulittle32_t ExtStyle; 1090 ulittle32_t Style; 1091 } Prefix{ulittle16_t(1), ulittle16_t(DialogExMagic), 1092 ulittle32_t(Res->HelpID), ulittle32_t(0), ulittle32_t(UsedStyle)}; 1093 1094 writeObject(Prefix); 1095 } 1096 1097 // Now, a common part. First, fixed-length fields. 1098 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Res->Controls.size(), 1099 "Number of dialog controls")); 1100 RETURN_IF_ERROR( 1101 checkSignedNumberFits<int16_t>(Res->X, "Dialog x-coordinate", true)); 1102 RETURN_IF_ERROR( 1103 checkSignedNumberFits<int16_t>(Res->Y, "Dialog y-coordinate", true)); 1104 RETURN_IF_ERROR( 1105 checkSignedNumberFits<int16_t>(Res->Width, "Dialog width", false)); 1106 RETURN_IF_ERROR( 1107 checkSignedNumberFits<int16_t>(Res->Height, "Dialog height", false)); 1108 struct { 1109 ulittle16_t Count; 1110 ulittle16_t PosX; 1111 ulittle16_t PosY; 1112 ulittle16_t DialogWidth; 1113 ulittle16_t DialogHeight; 1114 } Middle{ulittle16_t(Res->Controls.size()), ulittle16_t(Res->X), 1115 ulittle16_t(Res->Y), ulittle16_t(Res->Width), 1116 ulittle16_t(Res->Height)}; 1117 writeObject(Middle); 1118 1119 // MENU field. As of now, we don't keep them in the state and can peacefully 1120 // think there is no menu attached to the dialog. 1121 writeInt<uint16_t>(0); 1122 1123 // Window CLASS field. Not kept here. 1124 writeInt<uint16_t>(0); 1125 1126 // Window title or a single word equal to 0. 1127 RETURN_IF_ERROR(writeCString(ObjectData.Caption)); 1128 1129 // If there *is* a window font declared, output its data. 1130 auto &Font = ObjectData.Font; 1131 if (Font) { 1132 writeInt<uint16_t>(Font->Size); 1133 // Additional description occurs only in DIALOGEX. 1134 if (Res->IsExtended) { 1135 writeInt<uint16_t>(Font->Weight); 1136 writeInt<uint8_t>(Font->IsItalic); 1137 writeInt<uint8_t>(Font->Charset); 1138 } 1139 RETURN_IF_ERROR(writeCString(Font->Typeface)); 1140 } 1141 1142 auto handleCtlError = [&](Error &&Err, const Control &Ctl) -> Error { 1143 if (!Err) 1144 return Error::success(); 1145 return joinErrors(createError("Error in " + Twine(Ctl.Type) + 1146 " control (ID " + Twine(Ctl.ID) + "):"), 1147 std::move(Err)); 1148 }; 1149 1150 for (auto &Ctl : Res->Controls) 1151 RETURN_IF_ERROR( 1152 handleCtlError(writeSingleDialogControl(Ctl, Res->IsExtended), Ctl)); 1153 1154 return Error::success(); 1155 } 1156 1157 // --- HTMLResource helpers. --- // 1158 1159 Error ResourceFileWriter::writeHTMLBody(const RCResource *Base) { 1160 return appendFile(cast<HTMLResource>(Base)->HTMLLoc); 1161 } 1162 1163 // --- MenuResource helpers. --- // 1164 1165 Error ResourceFileWriter::writeMenuDefinition( 1166 const std::unique_ptr<MenuDefinition> &Def, uint16_t Flags) { 1167 assert(Def); 1168 const MenuDefinition *DefPtr = Def.get(); 1169 1170 if (auto *MenuItemPtr = dyn_cast<MenuItem>(DefPtr)) { 1171 writeInt<uint16_t>(Flags); 1172 RETURN_IF_ERROR( 1173 checkNumberFits<uint16_t>(MenuItemPtr->Id, "MENUITEM action ID")); 1174 writeInt<uint16_t>(MenuItemPtr->Id); 1175 RETURN_IF_ERROR(writeCString(MenuItemPtr->Name)); 1176 return Error::success(); 1177 } 1178 1179 if (isa<MenuSeparator>(DefPtr)) { 1180 writeInt<uint16_t>(Flags); 1181 writeInt<uint32_t>(0); 1182 return Error::success(); 1183 } 1184 1185 auto *PopupPtr = cast<PopupItem>(DefPtr); 1186 writeInt<uint16_t>(Flags); 1187 RETURN_IF_ERROR(writeCString(PopupPtr->Name)); 1188 return writeMenuDefinitionList(PopupPtr->SubItems); 1189 } 1190 1191 Error ResourceFileWriter::writeMenuDefinitionList( 1192 const MenuDefinitionList &List) { 1193 for (auto &Def : List.Definitions) { 1194 uint16_t Flags = Def->getResFlags(); 1195 // Last element receives an additional 0x80 flag. 1196 const uint16_t LastElementFlag = 0x0080; 1197 if (&Def == &List.Definitions.back()) 1198 Flags |= LastElementFlag; 1199 1200 RETURN_IF_ERROR(writeMenuDefinition(Def, Flags)); 1201 } 1202 return Error::success(); 1203 } 1204 1205 Error ResourceFileWriter::writeMenuBody(const RCResource *Base) { 1206 // At first, MENUHEADER structure. In fact, these are two WORDs equal to 0. 1207 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648018.aspx 1208 writeInt<uint32_t>(0); 1209 1210 return writeMenuDefinitionList(cast<MenuResource>(Base)->Elements); 1211 } 1212 1213 // --- StringTableResource helpers. --- // 1214 1215 class BundleResource : public RCResource { 1216 public: 1217 using BundleType = ResourceFileWriter::StringTableInfo::Bundle; 1218 BundleType Bundle; 1219 1220 BundleResource(const BundleType &StrBundle) 1221 : RCResource(StrBundle.MemoryFlags), Bundle(StrBundle) {} 1222 IntOrString getResourceType() const override { return 6; } 1223 1224 ResourceKind getKind() const override { return RkStringTableBundle; } 1225 static bool classof(const RCResource *Res) { 1226 return Res->getKind() == RkStringTableBundle; 1227 } 1228 Twine getResourceTypeName() const override { return "STRINGTABLE"; } 1229 }; 1230 1231 Error ResourceFileWriter::visitStringTableBundle(const RCResource *Res) { 1232 return writeResource(Res, &ResourceFileWriter::writeStringTableBundleBody); 1233 } 1234 1235 Error ResourceFileWriter::insertStringIntoBundle( 1236 StringTableInfo::Bundle &Bundle, uint16_t StringID, StringRef String) { 1237 uint16_t StringLoc = StringID & 15; 1238 if (Bundle.Data[StringLoc]) 1239 return createError("Multiple STRINGTABLE strings located under ID " + 1240 Twine(StringID)); 1241 Bundle.Data[StringLoc] = String; 1242 return Error::success(); 1243 } 1244 1245 Error ResourceFileWriter::writeStringTableBundleBody(const RCResource *Base) { 1246 auto *Res = cast<BundleResource>(Base); 1247 for (size_t ID = 0; ID < Res->Bundle.Data.size(); ++ID) { 1248 // The string format is a tiny bit different here. We 1249 // first output the size of the string, and then the string itself 1250 // (which is not null-terminated). 1251 bool IsLongString; 1252 SmallVector<UTF16, 128> Data; 1253 RETURN_IF_ERROR(processString(Res->Bundle.Data[ID].getValueOr(StringRef()), 1254 NullHandlingMethod::CutAtDoubleNull, 1255 IsLongString, Data, Params.CodePage)); 1256 if (AppendNull && Res->Bundle.Data[ID]) 1257 Data.push_back('\0'); 1258 RETURN_IF_ERROR( 1259 checkNumberFits<uint16_t>(Data.size(), "STRINGTABLE string size")); 1260 writeInt<uint16_t>(Data.size()); 1261 for (auto Char : Data) 1262 writeInt(Char); 1263 } 1264 return Error::success(); 1265 } 1266 1267 Error ResourceFileWriter::dumpAllStringTables() { 1268 for (auto Key : StringTableData.BundleList) { 1269 auto Iter = StringTableData.BundleData.find(Key); 1270 assert(Iter != StringTableData.BundleData.end()); 1271 1272 // For a moment, revert the context info to moment of bundle declaration. 1273 ContextKeeper RAII(this); 1274 ObjectData = Iter->second.DeclTimeInfo; 1275 1276 BundleResource Res(Iter->second); 1277 // Bundle #(k+1) contains keys [16k, 16k + 15]. 1278 Res.setName(Key.first + 1); 1279 RETURN_IF_ERROR(visitStringTableBundle(&Res)); 1280 } 1281 return Error::success(); 1282 } 1283 1284 // --- UserDefinedResource helpers. --- // 1285 1286 Error ResourceFileWriter::writeUserDefinedBody(const RCResource *Base) { 1287 auto *Res = cast<UserDefinedResource>(Base); 1288 1289 if (Res->IsFileResource) 1290 return appendFile(Res->FileLoc); 1291 1292 for (auto &Elem : Res->Contents) { 1293 if (Elem.isInt()) { 1294 RETURN_IF_ERROR( 1295 checkRCInt(Elem.getInt(), "Number in user-defined resource")); 1296 writeRCInt(Elem.getInt()); 1297 continue; 1298 } 1299 1300 SmallVector<UTF16, 128> ProcessedString; 1301 bool IsLongString; 1302 RETURN_IF_ERROR( 1303 processString(Elem.getString(), NullHandlingMethod::UserResource, 1304 IsLongString, ProcessedString, Params.CodePage)); 1305 1306 for (auto Ch : ProcessedString) { 1307 if (IsLongString) { 1308 writeInt(Ch); 1309 continue; 1310 } 1311 1312 RETURN_IF_ERROR(checkNumberFits<uint8_t>( 1313 Ch, "Character in narrow string in user-defined resource")); 1314 writeInt<uint8_t>(Ch); 1315 } 1316 } 1317 1318 return Error::success(); 1319 } 1320 1321 // --- VersionInfoResourceResource helpers. --- // 1322 1323 Error ResourceFileWriter::writeVersionInfoBlock(const VersionInfoBlock &Blk) { 1324 // Output the header if the block has name. 1325 bool OutputHeader = Blk.Name != ""; 1326 uint64_t LengthLoc; 1327 1328 padStream(sizeof(uint32_t)); 1329 if (OutputHeader) { 1330 LengthLoc = writeInt<uint16_t>(0); 1331 writeInt<uint16_t>(0); 1332 writeInt<uint16_t>(1); // true 1333 RETURN_IF_ERROR(writeCString(Blk.Name)); 1334 padStream(sizeof(uint32_t)); 1335 } 1336 1337 for (const std::unique_ptr<VersionInfoStmt> &Item : Blk.Stmts) { 1338 VersionInfoStmt *ItemPtr = Item.get(); 1339 1340 if (auto *BlockPtr = dyn_cast<VersionInfoBlock>(ItemPtr)) { 1341 RETURN_IF_ERROR(writeVersionInfoBlock(*BlockPtr)); 1342 continue; 1343 } 1344 1345 auto *ValuePtr = cast<VersionInfoValue>(ItemPtr); 1346 RETURN_IF_ERROR(writeVersionInfoValue(*ValuePtr)); 1347 } 1348 1349 if (OutputHeader) { 1350 uint64_t CurLoc = tell(); 1351 writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc); 1352 } 1353 1354 return Error::success(); 1355 } 1356 1357 Error ResourceFileWriter::writeVersionInfoValue(const VersionInfoValue &Val) { 1358 // rc has a peculiar algorithm to output VERSIONINFO VALUEs. Each VALUE 1359 // is a mapping from the key (string) to the value (a sequence of ints or 1360 // a sequence of strings). 1361 // 1362 // If integers are to be written: width of each integer written depends on 1363 // whether it's been declared 'long' (it's DWORD then) or not (it's WORD). 1364 // ValueLength defined in structure referenced below is then the total 1365 // number of bytes taken by these integers. 1366 // 1367 // If strings are to be written: characters are always WORDs. 1368 // Moreover, '\0' character is written after the last string, and between 1369 // every two strings separated by comma (if strings are not comma-separated, 1370 // they're simply concatenated). ValueLength is equal to the number of WORDs 1371 // written (that is, half of the bytes written). 1372 // 1373 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms646994.aspx 1374 bool HasStrings = false, HasInts = false; 1375 for (auto &Item : Val.Values) 1376 (Item.isInt() ? HasInts : HasStrings) = true; 1377 1378 assert((HasStrings || HasInts) && "VALUE must have at least one argument"); 1379 if (HasStrings && HasInts) 1380 return createError(Twine("VALUE ") + Val.Key + 1381 " cannot contain both strings and integers"); 1382 1383 padStream(sizeof(uint32_t)); 1384 auto LengthLoc = writeInt<uint16_t>(0); 1385 auto ValLengthLoc = writeInt<uint16_t>(0); 1386 writeInt<uint16_t>(HasStrings); 1387 RETURN_IF_ERROR(writeCString(Val.Key)); 1388 padStream(sizeof(uint32_t)); 1389 1390 auto DataLoc = tell(); 1391 for (size_t Id = 0; Id < Val.Values.size(); ++Id) { 1392 auto &Item = Val.Values[Id]; 1393 if (Item.isInt()) { 1394 auto Value = Item.getInt(); 1395 RETURN_IF_ERROR(checkRCInt(Value, "VERSIONINFO integer value")); 1396 writeRCInt(Value); 1397 continue; 1398 } 1399 1400 bool WriteTerminator = 1401 Id == Val.Values.size() - 1 || Val.HasPrecedingComma[Id + 1]; 1402 RETURN_IF_ERROR(writeCString(Item.getString(), WriteTerminator)); 1403 } 1404 1405 auto CurLoc = tell(); 1406 auto ValueLength = CurLoc - DataLoc; 1407 if (HasStrings) { 1408 assert(ValueLength % 2 == 0); 1409 ValueLength /= 2; 1410 } 1411 writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc); 1412 writeObjectAt(ulittle16_t(ValueLength), ValLengthLoc); 1413 return Error::success(); 1414 } 1415 1416 template <typename Ty> 1417 static Ty getWithDefault(const StringMap<Ty> &Map, StringRef Key, 1418 const Ty &Default) { 1419 auto Iter = Map.find(Key); 1420 if (Iter != Map.end()) 1421 return Iter->getValue(); 1422 return Default; 1423 } 1424 1425 Error ResourceFileWriter::writeVersionInfoBody(const RCResource *Base) { 1426 auto *Res = cast<VersionInfoResource>(Base); 1427 1428 const auto &FixedData = Res->FixedData; 1429 1430 struct /* VS_FIXEDFILEINFO */ { 1431 ulittle32_t Signature = ulittle32_t(0xFEEF04BD); 1432 ulittle32_t StructVersion = ulittle32_t(0x10000); 1433 // It's weird to have most-significant DWORD first on the little-endian 1434 // machines, but let it be this way. 1435 ulittle32_t FileVersionMS; 1436 ulittle32_t FileVersionLS; 1437 ulittle32_t ProductVersionMS; 1438 ulittle32_t ProductVersionLS; 1439 ulittle32_t FileFlagsMask; 1440 ulittle32_t FileFlags; 1441 ulittle32_t FileOS; 1442 ulittle32_t FileType; 1443 ulittle32_t FileSubtype; 1444 // MS implementation seems to always set these fields to 0. 1445 ulittle32_t FileDateMS = ulittle32_t(0); 1446 ulittle32_t FileDateLS = ulittle32_t(0); 1447 } FixedInfo; 1448 1449 // First, VS_VERSIONINFO. 1450 auto LengthLoc = writeInt<uint16_t>(0); 1451 writeInt<uint16_t>(sizeof(FixedInfo)); 1452 writeInt<uint16_t>(0); 1453 cantFail(writeCString("VS_VERSION_INFO")); 1454 padStream(sizeof(uint32_t)); 1455 1456 using VersionInfoFixed = VersionInfoResource::VersionInfoFixed; 1457 auto GetField = [&](VersionInfoFixed::VersionInfoFixedType Type) { 1458 static const SmallVector<uint32_t, 4> DefaultOut{0, 0, 0, 0}; 1459 if (!FixedData.IsTypePresent[(int)Type]) 1460 return DefaultOut; 1461 return FixedData.FixedInfo[(int)Type]; 1462 }; 1463 1464 auto FileVer = GetField(VersionInfoFixed::FtFileVersion); 1465 RETURN_IF_ERROR(checkNumberFits<uint16_t>( 1466 *std::max_element(FileVer.begin(), FileVer.end()), "FILEVERSION fields")); 1467 FixedInfo.FileVersionMS = (FileVer[0] << 16) | FileVer[1]; 1468 FixedInfo.FileVersionLS = (FileVer[2] << 16) | FileVer[3]; 1469 1470 auto ProdVer = GetField(VersionInfoFixed::FtProductVersion); 1471 RETURN_IF_ERROR(checkNumberFits<uint16_t>( 1472 *std::max_element(ProdVer.begin(), ProdVer.end()), 1473 "PRODUCTVERSION fields")); 1474 FixedInfo.ProductVersionMS = (ProdVer[0] << 16) | ProdVer[1]; 1475 FixedInfo.ProductVersionLS = (ProdVer[2] << 16) | ProdVer[3]; 1476 1477 FixedInfo.FileFlagsMask = GetField(VersionInfoFixed::FtFileFlagsMask)[0]; 1478 FixedInfo.FileFlags = GetField(VersionInfoFixed::FtFileFlags)[0]; 1479 FixedInfo.FileOS = GetField(VersionInfoFixed::FtFileOS)[0]; 1480 FixedInfo.FileType = GetField(VersionInfoFixed::FtFileType)[0]; 1481 FixedInfo.FileSubtype = GetField(VersionInfoFixed::FtFileSubtype)[0]; 1482 1483 writeObject(FixedInfo); 1484 padStream(sizeof(uint32_t)); 1485 1486 RETURN_IF_ERROR(writeVersionInfoBlock(Res->MainBlock)); 1487 1488 // FIXME: check overflow? 1489 writeObjectAt(ulittle16_t(tell() - LengthLoc), LengthLoc); 1490 1491 return Error::success(); 1492 } 1493 1494 Expected<std::unique_ptr<MemoryBuffer>> 1495 ResourceFileWriter::loadFile(StringRef File) const { 1496 SmallString<128> Path; 1497 SmallString<128> Cwd; 1498 std::unique_ptr<MemoryBuffer> Result; 1499 1500 // 1. The current working directory. 1501 sys::fs::current_path(Cwd); 1502 Path.assign(Cwd.begin(), Cwd.end()); 1503 sys::path::append(Path, File); 1504 if (sys::fs::exists(Path)) 1505 return errorOrToExpected(MemoryBuffer::getFile(Path, -1, false)); 1506 1507 // 2. The directory of the input resource file, if it is different from the 1508 // current 1509 // working directory. 1510 StringRef InputFileDir = sys::path::parent_path(Params.InputFilePath); 1511 Path.assign(InputFileDir.begin(), InputFileDir.end()); 1512 sys::path::append(Path, File); 1513 if (sys::fs::exists(Path)) 1514 return errorOrToExpected(MemoryBuffer::getFile(Path, -1, false)); 1515 1516 // 3. All of the include directories specified on the command line. 1517 for (StringRef ForceInclude : Params.Include) { 1518 Path.assign(ForceInclude.begin(), ForceInclude.end()); 1519 sys::path::append(Path, File); 1520 if (sys::fs::exists(Path)) 1521 return errorOrToExpected(MemoryBuffer::getFile(Path, -1, false)); 1522 } 1523 1524 if (auto Result = 1525 llvm::sys::Process::FindInEnvPath("INCLUDE", File, Params.NoInclude)) 1526 return errorOrToExpected(MemoryBuffer::getFile(*Result, -1, false)); 1527 1528 return make_error<StringError>("error : file not found : " + Twine(File), 1529 inconvertibleErrorCode()); 1530 } 1531 1532 } // namespace rc 1533 } // namespace llvm 1534