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