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
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 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.
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>(Args: Message, Args: std::make_error_code(e: 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(Message: FieldName + " (" + Twine(Number) + ") does not fit in " +
57 Twine(MaxBits) + " bits.",
58 Type: std::errc::value_too_large);
59}
60
61template <typename FitType>
62static Error checkNumberFits(uint32_t Number, const Twine &FieldName) {
63 return checkNumberFits(Number, MaxBits: 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(Message: FieldName + " (" + Twine(SignedNum) +
74 ") does not fit in " + Twine(sizeof(FitType) * 8) +
75 "-bit signed integer type.",
76 Type: std::errc::value_too_large);
77
78 if (!CanBeNegative && SignedNum < 0)
79 return createError(Message: 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>(Number: Value.getInt(), FieldName);
95}
96
97static bool stripQuotes(StringRef &Str, bool &IsLongString) {
98 if (!Str.contains(C: '"'))
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(Prefix: "L");
103 if (IsLongString)
104 Str = Str.drop_front();
105
106 bool StripSuccess = Str.consume_front(Prefix: "\"") && Str.consume_back(Suffix: "\"");
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 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
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(SrcUTF8: Str, DstUTF16&: Chars);
155 } else if (CodePage == CpWin1252) {
156 for (char C : Str)
157 Chars.push_back(Elt: cp1252ToUnicode(C: (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(Message: "Non-ASCII 8-bit codepoint (" + Twine(C) +
163 ") can't be interpreted in the current codepage");
164 Chars.push_back(Elt: (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(c: Ch);
173 }
174 Result.swap(RHS&: Chars);
175 return Error::success();
176 }
177 Result.reserve(N: 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(Message: "Non-8-bit codepoint (" + Twine(Char) +
187 ") can't occur in a user-defined narrow string");
188 }
189 }
190
191 Result.push_back(Elt: 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(Message: "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(Message: "Unable to interpret single byte (" + Twine(Char) +
204 ") as UTF-8");
205 } else if (CodePage == CpWin1252) {
206 Char = cp1252ToUnicode(C: Char);
207 } else {
208 // Unknown/unsupported codepage, only allow ASCII input.
209 if (Char > 0x7F)
210 return createError(Message: "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(Message: "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(c: 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(Message: "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(N: 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(N: 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(Ptr: (const char *)Data.begin(), Size: 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>(Value: Ch);
377 if (WriteTerminator)
378 writeInt<uint16_t>(Value: 0);
379 return Error::success();
380}
381
382Error ResourceFileWriter::writeIdentifier(const IntOrString &Ident) {
383 return writeIntOrString(Data: Ident);
384}
385
386Error ResourceFileWriter::writeIntOrString(const IntOrString &Value) {
387 if (!Value.isInt())
388 return writeCString(Str: Value.getString());
389
390 writeInt<uint16_t>(Value: 0xFFFF);
391 writeInt<uint16_t>(Value: Value.getInt());
392 return Error::success();
393}
394
395void ResourceFileWriter::writeRCInt(RCInt Value) {
396 if (Value.isLong())
397 writeInt<uint32_t>(Value);
398 else
399 writeInt<uint16_t>(Value);
400}
401
402Error ResourceFileWriter::appendFile(StringRef Filename) {
403 bool IsLong;
404 stripQuotes(Str&: Filename, IsLongString&: IsLong);
405
406 auto File = loadFile(File: 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>(Value: 0);
421}
422
423Error ResourceFileWriter::handleError(Error Err, const RCResource *Res) {
424 if (Err)
425 return joinErrors(E1: createError(Message: "Error in " + Res->getResourceTypeName() +
426 " statement (ID " + Twine(Res->ResName) +
427 "): "),
428 E2: std::move(Err));
429 return Error::success();
430}
431
432Error ResourceFileWriter::visitNullResource(const RCResource *Res) {
433 return writeResource(Res, BodyWriter: &ResourceFileWriter::writeNullBody);
434}
435
436Error ResourceFileWriter::visitAcceleratorsResource(const RCResource *Res) {
437 return writeResource(Res, BodyWriter: &ResourceFileWriter::writeAcceleratorsBody);
438}
439
440Error ResourceFileWriter::visitBitmapResource(const RCResource *Res) {
441 return writeResource(Res, BodyWriter: &ResourceFileWriter::writeBitmapBody);
442}
443
444Error ResourceFileWriter::visitCursorResource(const RCResource *Res) {
445 return handleError(Err: visitIconOrCursorResource(Res), Res);
446}
447
448Error ResourceFileWriter::visitDialogResource(const RCResource *Res) {
449 return writeResource(Res, BodyWriter: &ResourceFileWriter::writeDialogBody);
450}
451
452Error ResourceFileWriter::visitIconResource(const RCResource *Res) {
453 return handleError(Err: 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, BodyWriter: &ResourceFileWriter::writeHTMLBody);
468}
469
470Error ResourceFileWriter::visitMenuResource(const RCResource *Res) {
471 return writeResource(Res, BodyWriter: &ResourceFileWriter::writeMenuBody);
472}
473
474Error ResourceFileWriter::visitMenuExResource(const RCResource *Res) {
475 return writeResource(Res, BodyWriter: &ResourceFileWriter::writeMenuExBody);
476}
477
478Error ResourceFileWriter::visitStringTableResource(const RCResource *Base) {
479 const auto *Res = cast<StringTableResource>(Val: 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(x: Key);
490
491 if (Iter == BundleData.end()) {
492 // Need to create a bundle.
493 StringTableData.BundleList.push_back(x: Key);
494 auto EmplaceResult = BundleData.emplace(
495 args&: Key, args: 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, BodyWriter: &ResourceFileWriter::writeUserDefinedBody);
509}
510
511Error ResourceFileWriter::visitVersionInfoResource(const RCResource *Res) {
512 return writeResource(Res, BodyWriter: &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{.Size: Stmt->Size, .Typeface: Stmt->Name, .Weight: Stmt->Weight, .IsItalic: Stmt->Italic,
531 .Charset: Stmt->Charset};
532 ObjectData.Font.emplace(args&: 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{.DataSize: ulittle32_t(0U), .HeaderSize: ulittle32_t(0U)};
563 uint64_t HeaderLoc = writeObject(Value: 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(Length: sizeof(uint32_t));
576 object::WinResHeaderSuffix HeaderSuffix{
577 .DataVersion: ulittle32_t(0), // DataVersion; seems to always be 0
578 .MemoryFlags: ulittle16_t(Res->MemoryFlags), .Language: ulittle16_t(ObjectData.LanguageInfo),
579 .Version: ulittle32_t(ObjectData.VersionInfo),
580 .Characteristics: ulittle32_t(ObjectData.Characteristics)};
581 writeObject(Value: 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(Value: HeaderPrefix, Position: HeaderLoc);
591 padStream(Length: 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{.Flags: ulittle16_t(0), .ANSICode: ulittle16_t(0), .Id: ulittle16_t(0), .Padding: 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(Message: "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::ALT | Opt::SHIFT | Opt::CONTROL)))
635 return createAccError("Can only apply ALT, 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(Value: Entry);
649 return Error::success();
650 }
651
652 StringRef Str = Obj.Event.getString();
653 bool IsWide;
654 stripQuotes(Str, IsLongString&: 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 else
673 return createAccError("Control character accelerator event should be"
674 " alphabetic");
675
676 writeObject(Value: 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(c: EventCh);
694 Entry.ANSICode = ulittle16_t(EventCh);
695 writeObject(Value: Entry);
696 return Error::success();
697}
698
699Error ResourceFileWriter::writeAcceleratorsBody(const RCResource *Base) {
700 auto *Res = cast<AcceleratorsResource>(Val: 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>(Val: Base)->BitmapLoc;
714 bool IsLong;
715 stripQuotes(Str&: Filename, IsLongString&: IsLong);
716
717 auto File = loadFile(File: 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(Message: "Incorrect bitmap file.");
728
729 *FS << Buffer.substr(Start: 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.aspx
738struct 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).aspx
748struct CursorDir {
749 ulittle16_t Width;
750 ulittle16_t Height;
751};
752
753// RESDIRENTRY structure, stripped from the last item. Stripping made
754// for compatibility with RESDIR.
755//
756// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026(v=vs.85).aspx
757struct 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 bitmap
770// being read.
771//
772// Ref: msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
773struct 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 and
788// NEWHEADER in .res files.
789//
790// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648023(v=vs.85).aspx
791struct 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>(Val: Base);
845 if (Res->Type == IconCursorGroupType::Cursor) {
846 // In case of cursors, two WORDS are appended to the beginning
847 // of the resource: HotspotX (Planes in RESDIRENTRY),
848 // and HotspotY (BitCount).
849 //
850 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026.aspx
851 // (Remarks section).
852 writeObject(Value: Res->Header.Planes);
853 writeObject(Value: Res->Header.BitCount);
854 }
855
856 writeObject(Data: Res->Image);
857 return Error::success();
858}
859
860Error ResourceFileWriter::writeIconOrCursorGroupBody(const RCResource *Base) {
861 auto *Res = cast<IconCursorGroupResource>(Val: Base);
862 writeObject(Value: Res->Header);
863 for (auto Item : Res->ItemEntries) {
864 writeObject(Value: Item);
865 writeInt(Value: IconCursorID++);
866 }
867 return Error::success();
868}
869
870Error ResourceFileWriter::visitSingleIconOrCursor(const RCResource *Res) {
871 return writeResource(Res, BodyWriter: &ResourceFileWriter::writeSingleIconOrCursorBody);
872}
873
874Error ResourceFileWriter::visitIconOrCursorGroup(const RCResource *Res) {
875 return writeResource(Res, BodyWriter: &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>(Val: Base)) {
884 FileStr = IconRes->IconLoc;
885 Type = IconCursorGroupType::Icon;
886 } else {
887 auto *CursorRes = cast<CursorResource>(Val: Base);
888 FileStr = CursorRes->CursorLoc;
889 Type = IconCursorGroupType::Cursor;
890 }
891
892 bool IsLong;
893 stripQuotes(Str&: FileStr, IsLongString&: IsLong);
894 auto File = loadFile(File: 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 offsets
904 // to data.
905 const GroupIconDir *Header;
906
907 RETURN_IF_ERROR(Reader.readObject(Header));
908 if (Header->Reserved != 0)
909 return createError(Message: "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(Message: "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(n: 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(x: *Object);
925 RETURN_IF_ERROR(Reader.readInteger(ItemOffsets[ID]));
926 }
927
928 // Now write each icon/cursors one by one. At first, all the contents
929 // 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 values
944 // 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 the
952 // cursor, whereas the value existing in resource definition describes
953 // 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 (hotspot
957 // 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 find
962 // 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=12473
972 // "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(Length: sizeof(uint32_t));
997
998 auto TypeInfo = Control::SupportedCtls.lookup(Key: Ctl.Type);
999 IntWithNotMask CtlStyle(TypeInfo.Style);
1000 CtlStyle |= Ctl.Style.value_or(u: RCInt(0));
1001 uint32_t CtlExtStyle = Ctl.ExtStyle.value_or(u: 0);
1002
1003 // DIALOG(EX) item header prefix.
1004 if (!IsExtended) {
1005 struct {
1006 ulittle32_t Style;
1007 ulittle32_t ExtStyle;
1008 } Prefix{.Style: ulittle32_t(CtlStyle.getValue()), .ExtStyle: ulittle32_t(CtlExtStyle)};
1009 writeObject(Value: Prefix);
1010 } else {
1011 struct {
1012 ulittle32_t HelpID;
1013 ulittle32_t ExtStyle;
1014 ulittle32_t Style;
1015 } Prefix{.HelpID: ulittle32_t(Ctl.HelpID.value_or(u: 0)), .ExtStyle: ulittle32_t(CtlExtStyle),
1016 .Style: ulittle32_t(CtlStyle.getValue())};
1017 writeObject(Value: 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{.X: ulittle16_t(Ctl.X), .Y: ulittle16_t(Ctl.Y), .Width: ulittle16_t(Ctl.Width),
1035 .Height: ulittle16_t(Ctl.Height)};
1036 writeObject(Value: 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't
1041 // 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>(Value: Ctl.ID);
1046 } else {
1047 writeInt<uint32_t>(Value: 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>(Value: 0);
1059
1060 return Error::success();
1061}
1062
1063Error ResourceFileWriter::writeDialogBody(const RCResource *Base) {
1064 auto *Res = cast<DialogResource>(Val: 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(u: DefaultStyle);
1072 if (ObjectData.Font)
1073 UsedStyle |= StyleFontFlag;
1074 else
1075 UsedStyle &= ~StyleFontFlag;
1076
1077 // Actually, in case of empty (but existent) caption, the examined field
1078 // 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(u: 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 a
1089 // DIALOGEX. It might lead to access violation/segmentation fault in
1090 // resource readers. For example,
1091 // 1 DIALOG 0, 0, 0, 65432
1092 // STYLE 0xFFFF0001 {}
1093 // would be compiled to a DIALOGEX with 65432 controls.
1094 if ((UsedStyle >> 16) == DialogExMagic)
1095 return createError(Message: "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{.Style: ulittle32_t(UsedStyle),
1102 .ExtStyle: ulittle32_t(ExStyle)};
1103
1104 writeObject(Value: 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{.Version: ulittle16_t(1), .Magic: ulittle16_t(DialogExMagic),
1113 .HelpID: ulittle32_t(Res->HelpID), .ExtStyle: ulittle32_t(ExStyle), .Style: ulittle32_t(UsedStyle)};
1114
1115 writeObject(Value: 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{.Count: ulittle16_t(Res->Controls.size()), .PosX: ulittle16_t(Res->X),
1136 .PosY: ulittle16_t(Res->Y), .DialogWidth: ulittle16_t(Res->Width),
1137 .DialogHeight: ulittle16_t(Res->Height)};
1138 writeObject(Value: 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>(Value: Font->Size);
1153 // Additional description occurs only in DIALOGEX.
1154 if (Res->IsExtended) {
1155 writeInt<uint16_t>(Value: Font->Weight);
1156 writeInt<uint8_t>(Value: Font->IsItalic);
1157 writeInt<uint8_t>(Value: 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(E1: createError(Message: "Error in " + Twine(Ctl.Type) +
1166 " control (ID " + Twine(Ctl.ID) + "):"),
1167 E2: 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(Filename: cast<HTMLResource>(Val: 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-menuitemtemplate
1188 assert(Def);
1189 const MenuDefinition *DefPtr = Def.get();
1190
1191 if (auto *MenuItemPtr = dyn_cast<MenuItem>(Val: DefPtr)) {
1192 writeInt<uint16_t>(Value: 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>(Value: MenuItemPtr->Id);
1198 RETURN_IF_ERROR(writeCString(MenuItemPtr->Name));
1199 return Error::success();
1200 }
1201
1202 if (isa<MenuSeparator>(Val: DefPtr)) {
1203 writeInt<uint16_t>(Value: Flags);
1204 writeInt<uint32_t>(Value: 0);
1205 return Error::success();
1206 }
1207
1208 auto *PopupPtr = cast<PopupItem>(Val: DefPtr);
1209 writeInt<uint16_t>(Value: Flags);
1210 RETURN_IF_ERROR(writeCString(PopupPtr->Name));
1211 return writeMenuDefinitionList(List: 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-item
1217 assert(Def);
1218 const MenuDefinition *DefPtr = Def.get();
1219
1220 padStream(Length: sizeof(uint32_t));
1221 if (auto *MenuItemPtr = dyn_cast<MenuExItem>(Val: DefPtr)) {
1222 writeInt<uint32_t>(Value: MenuItemPtr->Type);
1223 writeInt<uint32_t>(Value: MenuItemPtr->State);
1224 writeInt<uint32_t>(Value: MenuItemPtr->Id);
1225 writeInt<uint16_t>(Value: Flags);
1226 padStream(Length: sizeof(uint16_t));
1227 RETURN_IF_ERROR(writeCString(MenuItemPtr->Name));
1228 return Error::success();
1229 }
1230
1231 auto *PopupPtr = cast<PopupExItem>(Val: DefPtr);
1232 writeInt<uint32_t>(Value: PopupPtr->Type);
1233 writeInt<uint32_t>(Value: PopupPtr->State);
1234 writeInt<uint32_t>(Value: PopupPtr->Id);
1235 writeInt<uint16_t>(Value: Flags);
1236 padStream(Length: sizeof(uint16_t));
1237 RETURN_IF_ERROR(writeCString(PopupPtr->Name));
1238 writeInt<uint32_t>(Value: PopupPtr->HelpId);
1239 return writeMenuExDefinitionList(List: 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.aspx
1273 writeInt<uint32_t>(Value: 0);
1274
1275 return writeMenuDefinitionList(List: cast<MenuResource>(Val: 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-header
1282 writeInt<uint16_t>(Value: 1);
1283 writeInt<uint16_t>(Value: 4);
1284 writeInt<uint32_t>(Value: 0);
1285
1286 return writeMenuExDefinitionList(List: cast<MenuExResource>(Val: 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, BodyWriter: &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(Message: "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>(Val: Base);
1324 for (size_t ID = 0; ID < Res->Bundle.Data.size(); ++ID) {
1325 // The string format is a tiny bit different here. We
1326 // first output the size of the string, and then the string itself
1327 // (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(Elt: '\0');
1336 }
1337 RETURN_IF_ERROR(
1338 checkNumberFits<uint16_t>(Data.size(), "STRINGTABLE string size"));
1339 writeInt<uint16_t>(Value: Data.size());
1340 for (auto Char : Data)
1341 writeInt(Value: Char);
1342 }
1343 return Error::success();
1344}
1345
1346Error ResourceFileWriter::dumpAllStringTables() {
1347 for (auto Key : StringTableData.BundleList) {
1348 auto Iter = StringTableData.BundleData.find(x: 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>(Val: Base);
1367
1368 if (Res->IsFileResource)
1369 return appendFile(Filename: 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(Value: 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(Value: 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>(Value: 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(Length: sizeof(uint32_t));
1408 if (OutputHeader) {
1409 LengthLoc = writeInt<uint16_t>(Value: 0);
1410 writeInt<uint16_t>(Value: 0);
1411 writeInt<uint16_t>(Value: 1); // true
1412 RETURN_IF_ERROR(writeCString(Blk.Name));
1413 padStream(Length: 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>(Val: ItemPtr)) {
1420 RETURN_IF_ERROR(writeVersionInfoBlock(*BlockPtr));
1421 continue;
1422 }
1423
1424 auto *ValuePtr = cast<VersionInfoValue>(Val: ItemPtr);
1425 RETURN_IF_ERROR(writeVersionInfoValue(*ValuePtr));
1426 }
1427
1428 if (OutputHeader) {
1429 uint64_t CurLoc = tell();
1430 writeObjectAt(Value: ulittle16_t(CurLoc - LengthLoc), Position: 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 VALUE
1438 // is a mapping from the key (string) to the value (a sequence of ints or
1439 // a sequence of strings).
1440 //
1441 // If integers are to be written: width of each integer written depends on
1442 // 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 total
1444 // 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 between
1448 // every two strings separated by comma (if strings are not comma-separated,
1449 // they're simply concatenated). ValueLength is equal to the number of WORDs
1450 // written (that is, half of the bytes written).
1451 //
1452 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms646994.aspx
1453 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(Message: Twine("VALUE ") + Val.Key +
1460 " cannot contain both strings and integers");
1461
1462 padStream(Length: sizeof(uint32_t));
1463 auto LengthLoc = writeInt<uint16_t>(Value: 0);
1464 auto ValLengthLoc = writeInt<uint16_t>(Value: 0);
1465 writeInt<uint16_t>(Value: HasStrings);
1466 RETURN_IF_ERROR(writeCString(Val.Key));
1467 padStream(Length: 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(Value: ulittle16_t(CurLoc - LengthLoc), Position: LengthLoc);
1491 writeObjectAt(Value: ulittle16_t(ValueLength), Position: 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>(Val: 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-endian
1513 // 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>(Value: 0);
1530 writeInt<uint16_t>(Value: sizeof(FixedInfo));
1531 writeInt<uint16_t>(Value: 0);
1532 cantFail(Err: writeCString(Str: "VS_VERSION_INFO"));
1533 padStream(Length: 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(Value: FixedInfo);
1562 padStream(Length: sizeof(uint32_t));
1563
1564 RETURN_IF_ERROR(writeVersionInfoBlock(Res->MainBlock));
1565
1566 // FIXME: check overflow?
1567 writeObjectAt(Value: ulittle16_t(tell() - LengthLoc), Position: 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 std::unique_ptr<MemoryBuffer> Result;
1577
1578 // 0. The file path is absolute or has a root directory, so we shouldn't
1579 // try to append it on top of other base directories. (An absolute path
1580 // must have a root directory, but e.g. the path "\dir\file" on windows
1581 // isn't considered absolute, but it does have a root directory. As long as
1582 // sys::path::append doesn't handle appending an absolute path or a path
1583 // starting with a root directory on top of a base, we must handle this
1584 // case separately at the top. C++17's path::append handles that case
1585 // properly though, so if using that to append paths below, this early
1586 // exception case could be removed.)
1587 if (sys::path::has_root_directory(path: File))
1588 return errorOrToExpected(EO: MemoryBuffer::getFile(
1589 Filename: File, /*IsText=*/false, /*RequiresNullTerminator=*/false));
1590
1591 // 1. The current working directory.
1592 sys::fs::current_path(result&: Cwd);
1593 Path.assign(in_start: Cwd.begin(), in_end: Cwd.end());
1594 sys::path::append(path&: Path, a: File);
1595 if (sys::fs::exists(Path))
1596 return errorOrToExpected(EO: MemoryBuffer::getFile(
1597 Filename: Path, /*IsText=*/false, /*RequiresNullTerminator=*/false));
1598
1599 // 2. The directory of the input resource file, if it is different from the
1600 // current working directory.
1601 StringRef InputFileDir = sys::path::parent_path(path: Params.InputFilePath);
1602 Path.assign(in_start: InputFileDir.begin(), in_end: InputFileDir.end());
1603 sys::path::append(path&: Path, a: File);
1604 if (sys::fs::exists(Path))
1605 return errorOrToExpected(EO: MemoryBuffer::getFile(
1606 Filename: Path, /*IsText=*/false, /*RequiresNullTerminator=*/false));
1607
1608 // 3. All of the include directories specified on the command line.
1609 for (StringRef ForceInclude : Params.Include) {
1610 Path.assign(in_start: ForceInclude.begin(), in_end: ForceInclude.end());
1611 sys::path::append(path&: Path, a: File);
1612 if (sys::fs::exists(Path))
1613 return errorOrToExpected(EO: MemoryBuffer::getFile(
1614 Filename: Path, /*IsText=*/false, /*RequiresNullTerminator=*/false));
1615 }
1616
1617 if (!Params.NoInclude) {
1618 if (auto Result = llvm::sys::Process::FindInEnvPath(EnvName: "INCLUDE", FileName: File))
1619 return errorOrToExpected(EO: MemoryBuffer::getFile(
1620 Filename: *Result, /*IsText=*/false, /*RequiresNullTerminator=*/false));
1621 }
1622
1623 return make_error<StringError>(Args: "error : file not found : " + Twine(File),
1624 Args: inconvertibleErrorCode());
1625}
1626
1627} // namespace rc
1628} // namespace llvm
1629