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