brintos

brintos / llvm-project-archived public Read only

0
0
Text · 54.4 KiB · 2c35c5a Raw
1628 lines · cpp
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-exception6//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 23using 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 30namespace llvm {31namespace rc {32 33// Class that employs RAII to save the current FileWriter object state34// and revert to it as soon as we leave the scope. This is useful if resources35// declare their own resource-local statements.36class ContextKeeper {37  ResourceFileWriter *FileWriter;38  ResourceFileWriter::ObjectInfo SavedInfo;39 40public:41  ContextKeeper(ResourceFileWriter *V)42      : FileWriter(V), SavedInfo(V->ObjectData) {}43  ~ContextKeeper() { FileWriter->ObjectData = SavedInfo; }44};45 46static 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 51static 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 61template <typename FitType>62static 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.67template <typename FitType>68static 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 85static Error checkRCInt(RCInt Number, const Twine &FieldName) {86  if (Number.isLong())87    return Error::success();88  return checkNumberFits<uint16_t>(Number, FieldName);89}90 91static 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 97static 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 112static 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 MENU127// titles), string is (predictably) truncated after first 0-byte.128// When outputting a string table, the behavior is equivalent to appending129// '\0\0' at the end of the string, and then stripping the string130// before the first '\0\0' occurrence.131// Finally, when handling strings in user-defined resources, 0-bytes132// aren't stripped, nor do they terminate the string.133 134enum 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.146static 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 in184        // 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 to197      // 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 between238        // 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 past242        // 0xFF invoke UB.243        //244        // FIXME: actually, Microsoft version probably doesn't check this245        // condition and uses their Unicode version of 'isxdigit'. However,246        // there are some hex-digit Unicode character outside of ASCII, and247        // some of these are actually accepted by rc.exe, the notable example248        // being fullwidth forms (U+FF10..U+FF19 etc.) These can be written249        // 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 to252        // 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          else263            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, \"" means316        // 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 with325        // following char. However, rc.exe consumes these characters when326        // 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 363uint64_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 369Error 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 382Error ResourceFileWriter::writeIdentifier(const IntOrString &Ident) {383  return writeIntOrString(Ident);384}385 386Error 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 395void ResourceFileWriter::writeRCInt(RCInt Value) {396  if (Value.isLong())397    writeInt<uint32_t>(Value);398  else399    writeInt<uint16_t>(Value);400}401 402Error 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 414void 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 423Error 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 432Error ResourceFileWriter::visitNullResource(const RCResource *Res) {433  return writeResource(Res, &ResourceFileWriter::writeNullBody);434}435 436Error ResourceFileWriter::visitAcceleratorsResource(const RCResource *Res) {437  return writeResource(Res, &ResourceFileWriter::writeAcceleratorsBody);438}439 440Error ResourceFileWriter::visitBitmapResource(const RCResource *Res) {441  return writeResource(Res, &ResourceFileWriter::writeBitmapBody);442}443 444Error ResourceFileWriter::visitCursorResource(const RCResource *Res) {445  return handleError(visitIconOrCursorResource(Res), Res);446}447 448Error ResourceFileWriter::visitDialogResource(const RCResource *Res) {449  return writeResource(Res, &ResourceFileWriter::writeDialogBody);450}451 452Error ResourceFileWriter::visitIconResource(const RCResource *Res) {453  return handleError(visitIconOrCursorResource(Res), Res);454}455 456Error ResourceFileWriter::visitCaptionStmt(const CaptionStmt *Stmt) {457  ObjectData.Caption = Stmt->Value;458  return Error::success();459}460 461Error ResourceFileWriter::visitClassStmt(const ClassStmt *Stmt) {462  ObjectData.Class = Stmt->Value;463  return Error::success();464}465 466Error ResourceFileWriter::visitHTMLResource(const RCResource *Res) {467  return writeResource(Res, &ResourceFileWriter::writeHTMLBody);468}469 470Error ResourceFileWriter::visitMenuResource(const RCResource *Res) {471  return writeResource(Res, &ResourceFileWriter::writeMenuBody);472}473 474Error ResourceFileWriter::visitMenuExResource(const RCResource *Res) {475  return writeResource(Res, &ResourceFileWriter::writeMenuExBody);476}477 478Error 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 507Error ResourceFileWriter::visitUserDefinedResource(const RCResource *Res) {508  return writeResource(Res, &ResourceFileWriter::writeUserDefinedBody);509}510 511Error ResourceFileWriter::visitVersionInfoResource(const RCResource *Res) {512  return writeResource(Res, &ResourceFileWriter::writeVersionInfoBody);513}514 515Error ResourceFileWriter::visitCharacteristicsStmt(516    const CharacteristicsStmt *Stmt) {517  ObjectData.Characteristics = Stmt->Value;518  return Error::success();519}520 521Error ResourceFileWriter::visitExStyleStmt(const ExStyleStmt *Stmt) {522  ObjectData.ExStyle = Stmt->Value;523  return Error::success();524}525 526Error 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 536Error 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 543Error ResourceFileWriter::visitStyleStmt(const StyleStmt *Stmt) {544  ObjectData.Style = Stmt->Value;545  return Error::success();546}547 548Error ResourceFileWriter::visitVersionStmt(const VersionStmt *Stmt) {549  ObjectData.VersionInfo = Stmt->Value;550  return Error::success();551}552 553Error ResourceFileWriter::visitMenuStmt(const MenuStmt *Stmt) {554  ObjectData.Menu = Stmt->Value;555  return Error::success();556}557 558Error ResourceFileWriter::writeResource(559    const RCResource *Res,560    Error (ResourceFileWriter::*BodyWriter)(const RCResource *)) {561  // We don't know the sizes yet.562  object::WinResHeaderPrefix HeaderPrefix{ulittle32_t(0U), ulittle32_t(0U)};563  uint64_t HeaderLoc = writeObject(HeaderPrefix);564 565  auto ResType = Res->getResourceType();566  RETURN_IF_ERROR(checkIntOrString(ResType, "Resource type"));567  RETURN_IF_ERROR(checkIntOrString(Res->ResName, "Resource ID"));568  RETURN_IF_ERROR(handleError(writeIdentifier(ResType), Res));569  RETURN_IF_ERROR(handleError(writeIdentifier(Res->ResName), Res));570 571  // Apply the resource-local optional statements.572  ContextKeeper RAII(this);573  RETURN_IF_ERROR(handleError(Res->applyStmts(this), Res));574 575  padStream(sizeof(uint32_t));576  object::WinResHeaderSuffix HeaderSuffix{577      ulittle32_t(0), // DataVersion; seems to always be 0578      ulittle16_t(Res->MemoryFlags), ulittle16_t(ObjectData.LanguageInfo),579      ulittle32_t(ObjectData.VersionInfo),580      ulittle32_t(ObjectData.Characteristics)};581  writeObject(HeaderSuffix);582 583  uint64_t DataLoc = tell();584  RETURN_IF_ERROR(handleError((this->*BodyWriter)(Res), Res));585  // RETURN_IF_ERROR(handleError(dumpResource(Ctx)));586 587  // Update the sizes.588  HeaderPrefix.DataSize = tell() - DataLoc;589  HeaderPrefix.HeaderSize = DataLoc - HeaderLoc;590  writeObjectAt(HeaderPrefix, HeaderLoc);591  padStream(sizeof(uint32_t));592 593  return Error::success();594}595 596// --- NullResource helpers. --- //597 598Error ResourceFileWriter::writeNullBody(const RCResource *) {599  return Error::success();600}601 602// --- AcceleratorsResource helpers. --- //603 604Error ResourceFileWriter::writeSingleAccelerator(605    const AcceleratorsResource::Accelerator &Obj, bool IsLastItem) {606  using Accelerator = AcceleratorsResource::Accelerator;607  using Opt = Accelerator::Options;608 609  struct AccelTableEntry {610    ulittle16_t Flags;611    ulittle16_t ANSICode;612    ulittle16_t Id;613    uint16_t Padding;614  } Entry{ulittle16_t(0), ulittle16_t(0), ulittle16_t(0), 0};615 616  bool IsASCII = Obj.Flags & Opt::ASCII, IsVirtKey = Obj.Flags & Opt::VIRTKEY;617 618  // Remove ASCII flags (which doesn't occur in .res files).619  Entry.Flags = Obj.Flags & ~Opt::ASCII;620 621  if (IsLastItem)622    Entry.Flags |= 0x80;623 624  RETURN_IF_ERROR(checkNumberFits<uint16_t>(Obj.Id, "ACCELERATORS entry ID"));625  Entry.Id = ulittle16_t(Obj.Id);626 627  auto createAccError = [&Obj](const char *Msg) {628    return createError("Accelerator ID " + Twine(Obj.Id) + ": " + Msg);629  };630 631  if (IsASCII && IsVirtKey)632    return createAccError("Accelerator can't be both ASCII and VIRTKEY");633 634  if (!IsVirtKey && (Obj.Flags & (Opt::SHIFT | Opt::CONTROL)))635    return createAccError("Can only apply SHIFT or CONTROL to VIRTKEY"636                          " accelerators");637 638  if (Obj.Event.isInt()) {639    if (!IsASCII && !IsVirtKey)640      return createAccError(641          "Accelerator with a numeric event must be either ASCII"642          " or VIRTKEY");643 644    uint32_t EventVal = Obj.Event.getInt();645    RETURN_IF_ERROR(646        checkNumberFits<uint16_t>(EventVal, "Numeric event key ID"));647    Entry.ANSICode = ulittle16_t(EventVal);648    writeObject(Entry);649    return Error::success();650  }651 652  StringRef Str = Obj.Event.getString();653  bool IsWide;654  stripQuotes(Str, IsWide);655 656  if (Str.size() == 0 || Str.size() > 2)657    return createAccError(658        "Accelerator string events should have length 1 or 2");659 660  if (Str[0] == '^') {661    if (Str.size() == 1)662      return createAccError("No character following '^' in accelerator event");663    if (IsVirtKey)664      return createAccError(665          "VIRTKEY accelerator events can't be preceded by '^'");666 667    char Ch = Str[1];668    if (Ch >= 'a' && Ch <= 'z')669      Entry.ANSICode = ulittle16_t(Ch - 'a' + 1);670    else if (Ch >= 'A' && Ch <= 'Z')671      Entry.ANSICode = ulittle16_t(Ch - 'A' + 1);672    else673      return createAccError("Control character accelerator event should be"674                            " alphabetic");675 676    writeObject(Entry);677    return Error::success();678  }679 680  if (Str.size() == 2)681    return createAccError("Event string should be one-character, possibly"682                          " preceded by '^'");683 684  uint8_t EventCh = Str[0];685  // The original tool just warns in this situation. We chose to fail.686  if (IsVirtKey && !isalnum(EventCh))687    return createAccError("Non-alphanumeric characters cannot describe virtual"688                          " keys");689  if (EventCh > 0x7F)690    return createAccError("Non-ASCII description of accelerator");691 692  if (IsVirtKey)693    EventCh = toupper(EventCh);694  Entry.ANSICode = ulittle16_t(EventCh);695  writeObject(Entry);696  return Error::success();697}698 699Error ResourceFileWriter::writeAcceleratorsBody(const RCResource *Base) {700  auto *Res = cast<AcceleratorsResource>(Base);701  size_t AcceleratorId = 0;702  for (auto &Acc : Res->Accelerators) {703    ++AcceleratorId;704    RETURN_IF_ERROR(705        writeSingleAccelerator(Acc, AcceleratorId == Res->Accelerators.size()));706  }707  return Error::success();708}709 710// --- BitmapResource helpers. --- //711 712Error ResourceFileWriter::writeBitmapBody(const RCResource *Base) {713  StringRef Filename = cast<BitmapResource>(Base)->BitmapLoc;714  bool IsLong;715  stripQuotes(Filename, IsLong);716 717  auto File = loadFile(Filename);718  if (!File)719    return File.takeError();720 721  StringRef Buffer = (*File)->getBuffer();722 723  // Skip the 14 byte BITMAPFILEHEADER.724  constexpr size_t BITMAPFILEHEADER_size = 14;725  if (Buffer.size() < BITMAPFILEHEADER_size || Buffer[0] != 'B' ||726      Buffer[1] != 'M')727    return createError("Incorrect bitmap file.");728 729  *FS << Buffer.substr(BITMAPFILEHEADER_size);730  return Error::success();731}732 733// --- CursorResource and IconResource helpers. --- //734 735// ICONRESDIR structure. Describes a single icon in resource group.736//737// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648016.aspx738struct IconResDir {739  uint8_t Width;740  uint8_t Height;741  uint8_t ColorCount;742  uint8_t Reserved;743};744 745// CURSORDIR structure. Describes a single cursor in resource group.746//747// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648011(v=vs.85).aspx748struct CursorDir {749  ulittle16_t Width;750  ulittle16_t Height;751};752 753// RESDIRENTRY structure, stripped from the last item. Stripping made754// for compatibility with RESDIR.755//756// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026(v=vs.85).aspx757struct ResourceDirEntryStart {758  union {759    CursorDir Cursor; // Used in CURSOR resources.760    IconResDir Icon;  // Used in .ico and .cur files, and ICON resources.761  };762  ulittle16_t Planes;   // HotspotX (.cur files but not CURSOR resource).763  ulittle16_t BitCount; // HotspotY (.cur files but not CURSOR resource).764  ulittle32_t Size;765  // ulittle32_t ImageOffset;  // Offset to image data (ICONDIRENTRY only).766  // ulittle16_t IconID;       // Resource icon ID (RESDIR only).767};768 769// BITMAPINFOHEADER structure. Describes basic information about the bitmap770// being read.771//772// Ref: msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx773struct BitmapInfoHeader {774  ulittle32_t Size;775  ulittle32_t Width;776  ulittle32_t Height;777  ulittle16_t Planes;778  ulittle16_t BitCount;779  ulittle32_t Compression;780  ulittle32_t SizeImage;781  ulittle32_t XPelsPerMeter;782  ulittle32_t YPelsPerMeter;783  ulittle32_t ClrUsed;784  ulittle32_t ClrImportant;785};786 787// Group icon directory header. Called ICONDIR in .ico/.cur files and788// NEWHEADER in .res files.789//790// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648023(v=vs.85).aspx791struct GroupIconDir {792  ulittle16_t Reserved; // Always 0.793  ulittle16_t ResType;  // 1 for icons, 2 for cursors.794  ulittle16_t ResCount; // Number of items.795};796 797enum class IconCursorGroupType { Icon, Cursor };798 799class SingleIconCursorResource : public RCResource {800public:801  IconCursorGroupType Type;802  const ResourceDirEntryStart &Header;803  ArrayRef<uint8_t> Image;804 805  SingleIconCursorResource(IconCursorGroupType ResourceType,806                           const ResourceDirEntryStart &HeaderEntry,807                           ArrayRef<uint8_t> ImageData, uint16_t Flags)808      : RCResource(Flags), Type(ResourceType), Header(HeaderEntry),809        Image(ImageData) {}810 811  Twine getResourceTypeName() const override { return "Icon/cursor image"; }812  IntOrString getResourceType() const override {813    return Type == IconCursorGroupType::Icon ? RkSingleIcon : RkSingleCursor;814  }815  ResourceKind getKind() const override { return RkSingleCursorOrIconRes; }816  static bool classof(const RCResource *Res) {817    return Res->getKind() == RkSingleCursorOrIconRes;818  }819};820 821class IconCursorGroupResource : public RCResource {822public:823  IconCursorGroupType Type;824  GroupIconDir Header;825  std::vector<ResourceDirEntryStart> ItemEntries;826 827  IconCursorGroupResource(IconCursorGroupType ResourceType,828                          const GroupIconDir &HeaderData,829                          std::vector<ResourceDirEntryStart> &&Entries)830      : Type(ResourceType), Header(HeaderData),831        ItemEntries(std::move(Entries)) {}832 833  Twine getResourceTypeName() const override { return "Icon/cursor group"; }834  IntOrString getResourceType() const override {835    return Type == IconCursorGroupType::Icon ? RkIconGroup : RkCursorGroup;836  }837  ResourceKind getKind() const override { return RkCursorOrIconGroupRes; }838  static bool classof(const RCResource *Res) {839    return Res->getKind() == RkCursorOrIconGroupRes;840  }841};842 843Error ResourceFileWriter::writeSingleIconOrCursorBody(const RCResource *Base) {844  auto *Res = cast<SingleIconCursorResource>(Base);845  if (Res->Type == IconCursorGroupType::Cursor) {846    // In case of cursors, two WORDS are appended to the beginning847    // of the resource: HotspotX (Planes in RESDIRENTRY),848    // and HotspotY (BitCount).849    //850    // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026.aspx851    //  (Remarks section).852    writeObject(Res->Header.Planes);853    writeObject(Res->Header.BitCount);854  }855 856  writeObject(Res->Image);857  return Error::success();858}859 860Error ResourceFileWriter::writeIconOrCursorGroupBody(const RCResource *Base) {861  auto *Res = cast<IconCursorGroupResource>(Base);862  writeObject(Res->Header);863  for (auto Item : Res->ItemEntries) {864    writeObject(Item);865    writeInt(IconCursorID++);866  }867  return Error::success();868}869 870Error ResourceFileWriter::visitSingleIconOrCursor(const RCResource *Res) {871  return writeResource(Res, &ResourceFileWriter::writeSingleIconOrCursorBody);872}873 874Error ResourceFileWriter::visitIconOrCursorGroup(const RCResource *Res) {875  return writeResource(Res, &ResourceFileWriter::writeIconOrCursorGroupBody);876}877 878Error ResourceFileWriter::visitIconOrCursorResource(const RCResource *Base) {879  IconCursorGroupType Type;880  StringRef FileStr;881  IntOrString ResName = Base->ResName;882 883  if (auto *IconRes = dyn_cast<IconResource>(Base)) {884    FileStr = IconRes->IconLoc;885    Type = IconCursorGroupType::Icon;886  } else {887    auto *CursorRes = cast<CursorResource>(Base);888    FileStr = CursorRes->CursorLoc;889    Type = IconCursorGroupType::Cursor;890  }891 892  bool IsLong;893  stripQuotes(FileStr, IsLong);894  auto File = loadFile(FileStr);895 896  if (!File)897    return File.takeError();898 899  BinaryStreamReader Reader((*File)->getBuffer(), llvm::endianness::little);900 901  // Read the file headers.902  //   - At the beginning, ICONDIR/NEWHEADER header.903  //   - Then, a number of RESDIR headers follow. These contain offsets904  //       to data.905  const GroupIconDir *Header;906 907  RETURN_IF_ERROR(Reader.readObject(Header));908  if (Header->Reserved != 0)909    return createError("Incorrect icon/cursor Reserved field; should be 0.");910  uint16_t NeededType = Type == IconCursorGroupType::Icon ? 1 : 2;911  if (Header->ResType != NeededType)912    return createError("Incorrect icon/cursor ResType field; should be " +913                       Twine(NeededType) + ".");914 915  uint16_t NumItems = Header->ResCount;916 917  // Read single ico/cur headers.918  std::vector<ResourceDirEntryStart> ItemEntries;919  ItemEntries.reserve(NumItems);920  std::vector<uint32_t> ItemOffsets(NumItems);921  for (size_t ID = 0; ID < NumItems; ++ID) {922    const ResourceDirEntryStart *Object;923    RETURN_IF_ERROR(Reader.readObject(Object));924    ItemEntries.push_back(*Object);925    RETURN_IF_ERROR(Reader.readInteger(ItemOffsets[ID]));926  }927 928  // Now write each icon/cursors one by one. At first, all the contents929  // without ICO/CUR header. This is described by SingleIconCursorResource.930  for (size_t ID = 0; ID < NumItems; ++ID) {931    // Load the fragment of file.932    Reader.setOffset(ItemOffsets[ID]);933    ArrayRef<uint8_t> Image;934    RETURN_IF_ERROR(Reader.readArray(Image, ItemEntries[ID].Size));935    SingleIconCursorResource SingleRes(Type, ItemEntries[ID], Image,936                                       Base->MemoryFlags);937    SingleRes.setName(IconCursorID + ID);938    RETURN_IF_ERROR(visitSingleIconOrCursor(&SingleRes));939  }940 941  // Now, write all the headers concatenated into a separate resource.942  for (size_t ID = 0; ID < NumItems; ++ID) {943    // We need to rewrite the cursor headers, and fetch actual values944    // for Planes/BitCount.945    const auto &OldHeader = ItemEntries[ID];946    ResourceDirEntryStart NewHeader = OldHeader;947 948    if (Type == IconCursorGroupType::Cursor) {949      NewHeader.Cursor.Width = OldHeader.Icon.Width;950      // Each cursor in fact stores two bitmaps, one under another.951      // Height provided in cursor definition describes the height of the952      // cursor, whereas the value existing in resource definition describes953      // the height of the bitmap. Therefore, we need to double this height.954      NewHeader.Cursor.Height = OldHeader.Icon.Height * 2;955 956      // Two WORDs were written at the beginning of the resource (hotspot957      // location). This is reflected in Size field.958      NewHeader.Size += 2 * sizeof(uint16_t);959    }960 961    // Now, we actually need to read the bitmap header to find962    // the number of planes and the number of bits per pixel.963    Reader.setOffset(ItemOffsets[ID]);964    const BitmapInfoHeader *BMPHeader;965    RETURN_IF_ERROR(Reader.readObject(BMPHeader));966    if (BMPHeader->Size == sizeof(BitmapInfoHeader)) {967      NewHeader.Planes = BMPHeader->Planes;968      NewHeader.BitCount = BMPHeader->BitCount;969    } else {970      // A PNG .ico file.971      // https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473972      // "The image must be in 32bpp"973      NewHeader.Planes = 1;974      NewHeader.BitCount = 32;975    }976 977    ItemEntries[ID] = NewHeader;978  }979 980  IconCursorGroupResource HeaderRes(Type, *Header, std::move(ItemEntries));981  HeaderRes.setName(ResName);982  if (Base->MemoryFlags & MfPreload) {983    HeaderRes.MemoryFlags |= MfPreload;984    HeaderRes.MemoryFlags &= ~MfPure;985  }986  RETURN_IF_ERROR(visitIconOrCursorGroup(&HeaderRes));987 988  return Error::success();989}990 991// --- DialogResource helpers. --- //992 993Error ResourceFileWriter::writeSingleDialogControl(const Control &Ctl,994                                                   bool IsExtended) {995  // Each control should be aligned to DWORD.996  padStream(sizeof(uint32_t));997 998  auto TypeInfo = Control::SupportedCtls.lookup(Ctl.Type);999  IntWithNotMask CtlStyle(TypeInfo.Style);1000  CtlStyle |= Ctl.Style.value_or(RCInt(0));1001  uint32_t CtlExtStyle = Ctl.ExtStyle.value_or(0);1002 1003  // DIALOG(EX) item header prefix.1004  if (!IsExtended) {1005    struct {1006      ulittle32_t Style;1007      ulittle32_t ExtStyle;1008    } Prefix{ulittle32_t(CtlStyle.getValue()), ulittle32_t(CtlExtStyle)};1009    writeObject(Prefix);1010  } else {1011    struct {1012      ulittle32_t HelpID;1013      ulittle32_t ExtStyle;1014      ulittle32_t Style;1015    } Prefix{ulittle32_t(Ctl.HelpID.value_or(0)), ulittle32_t(CtlExtStyle),1016             ulittle32_t(CtlStyle.getValue())};1017    writeObject(Prefix);1018  }1019 1020  // Common fixed-length part.1021  RETURN_IF_ERROR(checkSignedNumberFits<int16_t>(1022      Ctl.X, "Dialog control x-coordinate", true));1023  RETURN_IF_ERROR(checkSignedNumberFits<int16_t>(1024      Ctl.Y, "Dialog control y-coordinate", true));1025  RETURN_IF_ERROR(1026      checkSignedNumberFits<int16_t>(Ctl.Width, "Dialog control width", false));1027  RETURN_IF_ERROR(checkSignedNumberFits<int16_t>(1028      Ctl.Height, "Dialog control height", false));1029  struct {1030    ulittle16_t X;1031    ulittle16_t Y;1032    ulittle16_t Width;1033    ulittle16_t Height;1034  } Middle{ulittle16_t(Ctl.X), ulittle16_t(Ctl.Y), ulittle16_t(Ctl.Width),1035           ulittle16_t(Ctl.Height)};1036  writeObject(Middle);1037 1038  // ID; it's 16-bit in DIALOG and 32-bit in DIALOGEX.1039  if (!IsExtended) {1040    // It's common to use -1, i.e. UINT32_MAX, for controls one doesn't1041    // want to refer to later.1042    if (Ctl.ID != static_cast<uint32_t>(-1))1043      RETURN_IF_ERROR(checkNumberFits<uint16_t>(1044          Ctl.ID, "Control ID in simple DIALOG resource"));1045    writeInt<uint16_t>(Ctl.ID);1046  } else {1047    writeInt<uint32_t>(Ctl.ID);1048  }1049 1050  // Window class - either 0xFFFF + 16-bit integer or a string.1051  RETURN_IF_ERROR(writeIntOrString(Ctl.Class));1052 1053  // Element caption/reference ID. ID is preceded by 0xFFFF.1054  RETURN_IF_ERROR(checkIntOrString(Ctl.Title, "Control reference ID"));1055  RETURN_IF_ERROR(writeIntOrString(Ctl.Title));1056 1057  // # bytes of extra creation data count. Don't pass any.1058  writeInt<uint16_t>(0);1059 1060  return Error::success();1061}1062 1063Error ResourceFileWriter::writeDialogBody(const RCResource *Base) {1064  auto *Res = cast<DialogResource>(Base);1065 1066  // Default style: WS_POPUP | WS_BORDER | WS_SYSMENU.1067  const uint32_t DefaultStyle = 0x80880000;1068  const uint32_t StyleFontFlag = 0x40;1069  const uint32_t StyleCaptionFlag = 0x00C00000;1070 1071  uint32_t UsedStyle = ObjectData.Style.value_or(DefaultStyle);1072  if (ObjectData.Font)1073    UsedStyle |= StyleFontFlag;1074  else1075    UsedStyle &= ~StyleFontFlag;1076 1077  // Actually, in case of empty (but existent) caption, the examined field1078  // is equal to "\"\"". That's why empty captions are still noticed.1079  if (ObjectData.Caption != "")1080    UsedStyle |= StyleCaptionFlag;1081 1082  const uint16_t DialogExMagic = 0xFFFF;1083  uint32_t ExStyle = ObjectData.ExStyle.value_or(0);1084 1085  // Write DIALOG(EX) header prefix. These are pretty different.1086  if (!Res->IsExtended) {1087    // We cannot let the higher word of DefaultStyle be equal to 0xFFFF.1088    // In such a case, whole object (in .res file) is equivalent to a1089    // DIALOGEX. It might lead to access violation/segmentation fault in1090    // resource readers. For example,1091    //   1 DIALOG 0, 0, 0, 654321092    //   STYLE 0xFFFF0001 {}1093    // would be compiled to a DIALOGEX with 65432 controls.1094    if ((UsedStyle >> 16) == DialogExMagic)1095      return createError("16 higher bits of DIALOG resource style cannot be"1096                         " equal to 0xFFFF");1097 1098    struct {1099      ulittle32_t Style;1100      ulittle32_t ExtStyle;1101    } Prefix{ulittle32_t(UsedStyle),1102             ulittle32_t(ExStyle)};1103 1104    writeObject(Prefix);1105  } else {1106    struct {1107      ulittle16_t Version;1108      ulittle16_t Magic;1109      ulittle32_t HelpID;1110      ulittle32_t ExtStyle;1111      ulittle32_t Style;1112    } Prefix{ulittle16_t(1), ulittle16_t(DialogExMagic),1113             ulittle32_t(Res->HelpID), ulittle32_t(ExStyle), ulittle32_t(UsedStyle)};1114 1115    writeObject(Prefix);1116  }1117 1118  // Now, a common part. First, fixed-length fields.1119  RETURN_IF_ERROR(checkNumberFits<uint16_t>(Res->Controls.size(),1120                                            "Number of dialog controls"));1121  RETURN_IF_ERROR(1122      checkSignedNumberFits<int16_t>(Res->X, "Dialog x-coordinate", true));1123  RETURN_IF_ERROR(1124      checkSignedNumberFits<int16_t>(Res->Y, "Dialog y-coordinate", true));1125  RETURN_IF_ERROR(1126      checkSignedNumberFits<int16_t>(Res->Width, "Dialog width", false));1127  RETURN_IF_ERROR(1128      checkSignedNumberFits<int16_t>(Res->Height, "Dialog height", false));1129  struct {1130    ulittle16_t Count;1131    ulittle16_t PosX;1132    ulittle16_t PosY;1133    ulittle16_t DialogWidth;1134    ulittle16_t DialogHeight;1135  } Middle{ulittle16_t(Res->Controls.size()), ulittle16_t(Res->X),1136           ulittle16_t(Res->Y), ulittle16_t(Res->Width),1137           ulittle16_t(Res->Height)};1138  writeObject(Middle);1139 1140  // MENU field.1141  RETURN_IF_ERROR(writeIntOrString(ObjectData.Menu));1142 1143  // Window CLASS field.1144  RETURN_IF_ERROR(writeIntOrString(ObjectData.Class));1145 1146  // Window title or a single word equal to 0.1147  RETURN_IF_ERROR(writeCString(ObjectData.Caption));1148 1149  // If there *is* a window font declared, output its data.1150  auto &Font = ObjectData.Font;1151  if (Font) {1152    writeInt<uint16_t>(Font->Size);1153    // Additional description occurs only in DIALOGEX.1154    if (Res->IsExtended) {1155      writeInt<uint16_t>(Font->Weight);1156      writeInt<uint8_t>(Font->IsItalic);1157      writeInt<uint8_t>(Font->Charset);1158    }1159    RETURN_IF_ERROR(writeCString(Font->Typeface));1160  }1161 1162  auto handleCtlError = [&](Error &&Err, const Control &Ctl) -> Error {1163    if (!Err)1164      return Error::success();1165    return joinErrors(createError("Error in " + Twine(Ctl.Type) +1166                                  " control  (ID " + Twine(Ctl.ID) + "):"),1167                      std::move(Err));1168  };1169 1170  for (auto &Ctl : Res->Controls)1171    RETURN_IF_ERROR(1172        handleCtlError(writeSingleDialogControl(Ctl, Res->IsExtended), Ctl));1173 1174  return Error::success();1175}1176 1177// --- HTMLResource helpers. --- //1178 1179Error ResourceFileWriter::writeHTMLBody(const RCResource *Base) {1180  return appendFile(cast<HTMLResource>(Base)->HTMLLoc);1181}1182 1183// --- MenuResource helpers. --- //1184 1185Error ResourceFileWriter::writeMenuDefinition(1186    const std::unique_ptr<MenuDefinition> &Def, uint16_t Flags) {1187  // https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-menuitemtemplate1188  assert(Def);1189  const MenuDefinition *DefPtr = Def.get();1190 1191  if (auto *MenuItemPtr = dyn_cast<MenuItem>(DefPtr)) {1192    writeInt<uint16_t>(Flags);1193    // Some resource files use -1, i.e. UINT32_MAX, for empty menu items.1194    if (MenuItemPtr->Id != static_cast<uint32_t>(-1))1195      RETURN_IF_ERROR(1196          checkNumberFits<uint16_t>(MenuItemPtr->Id, "MENUITEM action ID"));1197    writeInt<uint16_t>(MenuItemPtr->Id);1198    RETURN_IF_ERROR(writeCString(MenuItemPtr->Name));1199    return Error::success();1200  }1201 1202  if (isa<MenuSeparator>(DefPtr)) {1203    writeInt<uint16_t>(Flags);1204    writeInt<uint32_t>(0);1205    return Error::success();1206  }1207 1208  auto *PopupPtr = cast<PopupItem>(DefPtr);1209  writeInt<uint16_t>(Flags);1210  RETURN_IF_ERROR(writeCString(PopupPtr->Name));1211  return writeMenuDefinitionList(PopupPtr->SubItems);1212}1213 1214Error ResourceFileWriter::writeMenuExDefinition(1215    const std::unique_ptr<MenuDefinition> &Def, uint16_t Flags) {1216  // https://learn.microsoft.com/en-us/windows/win32/menurc/menuex-template-item1217  assert(Def);1218  const MenuDefinition *DefPtr = Def.get();1219 1220  padStream(sizeof(uint32_t));1221  if (auto *MenuItemPtr = dyn_cast<MenuExItem>(DefPtr)) {1222    writeInt<uint32_t>(MenuItemPtr->Type);1223    writeInt<uint32_t>(MenuItemPtr->State);1224    writeInt<uint32_t>(MenuItemPtr->Id);1225    writeInt<uint16_t>(Flags);1226    padStream(sizeof(uint16_t));1227    RETURN_IF_ERROR(writeCString(MenuItemPtr->Name));1228    return Error::success();1229  }1230 1231  auto *PopupPtr = cast<PopupExItem>(DefPtr);1232  writeInt<uint32_t>(PopupPtr->Type);1233  writeInt<uint32_t>(PopupPtr->State);1234  writeInt<uint32_t>(PopupPtr->Id);1235  writeInt<uint16_t>(Flags);1236  padStream(sizeof(uint16_t));1237  RETURN_IF_ERROR(writeCString(PopupPtr->Name));1238  writeInt<uint32_t>(PopupPtr->HelpId);1239  return writeMenuExDefinitionList(PopupPtr->SubItems);1240}1241 1242Error ResourceFileWriter::writeMenuDefinitionList(1243    const MenuDefinitionList &List) {1244  for (auto &Def : List.Definitions) {1245    uint16_t Flags = Def->getResFlags();1246    // Last element receives an additional 0x80 flag.1247    const uint16_t LastElementFlag = 0x0080;1248    if (&Def == &List.Definitions.back())1249      Flags |= LastElementFlag;1250 1251    RETURN_IF_ERROR(writeMenuDefinition(Def, Flags));1252  }1253  return Error::success();1254}1255 1256Error ResourceFileWriter::writeMenuExDefinitionList(1257    const MenuDefinitionList &List) {1258  for (auto &Def : List.Definitions) {1259    uint16_t Flags = Def->getResFlags();1260    // Last element receives an additional 0x80 flag.1261    const uint16_t LastElementFlag = 0x0080;1262    if (&Def == &List.Definitions.back())1263      Flags |= LastElementFlag;1264 1265    RETURN_IF_ERROR(writeMenuExDefinition(Def, Flags));1266  }1267  return Error::success();1268}1269 1270Error ResourceFileWriter::writeMenuBody(const RCResource *Base) {1271  // At first, MENUHEADER structure. In fact, these are two WORDs equal to 0.1272  // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648018.aspx1273  writeInt<uint32_t>(0);1274 1275  return writeMenuDefinitionList(cast<MenuResource>(Base)->Elements);1276}1277 1278Error ResourceFileWriter::writeMenuExBody(const RCResource *Base) {1279  // At first, MENUEX_TEMPLATE_HEADER structure.1280  // Ref:1281  // https://learn.microsoft.com/en-us/windows/win32/menurc/menuex-template-header1282  writeInt<uint16_t>(1);1283  writeInt<uint16_t>(4);1284  writeInt<uint32_t>(0);1285 1286  return writeMenuExDefinitionList(cast<MenuExResource>(Base)->Elements);1287}1288 1289// --- StringTableResource helpers. --- //1290 1291class BundleResource : public RCResource {1292public:1293  using BundleType = ResourceFileWriter::StringTableInfo::Bundle;1294  BundleType Bundle;1295 1296  BundleResource(const BundleType &StrBundle)1297      : RCResource(StrBundle.MemoryFlags), Bundle(StrBundle) {}1298  IntOrString getResourceType() const override { return 6; }1299 1300  ResourceKind getKind() const override { return RkStringTableBundle; }1301  static bool classof(const RCResource *Res) {1302    return Res->getKind() == RkStringTableBundle;1303  }1304  Twine getResourceTypeName() const override { return "STRINGTABLE"; }1305};1306 1307Error ResourceFileWriter::visitStringTableBundle(const RCResource *Res) {1308  return writeResource(Res, &ResourceFileWriter::writeStringTableBundleBody);1309}1310 1311Error ResourceFileWriter::insertStringIntoBundle(1312    StringTableInfo::Bundle &Bundle, uint16_t StringID,1313    const std::vector<StringRef> &String) {1314  uint16_t StringLoc = StringID & 15;1315  if (Bundle.Data[StringLoc])1316    return createError("Multiple STRINGTABLE strings located under ID " +1317                       Twine(StringID));1318  Bundle.Data[StringLoc] = String;1319  return Error::success();1320}1321 1322Error ResourceFileWriter::writeStringTableBundleBody(const RCResource *Base) {1323  auto *Res = cast<BundleResource>(Base);1324  for (size_t ID = 0; ID < Res->Bundle.Data.size(); ++ID) {1325    // The string format is a tiny bit different here. We1326    // first output the size of the string, and then the string itself1327    // (which is not null-terminated).1328    SmallVector<UTF16, 128> Data;1329    if (Res->Bundle.Data[ID]) {1330      bool IsLongString;1331      for (StringRef S : *Res->Bundle.Data[ID])1332        RETURN_IF_ERROR(processString(S, NullHandlingMethod::CutAtDoubleNull,1333                                      IsLongString, Data, Params.CodePage));1334      if (AppendNull)1335        Data.push_back('\0');1336    }1337    RETURN_IF_ERROR(1338        checkNumberFits<uint16_t>(Data.size(), "STRINGTABLE string size"));1339    writeInt<uint16_t>(Data.size());1340    for (auto Char : Data)1341      writeInt(Char);1342  }1343  return Error::success();1344}1345 1346Error ResourceFileWriter::dumpAllStringTables() {1347  for (auto Key : StringTableData.BundleList) {1348    auto Iter = StringTableData.BundleData.find(Key);1349    assert(Iter != StringTableData.BundleData.end());1350 1351    // For a moment, revert the context info to moment of bundle declaration.1352    ContextKeeper RAII(this);1353    ObjectData = Iter->second.DeclTimeInfo;1354 1355    BundleResource Res(Iter->second);1356    // Bundle #(k+1) contains keys [16k, 16k + 15].1357    Res.setName(Key.first + 1);1358    RETURN_IF_ERROR(visitStringTableBundle(&Res));1359  }1360  return Error::success();1361}1362 1363// --- UserDefinedResource helpers. --- //1364 1365Error ResourceFileWriter::writeUserDefinedBody(const RCResource *Base) {1366  auto *Res = cast<UserDefinedResource>(Base);1367 1368  if (Res->IsFileResource)1369    return appendFile(Res->FileLoc);1370 1371  for (auto &Elem : Res->Contents) {1372    if (Elem.isInt()) {1373      RETURN_IF_ERROR(1374          checkRCInt(Elem.getInt(), "Number in user-defined resource"));1375      writeRCInt(Elem.getInt());1376      continue;1377    }1378 1379    SmallVector<UTF16, 128> ProcessedString;1380    bool IsLongString;1381    RETURN_IF_ERROR(1382        processString(Elem.getString(), NullHandlingMethod::UserResource,1383                      IsLongString, ProcessedString, Params.CodePage));1384 1385    for (auto Ch : ProcessedString) {1386      if (IsLongString) {1387        writeInt(Ch);1388        continue;1389      }1390 1391      RETURN_IF_ERROR(checkNumberFits<uint8_t>(1392          Ch, "Character in narrow string in user-defined resource"));1393      writeInt<uint8_t>(Ch);1394    }1395  }1396 1397  return Error::success();1398}1399 1400// --- VersionInfoResourceResource helpers. --- //1401 1402Error ResourceFileWriter::writeVersionInfoBlock(const VersionInfoBlock &Blk) {1403  // Output the header if the block has name.1404  bool OutputHeader = Blk.Name != "";1405  uint64_t LengthLoc;1406 1407  padStream(sizeof(uint32_t));1408  if (OutputHeader) {1409    LengthLoc = writeInt<uint16_t>(0);1410    writeInt<uint16_t>(0);1411    writeInt<uint16_t>(1); // true1412    RETURN_IF_ERROR(writeCString(Blk.Name));1413    padStream(sizeof(uint32_t));1414  }1415 1416  for (const std::unique_ptr<VersionInfoStmt> &Item : Blk.Stmts) {1417    VersionInfoStmt *ItemPtr = Item.get();1418 1419    if (auto *BlockPtr = dyn_cast<VersionInfoBlock>(ItemPtr)) {1420      RETURN_IF_ERROR(writeVersionInfoBlock(*BlockPtr));1421      continue;1422    }1423 1424    auto *ValuePtr = cast<VersionInfoValue>(ItemPtr);1425    RETURN_IF_ERROR(writeVersionInfoValue(*ValuePtr));1426  }1427 1428  if (OutputHeader) {1429    uint64_t CurLoc = tell();1430    writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc);1431  }1432 1433  return Error::success();1434}1435 1436Error ResourceFileWriter::writeVersionInfoValue(const VersionInfoValue &Val) {1437  // rc has a peculiar algorithm to output VERSIONINFO VALUEs. Each VALUE1438  // is a mapping from the key (string) to the value (a sequence of ints or1439  // a sequence of strings).1440  //1441  // If integers are to be written: width of each integer written depends on1442  // whether it's been declared 'long' (it's DWORD then) or not (it's WORD).1443  // ValueLength defined in structure referenced below is then the total1444  // number of bytes taken by these integers.1445  //1446  // If strings are to be written: characters are always WORDs.1447  // Moreover, '\0' character is written after the last string, and between1448  // every two strings separated by comma (if strings are not comma-separated,1449  // they're simply concatenated). ValueLength is equal to the number of WORDs1450  // written (that is, half of the bytes written).1451  //1452  // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms646994.aspx1453  bool HasStrings = false, HasInts = false;1454  for (auto &Item : Val.Values)1455    (Item.isInt() ? HasInts : HasStrings) = true;1456 1457  assert((HasStrings || HasInts) && "VALUE must have at least one argument");1458  if (HasStrings && HasInts)1459    return createError(Twine("VALUE ") + Val.Key +1460                       " cannot contain both strings and integers");1461 1462  padStream(sizeof(uint32_t));1463  auto LengthLoc = writeInt<uint16_t>(0);1464  auto ValLengthLoc = writeInt<uint16_t>(0);1465  writeInt<uint16_t>(HasStrings);1466  RETURN_IF_ERROR(writeCString(Val.Key));1467  padStream(sizeof(uint32_t));1468 1469  auto DataLoc = tell();1470  for (size_t Id = 0; Id < Val.Values.size(); ++Id) {1471    auto &Item = Val.Values[Id];1472    if (Item.isInt()) {1473      auto Value = Item.getInt();1474      RETURN_IF_ERROR(checkRCInt(Value, "VERSIONINFO integer value"));1475      writeRCInt(Value);1476      continue;1477    }1478 1479    bool WriteTerminator =1480        Id == Val.Values.size() - 1 || Val.HasPrecedingComma[Id + 1];1481    RETURN_IF_ERROR(writeCString(Item.getString(), WriteTerminator));1482  }1483 1484  auto CurLoc = tell();1485  auto ValueLength = CurLoc - DataLoc;1486  if (HasStrings) {1487    assert(ValueLength % 2 == 0);1488    ValueLength /= 2;1489  }1490  writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc);1491  writeObjectAt(ulittle16_t(ValueLength), ValLengthLoc);1492  return Error::success();1493}1494 1495template <typename Ty>1496static Ty getWithDefault(const StringMap<Ty> &Map, StringRef Key,1497                         const Ty &Default) {1498  auto Iter = Map.find(Key);1499  if (Iter != Map.end())1500    return Iter->getValue();1501  return Default;1502}1503 1504Error ResourceFileWriter::writeVersionInfoBody(const RCResource *Base) {1505  auto *Res = cast<VersionInfoResource>(Base);1506 1507  const auto &FixedData = Res->FixedData;1508 1509  struct /* VS_FIXEDFILEINFO */ {1510    ulittle32_t Signature = ulittle32_t(0xFEEF04BD);1511    ulittle32_t StructVersion = ulittle32_t(0x10000);1512    // It's weird to have most-significant DWORD first on the little-endian1513    // machines, but let it be this way.1514    ulittle32_t FileVersionMS;1515    ulittle32_t FileVersionLS;1516    ulittle32_t ProductVersionMS;1517    ulittle32_t ProductVersionLS;1518    ulittle32_t FileFlagsMask;1519    ulittle32_t FileFlags;1520    ulittle32_t FileOS;1521    ulittle32_t FileType;1522    ulittle32_t FileSubtype;1523    // MS implementation seems to always set these fields to 0.1524    ulittle32_t FileDateMS = ulittle32_t(0);1525    ulittle32_t FileDateLS = ulittle32_t(0);1526  } FixedInfo;1527 1528  // First, VS_VERSIONINFO.1529  auto LengthLoc = writeInt<uint16_t>(0);1530  writeInt<uint16_t>(sizeof(FixedInfo));1531  writeInt<uint16_t>(0);1532  cantFail(writeCString("VS_VERSION_INFO"));1533  padStream(sizeof(uint32_t));1534 1535  using VersionInfoFixed = VersionInfoResource::VersionInfoFixed;1536  auto GetField = [&](VersionInfoFixed::VersionInfoFixedType Type) {1537    static const SmallVector<uint32_t, 4> DefaultOut{0, 0, 0, 0};1538    if (!FixedData.IsTypePresent[(int)Type])1539      return DefaultOut;1540    return FixedData.FixedInfo[(int)Type];1541  };1542 1543  auto FileVer = GetField(VersionInfoFixed::FtFileVersion);1544  RETURN_IF_ERROR(checkNumberFits<uint16_t>(*llvm::max_element(FileVer),1545                                            "FILEVERSION fields"));1546  FixedInfo.FileVersionMS = (FileVer[0] << 16) | FileVer[1];1547  FixedInfo.FileVersionLS = (FileVer[2] << 16) | FileVer[3];1548 1549  auto ProdVer = GetField(VersionInfoFixed::FtProductVersion);1550  RETURN_IF_ERROR(checkNumberFits<uint16_t>(*llvm::max_element(ProdVer),1551                                            "PRODUCTVERSION fields"));1552  FixedInfo.ProductVersionMS = (ProdVer[0] << 16) | ProdVer[1];1553  FixedInfo.ProductVersionLS = (ProdVer[2] << 16) | ProdVer[3];1554 1555  FixedInfo.FileFlagsMask = GetField(VersionInfoFixed::FtFileFlagsMask)[0];1556  FixedInfo.FileFlags = GetField(VersionInfoFixed::FtFileFlags)[0];1557  FixedInfo.FileOS = GetField(VersionInfoFixed::FtFileOS)[0];1558  FixedInfo.FileType = GetField(VersionInfoFixed::FtFileType)[0];1559  FixedInfo.FileSubtype = GetField(VersionInfoFixed::FtFileSubtype)[0];1560 1561  writeObject(FixedInfo);1562  padStream(sizeof(uint32_t));1563 1564  RETURN_IF_ERROR(writeVersionInfoBlock(Res->MainBlock));1565 1566  // FIXME: check overflow?1567  writeObjectAt(ulittle16_t(tell() - LengthLoc), LengthLoc);1568 1569  return Error::success();1570}1571 1572Expected<std::unique_ptr<MemoryBuffer>>1573ResourceFileWriter::loadFile(StringRef File) const {1574  SmallString<128> Path;1575  SmallString<128> Cwd;1576 1577  // 0. The file path is absolute or has a root directory, so we shouldn't1578  // try to append it on top of other base directories. (An absolute path1579  // must have a root directory, but e.g. the path "\dir\file" on windows1580  // isn't considered absolute, but it does have a root directory. As long as1581  // sys::path::append doesn't handle appending an absolute path or a path1582  // starting with a root directory on top of a base, we must handle this1583  // case separately at the top. C++17's path::append handles that case1584  // properly though, so if using that to append paths below, this early1585  // exception case could be removed.)1586  if (sys::path::has_root_directory(File))1587    return errorOrToExpected(MemoryBuffer::getFile(1588        File, /*IsText=*/false, /*RequiresNullTerminator=*/false));1589 1590  // 1. The current working directory.1591  sys::fs::current_path(Cwd);1592  Path.assign(Cwd.begin(), Cwd.end());1593  sys::path::append(Path, File);1594  if (sys::fs::exists(Path))1595    return errorOrToExpected(MemoryBuffer::getFile(1596        Path, /*IsText=*/false, /*RequiresNullTerminator=*/false));1597 1598  // 2. The directory of the input resource file, if it is different from the1599  // current working directory.1600  StringRef InputFileDir = sys::path::parent_path(Params.InputFilePath);1601  Path.assign(InputFileDir.begin(), InputFileDir.end());1602  sys::path::append(Path, File);1603  if (sys::fs::exists(Path))1604    return errorOrToExpected(MemoryBuffer::getFile(1605        Path, /*IsText=*/false, /*RequiresNullTerminator=*/false));1606 1607  // 3. All of the include directories specified on the command line.1608  for (StringRef ForceInclude : Params.Include) {1609    Path.assign(ForceInclude.begin(), ForceInclude.end());1610    sys::path::append(Path, File);1611    if (sys::fs::exists(Path))1612      return errorOrToExpected(MemoryBuffer::getFile(1613          Path, /*IsText=*/false, /*RequiresNullTerminator=*/false));1614  }1615 1616  if (!Params.NoInclude) {1617    if (auto Result = llvm::sys::Process::FindInEnvPath("INCLUDE", File))1618      return errorOrToExpected(MemoryBuffer::getFile(1619          *Result, /*IsText=*/false, /*RequiresNullTerminator=*/false));1620  }1621 1622  return make_error<StringError>("error : file not found : " + Twine(File),1623                                 inconvertibleErrorCode());1624}1625 1626} // namespace rc1627} // namespace llvm1628