blob: 8b234ae96d6ad3cec9c3c3155374cd2cda3c2f64 [file] [log] [blame]
Marek Sokolowski8f193432017-09-29 17:14:09 +00001//===-- ResourceFileWriter.cpp --------------------------------*- C++-*-===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===---------------------------------------------------------------------===//
9//
10// This implements the visitor serializing resources to a .res stream.
11//
12//===---------------------------------------------------------------------===//
13
14#include "ResourceFileWriter.h"
15
16#include "llvm/Object/WindowsResource.h"
17#include "llvm/Support/ConvertUTF.h"
18#include "llvm/Support/Endian.h"
19#include "llvm/Support/EndianStream.h"
20
21using namespace llvm::support;
22
23// Take an expression returning llvm::Error and forward the error if it exists.
24#define RETURN_IF_ERROR(Expr) \
25 if (auto Err = (Expr)) \
26 return Err;
27
28namespace llvm {
29namespace rc {
30
Zachary Turner07bc04f2017-10-06 21:26:06 +000031// Class that employs RAII to save the current FileWriter object state
Marek Sokolowski22fccd62017-09-29 19:07:44 +000032// and revert to it as soon as we leave the scope. This is useful if resources
33// declare their own resource-local statements.
34class ContextKeeper {
35 ResourceFileWriter *FileWriter;
36 ResourceFileWriter::ObjectInfo SavedInfo;
37
38public:
39 ContextKeeper(ResourceFileWriter *V)
40 : FileWriter(V), SavedInfo(V->ObjectData) {}
41 ~ContextKeeper() { FileWriter->ObjectData = SavedInfo; }
42};
43
Marek Sokolowski8f193432017-09-29 17:14:09 +000044static Error createError(Twine Message,
45 std::errc Type = std::errc::invalid_argument) {
46 return make_error<StringError>(Message, std::make_error_code(Type));
47}
48
49static Error checkNumberFits(uint32_t Number, size_t MaxBits, Twine FieldName) {
50 assert(1 <= MaxBits && MaxBits <= 32);
51 if (!(Number >> MaxBits))
52 return Error::success();
53 return createError(FieldName + " (" + Twine(Number) + ") does not fit in " +
54 Twine(MaxBits) + " bits.",
55 std::errc::value_too_large);
56}
57
58template <typename FitType>
59static Error checkNumberFits(uint32_t Number, Twine FieldName) {
60 return checkNumberFits(Number, sizeof(FitType) * 8, FieldName);
61}
62
Marek Sokolowski7f7745c2017-09-30 00:38:52 +000063// A similar function for signed integers.
64template <typename FitType>
65static Error checkSignedNumberFits(uint32_t Number, Twine FieldName,
66 bool CanBeNegative) {
67 int32_t SignedNum = Number;
68 if (SignedNum < std::numeric_limits<FitType>::min() ||
69 SignedNum > std::numeric_limits<FitType>::max())
70 return createError(FieldName + " (" + Twine(SignedNum) +
71 ") does not fit in " + Twine(sizeof(FitType) * 8) +
72 "-bit signed integer type.",
73 std::errc::value_too_large);
74
75 if (!CanBeNegative && SignedNum < 0)
76 return createError(FieldName + " (" + Twine(SignedNum) +
77 ") cannot be negative.");
78
79 return Error::success();
80}
81
Zachary Turner07bc04f2017-10-06 21:26:06 +000082static Error checkRCInt(RCInt Number, Twine FieldName) {
83 if (Number.isLong())
84 return Error::success();
85 return checkNumberFits<uint16_t>(Number, FieldName);
86}
87
Marek Sokolowski8f193432017-09-29 17:14:09 +000088static Error checkIntOrString(IntOrString Value, Twine FieldName) {
89 if (!Value.isInt())
90 return Error::success();
91 return checkNumberFits<uint16_t>(Value.getInt(), FieldName);
92}
93
94static bool stripQuotes(StringRef &Str, bool &IsLongString) {
95 if (!Str.contains('"'))
96 return false;
97
98 // Just take the contents of the string, checking if it's been marked long.
99 IsLongString = Str.startswith_lower("L");
100 if (IsLongString)
101 Str = Str.drop_front();
102
103 bool StripSuccess = Str.consume_front("\"") && Str.consume_back("\"");
104 (void)StripSuccess;
105 assert(StripSuccess && "Strings should be enclosed in quotes.");
106 return true;
107}
108
109// Describes a way to handle '\0' characters when processing the string.
110// rc.exe tool sometimes behaves in a weird way in postprocessing.
111// If the string to be output is equivalent to a C-string (e.g. in MENU
112// titles), string is (predictably) truncated after first 0-byte.
113// When outputting a string table, the behavior is equivalent to appending
114// '\0\0' at the end of the string, and then stripping the string
115// before the first '\0\0' occurrence.
116// Finally, when handling strings in user-defined resources, 0-bytes
117// aren't stripped, nor do they terminate the string.
118
119enum class NullHandlingMethod {
120 UserResource, // Don't terminate string on '\0'.
121 CutAtNull, // Terminate string on '\0'.
122 CutAtDoubleNull // Terminate string on '\0\0'; strip final '\0'.
123};
124
Zachary Turnera92eb332017-10-06 22:05:15 +0000125// Parses an identifier or string and returns a processed version of it:
126// * String the string boundary quotes.
Marek Sokolowski8f193432017-09-29 17:14:09 +0000127// * Squash "" to a single ".
128// * Replace the escape sequences with their processed version.
129// For identifiers, this is no-op.
130static Error processString(StringRef Str, NullHandlingMethod NullHandler,
131 bool &IsLongString, SmallVectorImpl<UTF16> &Result) {
Marek Sokolowski8f193432017-09-29 17:14:09 +0000132 bool IsString = stripQuotes(Str, IsLongString);
Zachary Turnera92eb332017-10-06 22:05:15 +0000133 SmallVector<UTF16, 128> Chars;
134 convertUTF8ToUTF16String(Str, Chars);
Marek Sokolowski8f193432017-09-29 17:14:09 +0000135
136 if (!IsString) {
137 // It's an identifier if it's not a string. Make all characters uppercase.
Zachary Turnera92eb332017-10-06 22:05:15 +0000138 for (UTF16 &Ch : Chars) {
Marek Sokolowski8f193432017-09-29 17:14:09 +0000139 assert(Ch <= 0x7F && "We didn't allow identifiers to be non-ASCII");
140 Ch = toupper(Ch);
141 }
Zachary Turnera92eb332017-10-06 22:05:15 +0000142 Result.swap(Chars);
Marek Sokolowski8f193432017-09-29 17:14:09 +0000143 return Error::success();
144 }
Zachary Turnera92eb332017-10-06 22:05:15 +0000145 Result.reserve(Chars.size());
146 size_t Pos = 0;
147
148 auto AddRes = [&Result, NullHandler, IsLongString](UTF16 Char) -> Error {
149 if (!IsLongString) {
150 if (NullHandler == NullHandlingMethod::UserResource) {
151 // Narrow strings in user-defined resources are *not* output in
152 // UTF-16 format.
153 if (Char > 0xFF)
154 return createError("Non-8-bit codepoint (" + Twine(Char) +
155 ") can't occur in a user-defined narrow string");
156
157 } else {
158 // In case of narrow non-user strings, Windows RC converts
159 // [0x80, 0xFF] chars according to the current codepage.
160 // There is no 'codepage' concept settled in every supported platform,
161 // so we should reject such inputs.
162 if (Char > 0x7F && Char <= 0xFF)
163 return createError("Non-ASCII 8-bit codepoint (" + Twine(Char) +
164 ") can't "
165 "occur in a non-Unicode string");
166 }
167 }
168
169 Result.push_back(Char);
170 return Error::success();
171 };
172
173 while (Pos < Chars.size()) {
174 UTF16 CurChar = Chars[Pos];
175 ++Pos;
176
177 // Strip double "".
178 if (CurChar == '"') {
179 if (Pos == Chars.size() || Chars[Pos] != '"')
180 return createError("Expected \"\"");
181 ++Pos;
182 RETURN_IF_ERROR(AddRes('"'));
183 continue;
184 }
185
186 if (CurChar == '\\') {
187 UTF16 TypeChar = Chars[Pos];
188 ++Pos;
189
190 if (TypeChar == 'x' || TypeChar == 'X') {
191 // Read a hex number. Max number of characters to read differs between
192 // narrow and wide strings.
193 UTF16 ReadInt = 0;
194 size_t RemainingChars = IsLongString ? 4 : 2;
195 // We don't want to read non-ASCII hex digits. std:: functions past
196 // 0xFF invoke UB.
197 //
198 // FIXME: actually, Microsoft version probably doesn't check this
199 // condition and uses their Unicode version of 'isxdigit'. However,
200 // there are some hex-digit Unicode character outside of ASCII, and
201 // some of these are actually accepted by rc.exe, the notable example
202 // being fullwidth forms (U+FF10..U+FF19 etc.) These can be written
203 // instead of ASCII digits in \x... escape sequence and get accepted.
204 // However, the resulting hexcodes seem totally unpredictable.
205 // We think it's infeasible to try to reproduce this behavior, nor to
206 // put effort in order to detect it.
207 while (RemainingChars && Pos < Chars.size() && Chars[Pos] < 0x80) {
208 if (!isxdigit(Chars[Pos]))
209 break;
210 char Digit = tolower(Chars[Pos]);
211 ++Pos;
212
213 ReadInt <<= 4;
214 if (isdigit(Digit))
215 ReadInt |= Digit - '0';
216 else
217 ReadInt |= Digit - 'a' + 10;
218
219 --RemainingChars;
220 }
221
222 RETURN_IF_ERROR(AddRes(ReadInt));
223 continue;
224 }
225
226 if (TypeChar >= '0' && TypeChar < '8') {
227 // Read an octal number. Note that we've already read the first digit.
228 UTF16 ReadInt = TypeChar - '0';
229 size_t RemainingChars = IsLongString ? 6 : 2;
230
231 while (RemainingChars && Pos < Chars.size() && Chars[Pos] >= '0' &&
232 Chars[Pos] < '8') {
233 ReadInt <<= 3;
234 ReadInt |= Chars[Pos] - '0';
235 --RemainingChars;
236 ++Pos;
237 }
238
239 RETURN_IF_ERROR(AddRes(ReadInt));
240
241 continue;
242 }
243
244 switch (TypeChar) {
245 case 'A':
246 case 'a':
247 // Windows '\a' translates into '\b' (Backspace).
248 RETURN_IF_ERROR(AddRes('\b'));
249 break;
250
251 case 'n': // Somehow, RC doesn't recognize '\N' and '\R'.
252 RETURN_IF_ERROR(AddRes('\n'));
253 break;
254
255 case 'r':
256 RETURN_IF_ERROR(AddRes('\r'));
257 break;
258
259 case 'T':
260 case 't':
261 RETURN_IF_ERROR(AddRes('\t'));
262 break;
263
264 case '\\':
265 RETURN_IF_ERROR(AddRes('\\'));
266 break;
267
268 case '"':
269 // RC accepts \" only if another " comes afterwards; then, \"" means
270 // a single ".
271 if (Pos == Chars.size() || Chars[Pos] != '"')
272 return createError("Expected \\\"\"");
273 ++Pos;
274 RETURN_IF_ERROR(AddRes('"'));
275 break;
276
277 default:
278 // If TypeChar means nothing, \ is should be output to stdout with
279 // following char. However, rc.exe consumes these characters when
280 // dealing with wide strings.
281 if (!IsLongString) {
282 RETURN_IF_ERROR(AddRes('\\'));
283 RETURN_IF_ERROR(AddRes(TypeChar));
284 }
285 break;
286 }
287
288 continue;
289 }
290
291 // If nothing interesting happens, just output the character.
292 RETURN_IF_ERROR(AddRes(CurChar));
293 }
Marek Sokolowski8f193432017-09-29 17:14:09 +0000294
Zachary Turnerda366692017-10-06 21:30:55 +0000295 switch (NullHandler) {
296 case NullHandlingMethod::CutAtNull:
297 for (size_t Pos = 0; Pos < Result.size(); ++Pos)
298 if (Result[Pos] == '\0')
299 Result.resize(Pos);
300 break;
Marek Sokolowski8f193432017-09-29 17:14:09 +0000301
Zachary Turnerda366692017-10-06 21:30:55 +0000302 case NullHandlingMethod::CutAtDoubleNull:
303 for (size_t Pos = 0; Pos + 1 < Result.size(); ++Pos)
304 if (Result[Pos] == '\0' && Result[Pos + 1] == '\0')
305 Result.resize(Pos);
306 if (Result.size() > 0 && Result.back() == '\0')
307 Result.pop_back();
308 break;
309
310 case NullHandlingMethod::UserResource:
311 break;
312 }
Marek Sokolowski8f193432017-09-29 17:14:09 +0000313
314 return Error::success();
315}
316
317uint64_t ResourceFileWriter::writeObject(const ArrayRef<uint8_t> Data) {
318 uint64_t Result = tell();
319 FS->write((const char *)Data.begin(), Data.size());
320 return Result;
321}
322
323Error ResourceFileWriter::writeCString(StringRef Str, bool WriteTerminator) {
324 SmallVector<UTF16, 128> ProcessedString;
325 bool IsLongString;
326 RETURN_IF_ERROR(processString(Str, NullHandlingMethod::CutAtNull,
327 IsLongString, ProcessedString));
328 for (auto Ch : ProcessedString)
329 writeInt<uint16_t>(Ch);
330 if (WriteTerminator)
331 writeInt<uint16_t>(0);
332 return Error::success();
333}
334
335Error ResourceFileWriter::writeIdentifier(const IntOrString &Ident) {
336 return writeIntOrString(Ident);
337}
338
339Error ResourceFileWriter::writeIntOrString(const IntOrString &Value) {
340 if (!Value.isInt())
341 return writeCString(Value.getString());
342
343 writeInt<uint16_t>(0xFFFF);
344 writeInt<uint16_t>(Value.getInt());
345 return Error::success();
346}
347
Zachary Turner07bc04f2017-10-06 21:26:06 +0000348void ResourceFileWriter::writeRCInt(RCInt Value) {
349 if (Value.isLong())
350 writeObject((uint32_t)Value);
351 else
352 writeObject((uint16_t)Value);
353}
354
Marek Sokolowski8f193432017-09-29 17:14:09 +0000355Error ResourceFileWriter::appendFile(StringRef Filename) {
356 bool IsLong;
357 stripQuotes(Filename, IsLong);
358
359 // Filename path should be relative to the current working directory.
360 // FIXME: docs say so, but reality is more complicated, script
361 // location and include paths must be taken into account.
362 ErrorOr<std::unique_ptr<MemoryBuffer>> File =
363 MemoryBuffer::getFile(Filename, -1, false);
364 if (!File)
365 return make_error<StringError>("Error opening file '" + Filename +
366 "': " + File.getError().message(),
367 File.getError());
368 *FS << (*File)->getBuffer();
369 return Error::success();
370}
371
372void ResourceFileWriter::padStream(uint64_t Length) {
373 assert(Length > 0);
374 uint64_t Location = tell();
375 Location %= Length;
376 uint64_t Pad = (Length - Location) % Length;
377 for (uint64_t i = 0; i < Pad; ++i)
378 writeInt<uint8_t>(0);
379}
380
381Error ResourceFileWriter::handleError(Error &&Err, const RCResource *Res) {
382 if (Err)
383 return joinErrors(createError("Error in " + Res->getResourceTypeName() +
384 " statement (ID " + Twine(Res->ResName) +
385 "): "),
386 std::move(Err));
387 return Error::success();
388}
389
390Error ResourceFileWriter::visitNullResource(const RCResource *Res) {
391 return writeResource(Res, &ResourceFileWriter::writeNullBody);
392}
393
Marek Sokolowski22fccd62017-09-29 19:07:44 +0000394Error ResourceFileWriter::visitAcceleratorsResource(const RCResource *Res) {
395 return writeResource(Res, &ResourceFileWriter::writeAcceleratorsBody);
396}
397
Zachary Turnerc3ab0132017-10-06 21:25:44 +0000398Error ResourceFileWriter::visitCursorResource(const RCResource *Res) {
399 return handleError(visitIconOrCursorResource(Res), Res);
400}
401
Marek Sokolowski7f7745c2017-09-30 00:38:52 +0000402Error ResourceFileWriter::visitDialogResource(const RCResource *Res) {
403 return writeResource(Res, &ResourceFileWriter::writeDialogBody);
404}
405
Zachary Turnerc3ab0132017-10-06 21:25:44 +0000406Error ResourceFileWriter::visitIconResource(const RCResource *Res) {
407 return handleError(visitIconOrCursorResource(Res), Res);
408}
409
Zachary Turner420090a2017-10-06 20:51:20 +0000410Error ResourceFileWriter::visitCaptionStmt(const CaptionStmt *Stmt) {
411 ObjectData.Caption = Stmt->Value;
412 return Error::success();
413}
414
Marek Sokolowski8f193432017-09-29 17:14:09 +0000415Error ResourceFileWriter::visitHTMLResource(const RCResource *Res) {
416 return writeResource(Res, &ResourceFileWriter::writeHTMLBody);
417}
418
Marek Sokolowski42f494d2017-09-29 22:25:05 +0000419Error ResourceFileWriter::visitMenuResource(const RCResource *Res) {
420 return writeResource(Res, &ResourceFileWriter::writeMenuBody);
421}
422
Zachary Turnerda366692017-10-06 21:30:55 +0000423Error ResourceFileWriter::visitStringTableResource(const RCResource *Base) {
424 const auto *Res = cast<StringTableResource>(Base);
425
426 ContextKeeper RAII(this);
427 RETURN_IF_ERROR(Res->applyStmts(this));
428
429 for (auto &String : Res->Table) {
430 RETURN_IF_ERROR(checkNumberFits<uint16_t>(String.first, "String ID"));
431 uint16_t BundleID = String.first >> 4;
432 StringTableInfo::BundleKey Key(BundleID, ObjectData.LanguageInfo);
433 auto &BundleData = StringTableData.BundleData;
434 auto Iter = BundleData.find(Key);
435
436 if (Iter == BundleData.end()) {
437 // Need to create a bundle.
438 StringTableData.BundleList.push_back(Key);
439 auto EmplaceResult =
440 BundleData.emplace(Key, StringTableInfo::Bundle(ObjectData));
441 assert(EmplaceResult.second && "Could not create a bundle");
442 Iter = EmplaceResult.first;
443 }
444
445 RETURN_IF_ERROR(
446 insertStringIntoBundle(Iter->second, String.first, String.second));
447 }
448
449 return Error::success();
450}
451
Zachary Turner9d8b3582017-10-06 21:52:15 +0000452Error ResourceFileWriter::visitUserDefinedResource(const RCResource *Res) {
453 return writeResource(Res, &ResourceFileWriter::writeUserDefinedBody);
454}
455
Zachary Turner07bc04f2017-10-06 21:26:06 +0000456Error ResourceFileWriter::visitVersionInfoResource(const RCResource *Res) {
457 return writeResource(Res, &ResourceFileWriter::writeVersionInfoBody);
458}
459
Marek Sokolowski22fccd62017-09-29 19:07:44 +0000460Error ResourceFileWriter::visitCharacteristicsStmt(
461 const CharacteristicsStmt *Stmt) {
462 ObjectData.Characteristics = Stmt->Value;
463 return Error::success();
464}
465
Zachary Turner420090a2017-10-06 20:51:20 +0000466Error ResourceFileWriter::visitFontStmt(const FontStmt *Stmt) {
467 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Stmt->Size, "Font size"));
468 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Stmt->Weight, "Font weight"));
469 RETURN_IF_ERROR(checkNumberFits<uint8_t>(Stmt->Charset, "Font charset"));
470 ObjectInfo::FontInfo Font{Stmt->Size, Stmt->Name, Stmt->Weight, Stmt->Italic,
471 Stmt->Charset};
472 ObjectData.Font.emplace(Font);
473 return Error::success();
474}
475
Marek Sokolowski8f193432017-09-29 17:14:09 +0000476Error ResourceFileWriter::visitLanguageStmt(const LanguageResource *Stmt) {
477 RETURN_IF_ERROR(checkNumberFits(Stmt->Lang, 10, "Primary language ID"));
478 RETURN_IF_ERROR(checkNumberFits(Stmt->SubLang, 6, "Sublanguage ID"));
479 ObjectData.LanguageInfo = Stmt->Lang | (Stmt->SubLang << 10);
480 return Error::success();
481}
482
Zachary Turner420090a2017-10-06 20:51:20 +0000483Error ResourceFileWriter::visitStyleStmt(const StyleStmt *Stmt) {
484 ObjectData.Style = Stmt->Value;
485 return Error::success();
486}
487
Marek Sokolowski22fccd62017-09-29 19:07:44 +0000488Error ResourceFileWriter::visitVersionStmt(const VersionStmt *Stmt) {
489 ObjectData.VersionInfo = Stmt->Value;
490 return Error::success();
491}
492
Marek Sokolowski8f193432017-09-29 17:14:09 +0000493Error ResourceFileWriter::writeResource(
494 const RCResource *Res,
495 Error (ResourceFileWriter::*BodyWriter)(const RCResource *)) {
496 // We don't know the sizes yet.
497 object::WinResHeaderPrefix HeaderPrefix{ulittle32_t(0U), ulittle32_t(0U)};
498 uint64_t HeaderLoc = writeObject(HeaderPrefix);
499
500 auto ResType = Res->getResourceType();
501 RETURN_IF_ERROR(checkIntOrString(ResType, "Resource type"));
502 RETURN_IF_ERROR(checkIntOrString(Res->ResName, "Resource ID"));
503 RETURN_IF_ERROR(handleError(writeIdentifier(ResType), Res));
504 RETURN_IF_ERROR(handleError(writeIdentifier(Res->ResName), Res));
505
Marek Sokolowski22fccd62017-09-29 19:07:44 +0000506 // Apply the resource-local optional statements.
507 ContextKeeper RAII(this);
508 RETURN_IF_ERROR(handleError(Res->applyStmts(this), Res));
509
Marek Sokolowski8f193432017-09-29 17:14:09 +0000510 padStream(sizeof(uint32_t));
511 object::WinResHeaderSuffix HeaderSuffix{
512 ulittle32_t(0), // DataVersion; seems to always be 0
513 ulittle16_t(Res->getMemoryFlags()), ulittle16_t(ObjectData.LanguageInfo),
Marek Sokolowski22fccd62017-09-29 19:07:44 +0000514 ulittle32_t(ObjectData.VersionInfo),
515 ulittle32_t(ObjectData.Characteristics)};
Marek Sokolowski8f193432017-09-29 17:14:09 +0000516 writeObject(HeaderSuffix);
517
518 uint64_t DataLoc = tell();
519 RETURN_IF_ERROR(handleError((this->*BodyWriter)(Res), Res));
520 // RETURN_IF_ERROR(handleError(dumpResource(Ctx)));
521
522 // Update the sizes.
523 HeaderPrefix.DataSize = tell() - DataLoc;
524 HeaderPrefix.HeaderSize = DataLoc - HeaderLoc;
525 writeObjectAt(HeaderPrefix, HeaderLoc);
526 padStream(sizeof(uint32_t));
527
528 return Error::success();
529}
530
Marek Sokolowski22fccd62017-09-29 19:07:44 +0000531// --- NullResource helpers. --- //
532
Marek Sokolowski8f193432017-09-29 17:14:09 +0000533Error ResourceFileWriter::writeNullBody(const RCResource *) {
534 return Error::success();
535}
536
Marek Sokolowski22fccd62017-09-29 19:07:44 +0000537// --- AcceleratorsResource helpers. --- //
538
539Error ResourceFileWriter::writeSingleAccelerator(
540 const AcceleratorsResource::Accelerator &Obj, bool IsLastItem) {
541 using Accelerator = AcceleratorsResource::Accelerator;
542 using Opt = Accelerator::Options;
543
544 struct AccelTableEntry {
545 ulittle16_t Flags;
546 ulittle16_t ANSICode;
547 ulittle16_t Id;
548 uint16_t Padding;
549 } Entry{ulittle16_t(0), ulittle16_t(0), ulittle16_t(0), 0};
550
551 bool IsASCII = Obj.Flags & Opt::ASCII, IsVirtKey = Obj.Flags & Opt::VIRTKEY;
552
553 // Remove ASCII flags (which doesn't occur in .res files).
554 Entry.Flags = Obj.Flags & ~Opt::ASCII;
555
556 if (IsLastItem)
557 Entry.Flags |= 0x80;
558
559 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Obj.Id, "ACCELERATORS entry ID"));
560 Entry.Id = ulittle16_t(Obj.Id);
561
562 auto createAccError = [&Obj](const char *Msg) {
563 return createError("Accelerator ID " + Twine(Obj.Id) + ": " + Msg);
564 };
565
566 if (IsASCII && IsVirtKey)
567 return createAccError("Accelerator can't be both ASCII and VIRTKEY");
568
569 if (!IsVirtKey && (Obj.Flags & (Opt::ALT | Opt::SHIFT | Opt::CONTROL)))
570 return createAccError("Can only apply ALT, SHIFT or CONTROL to VIRTKEY"
571 " accelerators");
572
573 if (Obj.Event.isInt()) {
574 if (!IsASCII && !IsVirtKey)
575 return createAccError(
576 "Accelerator with a numeric event must be either ASCII"
577 " or VIRTKEY");
578
579 uint32_t EventVal = Obj.Event.getInt();
580 RETURN_IF_ERROR(
581 checkNumberFits<uint16_t>(EventVal, "Numeric event key ID"));
582 Entry.ANSICode = ulittle16_t(EventVal);
583 writeObject(Entry);
584 return Error::success();
585 }
586
587 StringRef Str = Obj.Event.getString();
588 bool IsWide;
589 stripQuotes(Str, IsWide);
590
591 if (Str.size() == 0 || Str.size() > 2)
592 return createAccError(
593 "Accelerator string events should have length 1 or 2");
594
595 if (Str[0] == '^') {
596 if (Str.size() == 1)
597 return createAccError("No character following '^' in accelerator event");
598 if (IsVirtKey)
599 return createAccError(
600 "VIRTKEY accelerator events can't be preceded by '^'");
601
602 char Ch = Str[1];
603 if (Ch >= 'a' && Ch <= 'z')
604 Entry.ANSICode = ulittle16_t(Ch - 'a' + 1);
605 else if (Ch >= 'A' && Ch <= 'Z')
606 Entry.ANSICode = ulittle16_t(Ch - 'A' + 1);
607 else
608 return createAccError("Control character accelerator event should be"
609 " alphabetic");
610
611 writeObject(Entry);
612 return Error::success();
613 }
614
615 if (Str.size() == 2)
616 return createAccError("Event string should be one-character, possibly"
617 " preceded by '^'");
618
619 uint8_t EventCh = Str[0];
620 // The original tool just warns in this situation. We chose to fail.
621 if (IsVirtKey && !isalnum(EventCh))
622 return createAccError("Non-alphanumeric characters cannot describe virtual"
623 " keys");
624 if (EventCh > 0x7F)
625 return createAccError("Non-ASCII description of accelerator");
626
627 if (IsVirtKey)
628 EventCh = toupper(EventCh);
629 Entry.ANSICode = ulittle16_t(EventCh);
630 writeObject(Entry);
631 return Error::success();
632}
633
634Error ResourceFileWriter::writeAcceleratorsBody(const RCResource *Base) {
635 auto *Res = cast<AcceleratorsResource>(Base);
636 size_t AcceleratorId = 0;
637 for (auto &Acc : Res->Accelerators) {
638 ++AcceleratorId;
639 RETURN_IF_ERROR(
640 writeSingleAccelerator(Acc, AcceleratorId == Res->Accelerators.size()));
641 }
642 return Error::success();
643}
644
Zachary Turnerc3ab0132017-10-06 21:25:44 +0000645// --- CursorResource and IconResource helpers. --- //
646
647// ICONRESDIR structure. Describes a single icon in resouce group.
648//
649// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648016.aspx
650struct IconResDir {
651 uint8_t Width;
652 uint8_t Height;
653 uint8_t ColorCount;
654 uint8_t Reserved;
655};
656
657// CURSORDIR structure. Describes a single cursor in resource group.
658//
659// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648011(v=vs.85).aspx
660struct CursorDir {
661 ulittle16_t Width;
662 ulittle16_t Height;
663};
664
665// RESDIRENTRY structure, stripped from the last item. Stripping made
666// for compatibility with RESDIR.
667//
668// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026(v=vs.85).aspx
669struct ResourceDirEntryStart {
670 union {
671 CursorDir Cursor; // Used in CURSOR resources.
672 IconResDir Icon; // Used in .ico and .cur files, and ICON resources.
673 };
674 ulittle16_t Planes; // HotspotX (.cur files but not CURSOR resource).
675 ulittle16_t BitCount; // HotspotY (.cur files but not CURSOR resource).
676 ulittle32_t Size;
677 // ulittle32_t ImageOffset; // Offset to image data (ICONDIRENTRY only).
678 // ulittle16_t IconID; // Resource icon ID (RESDIR only).
679};
680
681// BITMAPINFOHEADER structure. Describes basic information about the bitmap
682// being read.
683//
684// Ref: msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
685struct BitmapInfoHeader {
686 ulittle32_t Size;
687 ulittle32_t Width;
688 ulittle32_t Height;
689 ulittle16_t Planes;
690 ulittle16_t BitCount;
691 ulittle32_t Compression;
692 ulittle32_t SizeImage;
693 ulittle32_t XPelsPerMeter;
694 ulittle32_t YPelsPerMeter;
695 ulittle32_t ClrUsed;
696 ulittle32_t ClrImportant;
697};
698
699// Group icon directory header. Called ICONDIR in .ico/.cur files and
700// NEWHEADER in .res files.
701//
702// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648023(v=vs.85).aspx
703struct GroupIconDir {
704 ulittle16_t Reserved; // Always 0.
705 ulittle16_t ResType; // 1 for icons, 2 for cursors.
706 ulittle16_t ResCount; // Number of items.
707};
708
709enum class IconCursorGroupType { Icon, Cursor };
710
711class SingleIconCursorResource : public RCResource {
712public:
713 IconCursorGroupType Type;
714 const ResourceDirEntryStart &Header;
715 ArrayRef<uint8_t> Image;
716
717 SingleIconCursorResource(IconCursorGroupType ResourceType,
718 const ResourceDirEntryStart &HeaderEntry,
719 ArrayRef<uint8_t> ImageData)
720 : Type(ResourceType), Header(HeaderEntry), Image(ImageData) {}
721
722 Twine getResourceTypeName() const override { return "Icon/cursor image"; }
723 IntOrString getResourceType() const override {
724 return Type == IconCursorGroupType::Icon ? RkSingleIcon : RkSingleCursor;
725 }
726 uint16_t getMemoryFlags() const override {
727 return MfDiscardable | MfMoveable;
728 }
729 ResourceKind getKind() const override { return RkSingleCursorOrIconRes; }
730 static bool classof(const RCResource *Res) {
731 return Res->getKind() == RkSingleCursorOrIconRes;
732 }
733};
734
735class IconCursorGroupResource : public RCResource {
736public:
737 IconCursorGroupType Type;
738 GroupIconDir Header;
739 std::vector<ResourceDirEntryStart> ItemEntries;
740
741 IconCursorGroupResource(IconCursorGroupType ResourceType,
742 const GroupIconDir &HeaderData,
743 std::vector<ResourceDirEntryStart> &&Entries)
744 : Type(ResourceType), Header(HeaderData),
745 ItemEntries(std::move(Entries)) {}
746
747 Twine getResourceTypeName() const override { return "Icon/cursor group"; }
748 IntOrString getResourceType() const override {
749 return Type == IconCursorGroupType::Icon ? RkIconGroup : RkCursorGroup;
750 }
751 ResourceKind getKind() const override { return RkCursorOrIconGroupRes; }
752 static bool classof(const RCResource *Res) {
753 return Res->getKind() == RkCursorOrIconGroupRes;
754 }
755};
756
757Error ResourceFileWriter::writeSingleIconOrCursorBody(const RCResource *Base) {
758 auto *Res = cast<SingleIconCursorResource>(Base);
759 if (Res->Type == IconCursorGroupType::Cursor) {
760 // In case of cursors, two WORDS are appended to the beginning
761 // of the resource: HotspotX (Planes in RESDIRENTRY),
762 // and HotspotY (BitCount).
763 //
764 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026.aspx
765 // (Remarks section).
766 writeObject(Res->Header.Planes);
767 writeObject(Res->Header.BitCount);
768 }
769
770 writeObject(Res->Image);
771 return Error::success();
772}
773
774Error ResourceFileWriter::writeIconOrCursorGroupBody(const RCResource *Base) {
775 auto *Res = cast<IconCursorGroupResource>(Base);
776 writeObject(Res->Header);
777 for (auto Item : Res->ItemEntries) {
778 writeObject(Item);
779 writeObject(ulittle16_t(IconCursorID++));
780 }
781 return Error::success();
782}
783
784Error ResourceFileWriter::visitSingleIconOrCursor(const RCResource *Res) {
785 return writeResource(Res, &ResourceFileWriter::writeSingleIconOrCursorBody);
786}
787
788Error ResourceFileWriter::visitIconOrCursorGroup(const RCResource *Res) {
789 return writeResource(Res, &ResourceFileWriter::writeIconOrCursorGroupBody);
790}
791
792Error ResourceFileWriter::visitIconOrCursorResource(const RCResource *Base) {
793 IconCursorGroupType Type;
794 StringRef FileStr;
795 IntOrString ResName = Base->ResName;
796
797 if (auto *IconRes = dyn_cast<IconResource>(Base)) {
798 FileStr = IconRes->IconLoc;
799 Type = IconCursorGroupType::Icon;
800 } else {
801 auto *CursorRes = dyn_cast<CursorResource>(Base);
802 FileStr = CursorRes->CursorLoc;
803 Type = IconCursorGroupType::Cursor;
804 }
805
806 bool IsLong;
807 stripQuotes(FileStr, IsLong);
808 ErrorOr<std::unique_ptr<MemoryBuffer>> File =
809 MemoryBuffer::getFile(FileStr, -1, false);
810
811 if (!File)
812 return make_error<StringError>(
813 "Error opening " +
814 Twine(Type == IconCursorGroupType::Icon ? "icon" : "cursor") +
815 " '" + FileStr + "': " + File.getError().message(),
816 File.getError());
817
818 BinaryStreamReader Reader((*File)->getBuffer(), support::little);
819
820 // Read the file headers.
821 // - At the beginning, ICONDIR/NEWHEADER header.
822 // - Then, a number of RESDIR headers follow. These contain offsets
823 // to data.
824 const GroupIconDir *Header;
825
826 RETURN_IF_ERROR(Reader.readObject(Header));
827 if (Header->Reserved != 0)
828 return createError("Incorrect icon/cursor Reserved field; should be 0.");
829 uint16_t NeededType = Type == IconCursorGroupType::Icon ? 1 : 2;
830 if (Header->ResType != NeededType)
831 return createError("Incorrect icon/cursor ResType field; should be " +
832 Twine(NeededType) + ".");
833
834 uint16_t NumItems = Header->ResCount;
835
836 // Read single ico/cur headers.
837 std::vector<ResourceDirEntryStart> ItemEntries;
838 ItemEntries.reserve(NumItems);
839 std::vector<uint32_t> ItemOffsets(NumItems);
840 for (size_t ID = 0; ID < NumItems; ++ID) {
841 const ResourceDirEntryStart *Object;
842 RETURN_IF_ERROR(Reader.readObject(Object));
843 ItemEntries.push_back(*Object);
844 RETURN_IF_ERROR(Reader.readInteger(ItemOffsets[ID]));
845 }
846
847 // Now write each icon/cursors one by one. At first, all the contents
848 // without ICO/CUR header. This is described by SingleIconCursorResource.
849 for (size_t ID = 0; ID < NumItems; ++ID) {
850 // Load the fragment of file.
851 Reader.setOffset(ItemOffsets[ID]);
852 ArrayRef<uint8_t> Image;
853 RETURN_IF_ERROR(Reader.readArray(Image, ItemEntries[ID].Size));
854 SingleIconCursorResource SingleRes(Type, ItemEntries[ID], Image);
855 SingleRes.setName(IconCursorID + ID);
856 RETURN_IF_ERROR(visitSingleIconOrCursor(&SingleRes));
857 }
858
859 // Now, write all the headers concatenated into a separate resource.
860 for (size_t ID = 0; ID < NumItems; ++ID) {
861 if (Type == IconCursorGroupType::Icon) {
862 // rc.exe seems to always set NumPlanes to 1. No idea why it happens.
863 ItemEntries[ID].Planes = 1;
864 continue;
865 }
866
867 // We need to rewrite the cursor headers.
868 const auto &OldHeader = ItemEntries[ID];
869 ResourceDirEntryStart NewHeader;
870 NewHeader.Cursor.Width = OldHeader.Icon.Width;
871 // Each cursor in fact stores two bitmaps, one under another.
872 // Height provided in cursor definition describes the height of the
873 // cursor, whereas the value existing in resource definition describes
874 // the height of the bitmap. Therefore, we need to double this height.
875 NewHeader.Cursor.Height = OldHeader.Icon.Height * 2;
876
877 // Now, we actually need to read the bitmap header to find
878 // the number of planes and the number of bits per pixel.
879 Reader.setOffset(ItemOffsets[ID]);
880 const BitmapInfoHeader *BMPHeader;
881 RETURN_IF_ERROR(Reader.readObject(BMPHeader));
882 NewHeader.Planes = BMPHeader->Planes;
883 NewHeader.BitCount = BMPHeader->BitCount;
884
885 // Two WORDs were written at the beginning of the resource (hotspot
886 // location). This is reflected in Size field.
887 NewHeader.Size = OldHeader.Size + 2 * sizeof(uint16_t);
888
889 ItemEntries[ID] = NewHeader;
890 }
891
892 IconCursorGroupResource HeaderRes(Type, *Header, std::move(ItemEntries));
893 HeaderRes.setName(ResName);
894 RETURN_IF_ERROR(visitIconOrCursorGroup(&HeaderRes));
895
896 return Error::success();
897}
898
Marek Sokolowski7f7745c2017-09-30 00:38:52 +0000899// --- DialogResource helpers. --- //
Marek Sokolowski22fccd62017-09-29 19:07:44 +0000900
Marek Sokolowski7f7745c2017-09-30 00:38:52 +0000901Error ResourceFileWriter::writeSingleDialogControl(const Control &Ctl,
902 bool IsExtended) {
903 // Each control should be aligned to DWORD.
904 padStream(sizeof(uint32_t));
905
906 auto TypeInfo = Control::SupportedCtls.lookup(Ctl.Type);
907 uint32_t CtlStyle = TypeInfo.Style | Ctl.Style.getValueOr(0);
908 uint32_t CtlExtStyle = Ctl.ExtStyle.getValueOr(0);
909
910 // DIALOG(EX) item header prefix.
911 if (!IsExtended) {
912 struct {
913 ulittle32_t Style;
914 ulittle32_t ExtStyle;
915 } Prefix{ulittle32_t(CtlStyle), ulittle32_t(CtlExtStyle)};
916 writeObject(Prefix);
917 } else {
918 struct {
919 ulittle32_t HelpID;
920 ulittle32_t ExtStyle;
921 ulittle32_t Style;
922 } Prefix{ulittle32_t(Ctl.HelpID.getValueOr(0)), ulittle32_t(CtlExtStyle),
923 ulittle32_t(CtlStyle)};
924 writeObject(Prefix);
925 }
926
927 // Common fixed-length part.
928 RETURN_IF_ERROR(checkSignedNumberFits<int16_t>(
929 Ctl.X, "Dialog control x-coordinate", true));
930 RETURN_IF_ERROR(checkSignedNumberFits<int16_t>(
931 Ctl.Y, "Dialog control y-coordinate", true));
932 RETURN_IF_ERROR(
933 checkSignedNumberFits<int16_t>(Ctl.Width, "Dialog control width", false));
934 RETURN_IF_ERROR(checkSignedNumberFits<int16_t>(
935 Ctl.Height, "Dialog control height", false));
936 struct {
937 ulittle16_t X;
938 ulittle16_t Y;
939 ulittle16_t Width;
940 ulittle16_t Height;
941 } Middle{ulittle16_t(Ctl.X), ulittle16_t(Ctl.Y), ulittle16_t(Ctl.Width),
942 ulittle16_t(Ctl.Height)};
943 writeObject(Middle);
944
945 // ID; it's 16-bit in DIALOG and 32-bit in DIALOGEX.
946 if (!IsExtended) {
947 RETURN_IF_ERROR(checkNumberFits<uint16_t>(
948 Ctl.ID, "Control ID in simple DIALOG resource"));
949 writeInt<uint16_t>(Ctl.ID);
950 } else {
951 writeInt<uint32_t>(Ctl.ID);
952 }
953
954 // Window class - either 0xFFFF + 16-bit integer or a string.
955 RETURN_IF_ERROR(writeIntOrString(IntOrString(TypeInfo.CtlClass)));
956
957 // Element caption/reference ID. ID is preceded by 0xFFFF.
958 RETURN_IF_ERROR(checkIntOrString(Ctl.Title, "Control reference ID"));
959 RETURN_IF_ERROR(writeIntOrString(Ctl.Title));
960
961 // # bytes of extra creation data count. Don't pass any.
962 writeInt<uint16_t>(0);
963
964 return Error::success();
965}
966
967Error ResourceFileWriter::writeDialogBody(const RCResource *Base) {
968 auto *Res = cast<DialogResource>(Base);
969
970 // Default style: WS_POPUP | WS_BORDER | WS_SYSMENU.
Zachary Turner420090a2017-10-06 20:51:20 +0000971 const uint32_t DefaultStyle = 0x80880000;
972 const uint32_t StyleFontFlag = 0x40;
973 const uint32_t StyleCaptionFlag = 0x00C00000;
974
975 uint32_t UsedStyle = ObjectData.Style.getValueOr(DefaultStyle);
976 if (ObjectData.Font)
977 UsedStyle |= StyleFontFlag;
978 else
979 UsedStyle &= ~StyleFontFlag;
980
981 // Actually, in case of empty (but existent) caption, the examined field
982 // is equal to "\"\"". That's why empty captions are still noticed.
983 if (ObjectData.Caption != "")
984 UsedStyle |= StyleCaptionFlag;
985
986 const uint16_t DialogExMagic = 0xFFFF;
Marek Sokolowski7f7745c2017-09-30 00:38:52 +0000987
988 // Write DIALOG(EX) header prefix. These are pretty different.
989 if (!Res->IsExtended) {
Zachary Turner420090a2017-10-06 20:51:20 +0000990 // We cannot let the higher word of DefaultStyle be equal to 0xFFFF.
991 // In such a case, whole object (in .res file) is equivalent to a
992 // DIALOGEX. It might lead to access violation/segmentation fault in
993 // resource readers. For example,
994 // 1 DIALOG 0, 0, 0, 65432
995 // STYLE 0xFFFF0001 {}
996 // would be compiled to a DIALOGEX with 65432 controls.
997 if ((UsedStyle >> 16) == DialogExMagic)
998 return createError("16 higher bits of DIALOG resource style cannot be"
999 " equal to 0xFFFF");
1000
Marek Sokolowski7f7745c2017-09-30 00:38:52 +00001001 struct {
1002 ulittle32_t Style;
1003 ulittle32_t ExtStyle;
1004 } Prefix{ulittle32_t(UsedStyle),
1005 ulittle32_t(0)}; // As of now, we don't keep EXSTYLE.
1006
1007 writeObject(Prefix);
1008 } else {
Marek Sokolowski7f7745c2017-09-30 00:38:52 +00001009 struct {
1010 ulittle16_t Version;
1011 ulittle16_t Magic;
1012 ulittle32_t HelpID;
1013 ulittle32_t ExtStyle;
1014 ulittle32_t Style;
1015 } Prefix{ulittle16_t(1), ulittle16_t(DialogExMagic),
1016 ulittle32_t(Res->HelpID), ulittle32_t(0), ulittle32_t(UsedStyle)};
1017
1018 writeObject(Prefix);
1019 }
1020
1021 // Now, a common part. First, fixed-length fields.
1022 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Res->Controls.size(),
1023 "Number of dialog controls"));
1024 RETURN_IF_ERROR(
1025 checkSignedNumberFits<int16_t>(Res->X, "Dialog x-coordinate", true));
1026 RETURN_IF_ERROR(
1027 checkSignedNumberFits<int16_t>(Res->Y, "Dialog y-coordinate", true));
1028 RETURN_IF_ERROR(
1029 checkSignedNumberFits<int16_t>(Res->Width, "Dialog width", false));
1030 RETURN_IF_ERROR(
1031 checkSignedNumberFits<int16_t>(Res->Height, "Dialog height", false));
1032 struct {
1033 ulittle16_t Count;
1034 ulittle16_t PosX;
1035 ulittle16_t PosY;
1036 ulittle16_t DialogWidth;
1037 ulittle16_t DialogHeight;
1038 } Middle{ulittle16_t(Res->Controls.size()), ulittle16_t(Res->X),
1039 ulittle16_t(Res->Y), ulittle16_t(Res->Width),
1040 ulittle16_t(Res->Height)};
1041 writeObject(Middle);
1042
1043 // MENU field. As of now, we don't keep them in the state and can peacefully
1044 // think there is no menu attached to the dialog.
1045 writeInt<uint16_t>(0);
1046
1047 // Window CLASS field. Not kept here.
1048 writeInt<uint16_t>(0);
1049
Zachary Turner420090a2017-10-06 20:51:20 +00001050 // Window title or a single word equal to 0.
1051 RETURN_IF_ERROR(writeCString(ObjectData.Caption));
1052
1053 // If there *is* a window font declared, output its data.
1054 auto &Font = ObjectData.Font;
1055 if (Font) {
1056 writeInt<uint16_t>(Font->Size);
1057 // Additional description occurs only in DIALOGEX.
1058 if (Res->IsExtended) {
1059 writeInt<uint16_t>(Font->Weight);
1060 writeInt<uint8_t>(Font->IsItalic);
1061 writeInt<uint8_t>(Font->Charset);
1062 }
1063 RETURN_IF_ERROR(writeCString(Font->Typeface));
1064 }
Marek Sokolowski7f7745c2017-09-30 00:38:52 +00001065
1066 auto handleCtlError = [&](Error &&Err, const Control &Ctl) -> Error {
1067 if (!Err)
1068 return Error::success();
1069 return joinErrors(createError("Error in " + Twine(Ctl.Type) +
1070 " control (ID " + Twine(Ctl.ID) + "):"),
1071 std::move(Err));
1072 };
1073
1074 for (auto &Ctl : Res->Controls)
1075 RETURN_IF_ERROR(
1076 handleCtlError(writeSingleDialogControl(Ctl, Res->IsExtended), Ctl));
1077
1078 return Error::success();
1079}
1080
1081// --- HTMLResource helpers. --- //
Marek Sokolowski22fccd62017-09-29 19:07:44 +00001082
Marek Sokolowski8f193432017-09-29 17:14:09 +00001083Error ResourceFileWriter::writeHTMLBody(const RCResource *Base) {
1084 return appendFile(cast<HTMLResource>(Base)->HTMLLoc);
1085}
1086
Marek Sokolowski42f494d2017-09-29 22:25:05 +00001087// --- MenuResource helpers. --- //
1088
1089Error ResourceFileWriter::writeMenuDefinition(
1090 const std::unique_ptr<MenuDefinition> &Def, uint16_t Flags) {
1091 assert(Def);
1092 const MenuDefinition *DefPtr = Def.get();
1093
1094 if (auto *MenuItemPtr = dyn_cast<MenuItem>(DefPtr)) {
1095 writeInt<uint16_t>(Flags);
1096 RETURN_IF_ERROR(
1097 checkNumberFits<uint16_t>(MenuItemPtr->Id, "MENUITEM action ID"));
1098 writeInt<uint16_t>(MenuItemPtr->Id);
1099 RETURN_IF_ERROR(writeCString(MenuItemPtr->Name));
1100 return Error::success();
1101 }
1102
1103 if (isa<MenuSeparator>(DefPtr)) {
1104 writeInt<uint16_t>(Flags);
1105 writeInt<uint32_t>(0);
1106 return Error::success();
1107 }
1108
1109 auto *PopupPtr = cast<PopupItem>(DefPtr);
1110 writeInt<uint16_t>(Flags);
1111 RETURN_IF_ERROR(writeCString(PopupPtr->Name));
1112 return writeMenuDefinitionList(PopupPtr->SubItems);
1113}
1114
1115Error ResourceFileWriter::writeMenuDefinitionList(
1116 const MenuDefinitionList &List) {
1117 for (auto &Def : List.Definitions) {
1118 uint16_t Flags = Def->getResFlags();
1119 // Last element receives an additional 0x80 flag.
1120 const uint16_t LastElementFlag = 0x0080;
1121 if (&Def == &List.Definitions.back())
1122 Flags |= LastElementFlag;
1123
1124 RETURN_IF_ERROR(writeMenuDefinition(Def, Flags));
1125 }
1126 return Error::success();
1127}
1128
1129Error ResourceFileWriter::writeMenuBody(const RCResource *Base) {
1130 // At first, MENUHEADER structure. In fact, these are two WORDs equal to 0.
1131 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648018.aspx
1132 writeObject<uint32_t>(0);
1133
1134 return writeMenuDefinitionList(cast<MenuResource>(Base)->Elements);
1135}
1136
Zachary Turnerda366692017-10-06 21:30:55 +00001137// --- StringTableResource helpers. --- //
1138
1139class BundleResource : public RCResource {
1140public:
1141 using BundleType = ResourceFileWriter::StringTableInfo::Bundle;
1142 BundleType Bundle;
1143
1144 BundleResource(const BundleType &StrBundle) : Bundle(StrBundle) {}
1145 IntOrString getResourceType() const override { return 6; }
1146
1147 ResourceKind getKind() const override { return RkStringTableBundle; }
1148 static bool classof(const RCResource *Res) {
1149 return Res->getKind() == RkStringTableBundle;
1150 }
1151};
1152
1153Error ResourceFileWriter::visitStringTableBundle(const RCResource *Res) {
1154 return writeResource(Res, &ResourceFileWriter::writeStringTableBundleBody);
1155}
1156
1157Error ResourceFileWriter::insertStringIntoBundle(
1158 StringTableInfo::Bundle &Bundle, uint16_t StringID, StringRef String) {
1159 uint16_t StringLoc = StringID & 15;
1160 if (Bundle.Data[StringLoc])
1161 return createError("Multiple STRINGTABLE strings located under ID " +
1162 Twine(StringID));
1163 Bundle.Data[StringLoc] = String;
1164 return Error::success();
1165}
1166
1167Error ResourceFileWriter::writeStringTableBundleBody(const RCResource *Base) {
1168 auto *Res = cast<BundleResource>(Base);
1169 for (size_t ID = 0; ID < Res->Bundle.Data.size(); ++ID) {
1170 // The string format is a tiny bit different here. We
1171 // first output the size of the string, and then the string itself
1172 // (which is not null-terminated).
1173 bool IsLongString;
1174 SmallVector<UTF16, 128> Data;
1175 RETURN_IF_ERROR(processString(Res->Bundle.Data[ID].getValueOr(StringRef()),
1176 NullHandlingMethod::CutAtDoubleNull,
1177 IsLongString, Data));
1178 if (AppendNull && Res->Bundle.Data[ID])
1179 Data.push_back('\0');
1180 RETURN_IF_ERROR(
1181 checkNumberFits<uint16_t>(Data.size(), "STRINGTABLE string size"));
1182 writeObject(ulittle16_t(Data.size()));
1183 for (auto Char : Data)
1184 writeObject(ulittle16_t(Char));
1185 }
1186 return Error::success();
1187}
1188
1189Error ResourceFileWriter::dumpAllStringTables() {
1190 for (auto Key : StringTableData.BundleList) {
1191 auto Iter = StringTableData.BundleData.find(Key);
1192 assert(Iter != StringTableData.BundleData.end());
1193
1194 // For a moment, revert the context info to moment of bundle declaration.
1195 ContextKeeper RAII(this);
1196 ObjectData = Iter->second.DeclTimeInfo;
1197
1198 BundleResource Res(Iter->second);
1199 // Bundle #(k+1) contains keys [16k, 16k + 15].
1200 Res.setName(Key.first + 1);
1201 RETURN_IF_ERROR(visitStringTableBundle(&Res));
1202 }
1203 return Error::success();
1204}
1205
Zachary Turner9d8b3582017-10-06 21:52:15 +00001206// --- UserDefinedResource helpers. --- //
1207
1208Error ResourceFileWriter::writeUserDefinedBody(const RCResource *Base) {
1209 auto *Res = cast<UserDefinedResource>(Base);
1210
1211 if (Res->IsFileResource)
1212 return appendFile(Res->FileLoc);
1213
1214 for (auto &Elem : Res->Contents) {
1215 if (Elem.isInt()) {
1216 RETURN_IF_ERROR(
1217 checkRCInt(Elem.getInt(), "Number in user-defined resource"));
1218 writeRCInt(Elem.getInt());
1219 continue;
1220 }
1221
1222 SmallVector<UTF16, 128> ProcessedString;
1223 bool IsLongString;
1224 RETURN_IF_ERROR(processString(Elem.getString(),
1225 NullHandlingMethod::UserResource,
1226 IsLongString, ProcessedString));
1227
1228 for (auto Ch : ProcessedString) {
1229 if (IsLongString) {
1230 writeObject(ulittle16_t(Ch));
1231 continue;
1232 }
1233
1234 RETURN_IF_ERROR(checkNumberFits<uint8_t>(
1235 Ch, "Character in narrow string in user-defined resoutce"));
1236 writeObject(uint8_t(Ch));
1237 }
1238 }
1239
1240 return Error::success();
1241}
1242
Zachary Turner07bc04f2017-10-06 21:26:06 +00001243// --- VersionInfoResourceResource helpers. --- //
1244
1245Error ResourceFileWriter::writeVersionInfoBlock(const VersionInfoBlock &Blk) {
1246 // Output the header if the block has name.
1247 bool OutputHeader = Blk.Name != "";
1248 uint64_t LengthLoc;
1249
1250 if (OutputHeader) {
1251 LengthLoc = writeObject<uint16_t>(0);
1252 writeObject<uint16_t>(0);
1253 writeObject<uint16_t>(true);
1254 RETURN_IF_ERROR(writeCString(Blk.Name));
1255 padStream(sizeof(uint32_t));
1256 }
1257
1258 for (const std::unique_ptr<VersionInfoStmt> &Item : Blk.Stmts) {
1259 VersionInfoStmt *ItemPtr = Item.get();
1260
1261 if (auto *BlockPtr = dyn_cast<VersionInfoBlock>(ItemPtr)) {
1262 RETURN_IF_ERROR(writeVersionInfoBlock(*BlockPtr));
1263 continue;
1264 }
1265
1266 auto *ValuePtr = cast<VersionInfoValue>(ItemPtr);
1267 RETURN_IF_ERROR(writeVersionInfoValue(*ValuePtr));
1268 }
1269
1270 if (OutputHeader) {
1271 uint64_t CurLoc = tell();
1272 writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc);
1273 }
1274
1275 padStream(sizeof(uint32_t));
1276 return Error::success();
1277}
1278
1279Error ResourceFileWriter::writeVersionInfoValue(const VersionInfoValue &Val) {
1280 // rc has a peculiar algorithm to output VERSIONINFO VALUEs. Each VALUE
1281 // is a mapping from the key (string) to the value (a sequence of ints or
1282 // a sequence of strings).
1283 //
1284 // If integers are to be written: width of each integer written depends on
1285 // whether it's been declared 'long' (it's DWORD then) or not (it's WORD).
1286 // ValueLength defined in structure referenced below is then the total
1287 // number of bytes taken by these integers.
1288 //
1289 // If strings are to be written: characters are always WORDs.
1290 // Moreover, '\0' character is written after the last string, and between
1291 // every two strings separated by comma (if strings are not comma-separated,
1292 // they're simply concatenated). ValueLength is equal to the number of WORDs
1293 // written (that is, half of the bytes written).
1294 //
1295 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms646994.aspx
1296 bool HasStrings = false, HasInts = false;
1297 for (auto &Item : Val.Values)
1298 (Item.isInt() ? HasInts : HasStrings) = true;
1299
1300 assert((HasStrings || HasInts) && "VALUE must have at least one argument");
1301 if (HasStrings && HasInts)
1302 return createError(Twine("VALUE ") + Val.Key +
1303 " cannot contain both strings and integers");
1304
1305 auto LengthLoc = writeObject<uint16_t>(0);
1306 auto ValLengthLoc = writeObject<uint16_t>(0);
1307 writeObject<uint16_t>(HasStrings);
1308 RETURN_IF_ERROR(writeCString(Val.Key));
1309 padStream(sizeof(uint32_t));
1310
1311 auto DataLoc = tell();
1312 for (size_t Id = 0; Id < Val.Values.size(); ++Id) {
1313 auto &Item = Val.Values[Id];
1314 if (Item.isInt()) {
1315 auto Value = Item.getInt();
1316 RETURN_IF_ERROR(checkRCInt(Value, "VERSIONINFO integer value"));
1317 writeRCInt(Value);
1318 continue;
1319 }
1320
1321 bool WriteTerminator =
1322 Id == Val.Values.size() - 1 || Val.HasPrecedingComma[Id + 1];
1323 RETURN_IF_ERROR(writeCString(Item.getString(), WriteTerminator));
1324 }
1325
1326 auto CurLoc = tell();
1327 auto ValueLength = CurLoc - DataLoc;
1328 if (HasStrings) {
1329 assert(ValueLength % 2 == 0);
1330 ValueLength /= 2;
1331 }
1332 writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc);
1333 writeObjectAt(ulittle16_t(ValueLength), ValLengthLoc);
1334 padStream(sizeof(uint32_t));
1335 return Error::success();
1336}
1337
1338template <typename Ty>
1339static Ty getWithDefault(const StringMap<Ty> &Map, StringRef Key,
1340 const Ty &Default) {
1341 auto Iter = Map.find(Key);
1342 if (Iter != Map.end())
1343 return Iter->getValue();
1344 return Default;
1345}
1346
1347Error ResourceFileWriter::writeVersionInfoBody(const RCResource *Base) {
1348 auto *Res = cast<VersionInfoResource>(Base);
1349
1350 const auto &FixedData = Res->FixedData;
1351
1352 struct /* VS_FIXEDFILEINFO */ {
1353 ulittle32_t Signature = ulittle32_t(0xFEEF04BD);
1354 ulittle32_t StructVersion = ulittle32_t(0x10000);
1355 // It's weird to have most-significant DWORD first on the little-endian
1356 // machines, but let it be this way.
1357 ulittle32_t FileVersionMS;
1358 ulittle32_t FileVersionLS;
1359 ulittle32_t ProductVersionMS;
1360 ulittle32_t ProductVersionLS;
1361 ulittle32_t FileFlagsMask;
1362 ulittle32_t FileFlags;
1363 ulittle32_t FileOS;
1364 ulittle32_t FileType;
1365 ulittle32_t FileSubtype;
1366 // MS implementation seems to always set these fields to 0.
1367 ulittle32_t FileDateMS = ulittle32_t(0);
1368 ulittle32_t FileDateLS = ulittle32_t(0);
1369 } FixedInfo;
1370
1371 // First, VS_VERSIONINFO.
1372 auto LengthLoc = writeObject<uint16_t>(0);
1373 writeObject(ulittle16_t(sizeof(FixedInfo)));
1374 writeObject(ulittle16_t(0));
1375 cantFail(writeCString("VS_VERSION_INFO"));
1376 padStream(sizeof(uint32_t));
1377
1378 using VersionInfoFixed = VersionInfoResource::VersionInfoFixed;
1379 auto GetField = [&](VersionInfoFixed::VersionInfoFixedType Type) {
1380 static const SmallVector<uint32_t, 4> DefaultOut{0, 0, 0, 0};
1381 if (!FixedData.IsTypePresent[(int)Type])
1382 return DefaultOut;
1383 return FixedData.FixedInfo[(int)Type];
1384 };
1385
1386 auto FileVer = GetField(VersionInfoFixed::FtFileVersion);
1387 RETURN_IF_ERROR(checkNumberFits<uint16_t>(
1388 *std::max_element(FileVer.begin(), FileVer.end()), "FILEVERSION fields"));
1389 FixedInfo.FileVersionMS = (FileVer[0] << 16) | FileVer[1];
1390 FixedInfo.FileVersionLS = (FileVer[2] << 16) | FileVer[3];
1391
1392 auto ProdVer = GetField(VersionInfoFixed::FtProductVersion);
1393 RETURN_IF_ERROR(checkNumberFits<uint16_t>(
1394 *std::max_element(ProdVer.begin(), ProdVer.end()),
1395 "PRODUCTVERSION fields"));
1396 FixedInfo.ProductVersionMS = (ProdVer[0] << 16) | ProdVer[1];
1397 FixedInfo.ProductVersionLS = (ProdVer[2] << 16) | ProdVer[3];
1398
1399 FixedInfo.FileFlagsMask = GetField(VersionInfoFixed::FtFileFlagsMask)[0];
1400 FixedInfo.FileFlags = GetField(VersionInfoFixed::FtFileFlags)[0];
1401 FixedInfo.FileOS = GetField(VersionInfoFixed::FtFileOS)[0];
1402 FixedInfo.FileType = GetField(VersionInfoFixed::FtFileType)[0];
1403 FixedInfo.FileSubtype = GetField(VersionInfoFixed::FtFileSubtype)[0];
1404
1405 writeObject(FixedInfo);
1406 padStream(sizeof(uint32_t));
1407
1408 RETURN_IF_ERROR(writeVersionInfoBlock(Res->MainBlock));
1409
1410 // FIXME: check overflow?
1411 writeObjectAt(ulittle16_t(tell() - LengthLoc), LengthLoc);
1412
1413 return Error::success();
1414}
1415
Marek Sokolowski8f193432017-09-29 17:14:09 +00001416} // namespace rc
1417} // namespace llvm