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

source code of llvm/tools/llvm-rc/ResourceFileWriter.cpp