blob: 7fe95669083bf65386c68eef013eedc0553c8f2a [file] [log] [blame]
Marek Sokolowskie37621b2017-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"
Zachary Turnere847ec42017-11-17 01:00:35 +000020#include "llvm/Support/MemoryBuffer.h"
Zachary Turnere315d732017-10-11 20:12:09 +000021#include "llvm/Support/Path.h"
22#include "llvm/Support/Process.h"
Marek Sokolowskie37621b2017-09-29 17:14:09 +000023
24using namespace llvm::support;
25
26// Take an expression returning llvm::Error and forward the error if it exists.
27#define RETURN_IF_ERROR(Expr) \
28 if (auto Err = (Expr)) \
29 return Err;
30
31namespace llvm {
32namespace rc {
33
Zachary Turner93bb30d2017-10-06 21:26:06 +000034// Class that employs RAII to save the current FileWriter object state
Marek Sokolowskib121e772017-09-29 19:07:44 +000035// and revert to it as soon as we leave the scope. This is useful if resources
36// declare their own resource-local statements.
37class ContextKeeper {
38 ResourceFileWriter *FileWriter;
39 ResourceFileWriter::ObjectInfo SavedInfo;
40
41public:
42 ContextKeeper(ResourceFileWriter *V)
43 : FileWriter(V), SavedInfo(V->ObjectData) {}
44 ~ContextKeeper() { FileWriter->ObjectData = SavedInfo; }
45};
46
Zachary Turnere315d732017-10-11 20:12:09 +000047static Error createError(const Twine &Message,
Marek Sokolowskie37621b2017-09-29 17:14:09 +000048 std::errc Type = std::errc::invalid_argument) {
49 return make_error<StringError>(Message, std::make_error_code(Type));
50}
51
Zachary Turnere315d732017-10-11 20:12:09 +000052static Error checkNumberFits(uint32_t Number, size_t MaxBits,
53 const Twine &FieldName) {
Marek Sokolowskie37621b2017-09-29 17:14:09 +000054 assert(1 <= MaxBits && MaxBits <= 32);
55 if (!(Number >> MaxBits))
56 return Error::success();
57 return createError(FieldName + " (" + Twine(Number) + ") does not fit in " +
58 Twine(MaxBits) + " bits.",
59 std::errc::value_too_large);
60}
61
62template <typename FitType>
Zachary Turnere315d732017-10-11 20:12:09 +000063static Error checkNumberFits(uint32_t Number, const Twine &FieldName) {
Marek Sokolowskie37621b2017-09-29 17:14:09 +000064 return checkNumberFits(Number, sizeof(FitType) * 8, FieldName);
65}
66
Marek Sokolowskibbf12f32017-09-30 00:38:52 +000067// A similar function for signed integers.
68template <typename FitType>
Zachary Turnere315d732017-10-11 20:12:09 +000069static Error checkSignedNumberFits(uint32_t Number, const Twine &FieldName,
Marek Sokolowskibbf12f32017-09-30 00:38:52 +000070 bool CanBeNegative) {
71 int32_t SignedNum = Number;
72 if (SignedNum < std::numeric_limits<FitType>::min() ||
73 SignedNum > std::numeric_limits<FitType>::max())
74 return createError(FieldName + " (" + Twine(SignedNum) +
75 ") does not fit in " + Twine(sizeof(FitType) * 8) +
76 "-bit signed integer type.",
77 std::errc::value_too_large);
78
79 if (!CanBeNegative && SignedNum < 0)
80 return createError(FieldName + " (" + Twine(SignedNum) +
81 ") cannot be negative.");
82
83 return Error::success();
84}
85
Zachary Turnere315d732017-10-11 20:12:09 +000086static Error checkRCInt(RCInt Number, const Twine &FieldName) {
Zachary Turner93bb30d2017-10-06 21:26:06 +000087 if (Number.isLong())
88 return Error::success();
89 return checkNumberFits<uint16_t>(Number, FieldName);
90}
91
Zachary Turnere315d732017-10-11 20:12:09 +000092static Error checkIntOrString(IntOrString Value, const Twine &FieldName) {
Marek Sokolowskie37621b2017-09-29 17:14:09 +000093 if (!Value.isInt())
94 return Error::success();
95 return checkNumberFits<uint16_t>(Value.getInt(), FieldName);
96}
97
98static bool stripQuotes(StringRef &Str, bool &IsLongString) {
99 if (!Str.contains('"'))
100 return false;
101
102 // Just take the contents of the string, checking if it's been marked long.
103 IsLongString = Str.startswith_lower("L");
104 if (IsLongString)
105 Str = Str.drop_front();
106
107 bool StripSuccess = Str.consume_front("\"") && Str.consume_back("\"");
108 (void)StripSuccess;
109 assert(StripSuccess && "Strings should be enclosed in quotes.");
110 return true;
111}
112
Martin Storsjoe99f5b42018-05-02 19:43:44 +0000113static UTF16 cp1252ToUnicode(unsigned char C) {
114 static const UTF16 Map80[] = {
115 0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021,
116 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f,
117 0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014,
118 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178,
119 };
120 if (C >= 0x80 && C <= 0x9F)
121 return Map80[C - 0x80];
122 return C;
123}
124
Marek Sokolowskie37621b2017-09-29 17:14:09 +0000125// Describes a way to handle '\0' characters when processing the string.
126// rc.exe tool sometimes behaves in a weird way in postprocessing.
127// If the string to be output is equivalent to a C-string (e.g. in MENU
128// titles), string is (predictably) truncated after first 0-byte.
129// When outputting a string table, the behavior is equivalent to appending
130// '\0\0' at the end of the string, and then stripping the string
131// before the first '\0\0' occurrence.
132// Finally, when handling strings in user-defined resources, 0-bytes
133// aren't stripped, nor do they terminate the string.
134
135enum class NullHandlingMethod {
136 UserResource, // Don't terminate string on '\0'.
137 CutAtNull, // Terminate string on '\0'.
138 CutAtDoubleNull // Terminate string on '\0\0'; strip final '\0'.
139};
140
Zachary Turnerb2310462017-10-06 22:05:15 +0000141// Parses an identifier or string and returns a processed version of it:
142// * String the string boundary quotes.
Marek Sokolowskie37621b2017-09-29 17:14:09 +0000143// * 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,
Martin Storsjoe99f5b42018-05-02 19:43:44 +0000147 bool &IsLongString, SmallVectorImpl<UTF16> &Result,
148 int CodePage) {
Marek Sokolowskie37621b2017-09-29 17:14:09 +0000149 bool IsString = stripQuotes(Str, IsLongString);
Zachary Turnerb2310462017-10-06 22:05:15 +0000150 SmallVector<UTF16, 128> Chars;
Martin Storsjoe99f5b42018-05-02 19:43:44 +0000151
152 // Convert the input bytes according to the chosen codepage.
153 if (CodePage == CpUtf8) {
154 convertUTF8ToUTF16String(Str, Chars);
155 } else if (CodePage == CpWin1252) {
156 for (char C : Str)
157 Chars.push_back(cp1252ToUnicode((unsigned char)C));
158 } else {
159 // For other, unknown codepages, only allow plain ASCII input.
160 for (char C : Str) {
161 if ((unsigned char)C > 0x7F)
162 return createError("Non-ASCII 8-bit codepoint (" + Twine(C) +
163 ") can't be interpreted in the current codepage");
164 Chars.push_back((unsigned char)C);
165 }
166 }
Marek Sokolowskie37621b2017-09-29 17:14:09 +0000167
168 if (!IsString) {
169 // It's an identifier if it's not a string. Make all characters uppercase.
Zachary Turnerb2310462017-10-06 22:05:15 +0000170 for (UTF16 &Ch : Chars) {
Marek Sokolowskie37621b2017-09-29 17:14:09 +0000171 assert(Ch <= 0x7F && "We didn't allow identifiers to be non-ASCII");
172 Ch = toupper(Ch);
173 }
Zachary Turnerb2310462017-10-06 22:05:15 +0000174 Result.swap(Chars);
Marek Sokolowskie37621b2017-09-29 17:14:09 +0000175 return Error::success();
176 }
Zachary Turnerb2310462017-10-06 22:05:15 +0000177 Result.reserve(Chars.size());
178 size_t Pos = 0;
179
180 auto AddRes = [&Result, NullHandler, IsLongString](UTF16 Char) -> Error {
181 if (!IsLongString) {
182 if (NullHandler == NullHandlingMethod::UserResource) {
183 // Narrow strings in user-defined resources are *not* output in
184 // UTF-16 format.
185 if (Char > 0xFF)
186 return createError("Non-8-bit codepoint (" + Twine(Char) +
187 ") can't occur in a user-defined narrow string");
Martin Storsjoe99f5b42018-05-02 19:43:44 +0000188 }
189 }
Zachary Turnerb2310462017-10-06 22:05:15 +0000190
Martin Storsjoe99f5b42018-05-02 19:43:44 +0000191 Result.push_back(Char);
192 return Error::success();
193 };
194 auto AddEscapedChar = [AddRes, IsLongString, CodePage](UTF16 Char) -> Error {
195 if (!IsLongString) {
196 // Escaped chars in narrow strings have to be interpreted according to
197 // the chosen code page.
198 if (Char > 0xFF)
199 return createError("Non-8-bit escaped char (" + Twine(Char) +
200 ") can't occur in narrow string");
201 if (CodePage == CpUtf8) {
202 if (Char >= 0x80)
203 return createError("Unable to interpret single byte (" + Twine(Char) +
204 ") as UTF-8");
205 } else if (CodePage == CpWin1252) {
206 Char = cp1252ToUnicode(Char);
Zachary Turnerb2310462017-10-06 22:05:15 +0000207 } else {
Martin Storsjoe99f5b42018-05-02 19:43:44 +0000208 // Unknown/unsupported codepage, only allow ASCII input.
209 if (Char > 0x7F)
Zachary Turnerb2310462017-10-06 22:05:15 +0000210 return createError("Non-ASCII 8-bit codepoint (" + Twine(Char) +
211 ") can't "
212 "occur in a non-Unicode string");
213 }
214 }
215
Martin Storsjoe99f5b42018-05-02 19:43:44 +0000216 return AddRes(Char);
Zachary Turnerb2310462017-10-06 22:05:15 +0000217 };
218
219 while (Pos < Chars.size()) {
220 UTF16 CurChar = Chars[Pos];
221 ++Pos;
222
223 // Strip double "".
224 if (CurChar == '"') {
225 if (Pos == Chars.size() || Chars[Pos] != '"')
226 return createError("Expected \"\"");
227 ++Pos;
228 RETURN_IF_ERROR(AddRes('"'));
229 continue;
230 }
231
232 if (CurChar == '\\') {
233 UTF16 TypeChar = Chars[Pos];
234 ++Pos;
235
236 if (TypeChar == 'x' || TypeChar == 'X') {
237 // Read a hex number. Max number of characters to read differs 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(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
Martin Storsjoe99f5b42018-05-02 19:43:44 +0000268 RETURN_IF_ERROR(AddEscapedChar(ReadInt));
Zachary Turnerb2310462017-10-06 22:05:15 +0000269 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
Martin Storsjoe99f5b42018-05-02 19:43:44 +0000285 RETURN_IF_ERROR(AddEscapedChar(ReadInt));
Zachary Turnerb2310462017-10-06 22:05:15 +0000286
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("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 }
Marek Sokolowskie37621b2017-09-29 17:14:09 +0000340
Zachary Turner84ad96b2017-10-06 21:30:55 +0000341 switch (NullHandler) {
342 case NullHandlingMethod::CutAtNull:
343 for (size_t Pos = 0; Pos < Result.size(); ++Pos)
344 if (Result[Pos] == '\0')
345 Result.resize(Pos);
346 break;
Marek Sokolowskie37621b2017-09-29 17:14:09 +0000347
Zachary Turner84ad96b2017-10-06 21:30:55 +0000348 case NullHandlingMethod::CutAtDoubleNull:
349 for (size_t Pos = 0; Pos + 1 < Result.size(); ++Pos)
350 if (Result[Pos] == '\0' && Result[Pos + 1] == '\0')
351 Result.resize(Pos);
352 if (Result.size() > 0 && Result.back() == '\0')
353 Result.pop_back();
354 break;
355
356 case NullHandlingMethod::UserResource:
357 break;
358 }
Marek Sokolowskie37621b2017-09-29 17:14:09 +0000359
360 return Error::success();
361}
362
363uint64_t ResourceFileWriter::writeObject(const ArrayRef<uint8_t> Data) {
364 uint64_t Result = tell();
365 FS->write((const char *)Data.begin(), Data.size());
366 return Result;
367}
368
369Error ResourceFileWriter::writeCString(StringRef Str, bool WriteTerminator) {
370 SmallVector<UTF16, 128> ProcessedString;
371 bool IsLongString;
372 RETURN_IF_ERROR(processString(Str, NullHandlingMethod::CutAtNull,
Martin Storsjoe99f5b42018-05-02 19:43:44 +0000373 IsLongString, ProcessedString,
374 Params.CodePage));
Marek Sokolowskie37621b2017-09-29 17:14:09 +0000375 for (auto Ch : ProcessedString)
376 writeInt<uint16_t>(Ch);
377 if (WriteTerminator)
378 writeInt<uint16_t>(0);
379 return Error::success();
380}
381
382Error ResourceFileWriter::writeIdentifier(const IntOrString &Ident) {
383 return writeIntOrString(Ident);
384}
385
386Error ResourceFileWriter::writeIntOrString(const IntOrString &Value) {
387 if (!Value.isInt())
388 return writeCString(Value.getString());
389
390 writeInt<uint16_t>(0xFFFF);
391 writeInt<uint16_t>(Value.getInt());
392 return Error::success();
393}
394
Zachary Turner93bb30d2017-10-06 21:26:06 +0000395void ResourceFileWriter::writeRCInt(RCInt Value) {
396 if (Value.isLong())
Zachary Turner88aa4b22017-10-06 23:21:43 +0000397 writeInt<uint32_t>(Value);
Zachary Turner93bb30d2017-10-06 21:26:06 +0000398 else
Zachary Turner88aa4b22017-10-06 23:21:43 +0000399 writeInt<uint16_t>(Value);
Zachary Turner93bb30d2017-10-06 21:26:06 +0000400}
401
Marek Sokolowskie37621b2017-09-29 17:14:09 +0000402Error ResourceFileWriter::appendFile(StringRef Filename) {
403 bool IsLong;
404 stripQuotes(Filename, IsLong);
405
Zachary Turnere315d732017-10-11 20:12:09 +0000406 auto File = loadFile(Filename);
Marek Sokolowskie37621b2017-09-29 17:14:09 +0000407 if (!File)
Zachary Turnere315d732017-10-11 20:12:09 +0000408 return File.takeError();
409
Marek Sokolowskie37621b2017-09-29 17:14:09 +0000410 *FS << (*File)->getBuffer();
411 return Error::success();
412}
413
414void ResourceFileWriter::padStream(uint64_t Length) {
415 assert(Length > 0);
416 uint64_t Location = tell();
417 Location %= Length;
418 uint64_t Pad = (Length - Location) % Length;
419 for (uint64_t i = 0; i < Pad; ++i)
420 writeInt<uint8_t>(0);
421}
422
Zachary Turner5d1b2d32017-10-09 18:50:29 +0000423Error ResourceFileWriter::handleError(Error Err, const RCResource *Res) {
Marek Sokolowskie37621b2017-09-29 17:14:09 +0000424 if (Err)
425 return joinErrors(createError("Error in " + Res->getResourceTypeName() +
426 " statement (ID " + Twine(Res->ResName) +
427 "): "),
428 std::move(Err));
429 return Error::success();
430}
431
432Error ResourceFileWriter::visitNullResource(const RCResource *Res) {
433 return writeResource(Res, &ResourceFileWriter::writeNullBody);
434}
435
Marek Sokolowskib121e772017-09-29 19:07:44 +0000436Error ResourceFileWriter::visitAcceleratorsResource(const RCResource *Res) {
437 return writeResource(Res, &ResourceFileWriter::writeAcceleratorsBody);
438}
439
Martin Storsjo31dc80f2018-05-07 20:27:37 +0000440Error ResourceFileWriter::visitBitmapResource(const RCResource *Res) {
441 return writeResource(Res, &ResourceFileWriter::writeBitmapBody);
442}
443
Zachary Turner4c72b952017-10-06 21:25:44 +0000444Error ResourceFileWriter::visitCursorResource(const RCResource *Res) {
445 return handleError(visitIconOrCursorResource(Res), Res);
446}
447
Marek Sokolowskibbf12f32017-09-30 00:38:52 +0000448Error ResourceFileWriter::visitDialogResource(const RCResource *Res) {
449 return writeResource(Res, &ResourceFileWriter::writeDialogBody);
450}
451
Zachary Turner4c72b952017-10-06 21:25:44 +0000452Error ResourceFileWriter::visitIconResource(const RCResource *Res) {
453 return handleError(visitIconOrCursorResource(Res), Res);
454}
455
Zachary Turner44bde8d2017-10-06 20:51:20 +0000456Error ResourceFileWriter::visitCaptionStmt(const CaptionStmt *Stmt) {
457 ObjectData.Caption = Stmt->Value;
458 return Error::success();
459}
460
Martin Storsjo65de7bd2018-05-15 19:21:28 +0000461Error ResourceFileWriter::visitClassStmt(const ClassStmt *Stmt) {
462 ObjectData.Class = Stmt->Value;
463 return Error::success();
464}
465
Marek Sokolowskie37621b2017-09-29 17:14:09 +0000466Error ResourceFileWriter::visitHTMLResource(const RCResource *Res) {
467 return writeResource(Res, &ResourceFileWriter::writeHTMLBody);
468}
469
Marek Sokolowskiea529932017-09-29 22:25:05 +0000470Error ResourceFileWriter::visitMenuResource(const RCResource *Res) {
471 return writeResource(Res, &ResourceFileWriter::writeMenuBody);
472}
473
Zachary Turner84ad96b2017-10-06 21:30:55 +0000474Error ResourceFileWriter::visitStringTableResource(const RCResource *Base) {
475 const auto *Res = cast<StringTableResource>(Base);
476
477 ContextKeeper RAII(this);
478 RETURN_IF_ERROR(Res->applyStmts(this));
479
480 for (auto &String : Res->Table) {
481 RETURN_IF_ERROR(checkNumberFits<uint16_t>(String.first, "String ID"));
482 uint16_t BundleID = String.first >> 4;
483 StringTableInfo::BundleKey Key(BundleID, ObjectData.LanguageInfo);
484 auto &BundleData = StringTableData.BundleData;
485 auto Iter = BundleData.find(Key);
486
487 if (Iter == BundleData.end()) {
488 // Need to create a bundle.
489 StringTableData.BundleList.push_back(Key);
Martin Storsjo370633f2018-05-15 06:35:29 +0000490 auto EmplaceResult = BundleData.emplace(
491 Key, StringTableInfo::Bundle(ObjectData, Res->MemoryFlags));
Zachary Turner84ad96b2017-10-06 21:30:55 +0000492 assert(EmplaceResult.second && "Could not create a bundle");
493 Iter = EmplaceResult.first;
494 }
495
496 RETURN_IF_ERROR(
497 insertStringIntoBundle(Iter->second, String.first, String.second));
498 }
499
500 return Error::success();
501}
502
Zachary Turner080f10e2017-10-06 21:52:15 +0000503Error ResourceFileWriter::visitUserDefinedResource(const RCResource *Res) {
504 return writeResource(Res, &ResourceFileWriter::writeUserDefinedBody);
505}
506
Zachary Turner93bb30d2017-10-06 21:26:06 +0000507Error ResourceFileWriter::visitVersionInfoResource(const RCResource *Res) {
508 return writeResource(Res, &ResourceFileWriter::writeVersionInfoBody);
509}
510
Marek Sokolowskib121e772017-09-29 19:07:44 +0000511Error ResourceFileWriter::visitCharacteristicsStmt(
512 const CharacteristicsStmt *Stmt) {
513 ObjectData.Characteristics = Stmt->Value;
514 return Error::success();
515}
516
Martin Storsjocec32ed2018-11-29 12:17:39 +0000517Error ResourceFileWriter::visitExStyleStmt(const ExStyleStmt *Stmt) {
518 ObjectData.ExStyle = Stmt->Value;
519 return Error::success();
520}
521
Zachary Turner44bde8d2017-10-06 20:51:20 +0000522Error ResourceFileWriter::visitFontStmt(const FontStmt *Stmt) {
523 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Stmt->Size, "Font size"));
524 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Stmt->Weight, "Font weight"));
525 RETURN_IF_ERROR(checkNumberFits<uint8_t>(Stmt->Charset, "Font charset"));
526 ObjectInfo::FontInfo Font{Stmt->Size, Stmt->Name, Stmt->Weight, Stmt->Italic,
527 Stmt->Charset};
528 ObjectData.Font.emplace(Font);
529 return Error::success();
530}
531
Marek Sokolowskie37621b2017-09-29 17:14:09 +0000532Error ResourceFileWriter::visitLanguageStmt(const LanguageResource *Stmt) {
533 RETURN_IF_ERROR(checkNumberFits(Stmt->Lang, 10, "Primary language ID"));
534 RETURN_IF_ERROR(checkNumberFits(Stmt->SubLang, 6, "Sublanguage ID"));
535 ObjectData.LanguageInfo = Stmt->Lang | (Stmt->SubLang << 10);
536 return Error::success();
537}
538
Zachary Turner44bde8d2017-10-06 20:51:20 +0000539Error ResourceFileWriter::visitStyleStmt(const StyleStmt *Stmt) {
540 ObjectData.Style = Stmt->Value;
541 return Error::success();
542}
543
Marek Sokolowskib121e772017-09-29 19:07:44 +0000544Error ResourceFileWriter::visitVersionStmt(const VersionStmt *Stmt) {
545 ObjectData.VersionInfo = Stmt->Value;
546 return Error::success();
547}
548
Marek Sokolowskie37621b2017-09-29 17:14:09 +0000549Error ResourceFileWriter::writeResource(
550 const RCResource *Res,
551 Error (ResourceFileWriter::*BodyWriter)(const RCResource *)) {
552 // We don't know the sizes yet.
553 object::WinResHeaderPrefix HeaderPrefix{ulittle32_t(0U), ulittle32_t(0U)};
554 uint64_t HeaderLoc = writeObject(HeaderPrefix);
555
556 auto ResType = Res->getResourceType();
557 RETURN_IF_ERROR(checkIntOrString(ResType, "Resource type"));
558 RETURN_IF_ERROR(checkIntOrString(Res->ResName, "Resource ID"));
559 RETURN_IF_ERROR(handleError(writeIdentifier(ResType), Res));
560 RETURN_IF_ERROR(handleError(writeIdentifier(Res->ResName), Res));
561
Marek Sokolowskib121e772017-09-29 19:07:44 +0000562 // Apply the resource-local optional statements.
563 ContextKeeper RAII(this);
564 RETURN_IF_ERROR(handleError(Res->applyStmts(this), Res));
565
Marek Sokolowskie37621b2017-09-29 17:14:09 +0000566 padStream(sizeof(uint32_t));
567 object::WinResHeaderSuffix HeaderSuffix{
568 ulittle32_t(0), // DataVersion; seems to always be 0
Martin Storsjo370633f2018-05-15 06:35:29 +0000569 ulittle16_t(Res->MemoryFlags), ulittle16_t(ObjectData.LanguageInfo),
Marek Sokolowskib121e772017-09-29 19:07:44 +0000570 ulittle32_t(ObjectData.VersionInfo),
571 ulittle32_t(ObjectData.Characteristics)};
Marek Sokolowskie37621b2017-09-29 17:14:09 +0000572 writeObject(HeaderSuffix);
573
574 uint64_t DataLoc = tell();
575 RETURN_IF_ERROR(handleError((this->*BodyWriter)(Res), Res));
576 // RETURN_IF_ERROR(handleError(dumpResource(Ctx)));
577
578 // Update the sizes.
579 HeaderPrefix.DataSize = tell() - DataLoc;
580 HeaderPrefix.HeaderSize = DataLoc - HeaderLoc;
581 writeObjectAt(HeaderPrefix, HeaderLoc);
582 padStream(sizeof(uint32_t));
583
584 return Error::success();
585}
586
Marek Sokolowskib121e772017-09-29 19:07:44 +0000587// --- NullResource helpers. --- //
588
Marek Sokolowskie37621b2017-09-29 17:14:09 +0000589Error ResourceFileWriter::writeNullBody(const RCResource *) {
590 return Error::success();
591}
592
Marek Sokolowskib121e772017-09-29 19:07:44 +0000593// --- AcceleratorsResource helpers. --- //
594
595Error ResourceFileWriter::writeSingleAccelerator(
596 const AcceleratorsResource::Accelerator &Obj, bool IsLastItem) {
597 using Accelerator = AcceleratorsResource::Accelerator;
598 using Opt = Accelerator::Options;
599
600 struct AccelTableEntry {
601 ulittle16_t Flags;
602 ulittle16_t ANSICode;
603 ulittle16_t Id;
604 uint16_t Padding;
605 } Entry{ulittle16_t(0), ulittle16_t(0), ulittle16_t(0), 0};
606
607 bool IsASCII = Obj.Flags & Opt::ASCII, IsVirtKey = Obj.Flags & Opt::VIRTKEY;
608
609 // Remove ASCII flags (which doesn't occur in .res files).
610 Entry.Flags = Obj.Flags & ~Opt::ASCII;
611
612 if (IsLastItem)
613 Entry.Flags |= 0x80;
614
615 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Obj.Id, "ACCELERATORS entry ID"));
616 Entry.Id = ulittle16_t(Obj.Id);
617
618 auto createAccError = [&Obj](const char *Msg) {
619 return createError("Accelerator ID " + Twine(Obj.Id) + ": " + Msg);
620 };
621
622 if (IsASCII && IsVirtKey)
623 return createAccError("Accelerator can't be both ASCII and VIRTKEY");
624
625 if (!IsVirtKey && (Obj.Flags & (Opt::ALT | Opt::SHIFT | Opt::CONTROL)))
626 return createAccError("Can only apply ALT, SHIFT or CONTROL to VIRTKEY"
627 " accelerators");
628
629 if (Obj.Event.isInt()) {
630 if (!IsASCII && !IsVirtKey)
631 return createAccError(
632 "Accelerator with a numeric event must be either ASCII"
633 " or VIRTKEY");
634
635 uint32_t EventVal = Obj.Event.getInt();
636 RETURN_IF_ERROR(
637 checkNumberFits<uint16_t>(EventVal, "Numeric event key ID"));
638 Entry.ANSICode = ulittle16_t(EventVal);
639 writeObject(Entry);
640 return Error::success();
641 }
642
643 StringRef Str = Obj.Event.getString();
644 bool IsWide;
645 stripQuotes(Str, IsWide);
646
647 if (Str.size() == 0 || Str.size() > 2)
648 return createAccError(
649 "Accelerator string events should have length 1 or 2");
650
651 if (Str[0] == '^') {
652 if (Str.size() == 1)
653 return createAccError("No character following '^' in accelerator event");
654 if (IsVirtKey)
655 return createAccError(
656 "VIRTKEY accelerator events can't be preceded by '^'");
657
658 char Ch = Str[1];
659 if (Ch >= 'a' && Ch <= 'z')
660 Entry.ANSICode = ulittle16_t(Ch - 'a' + 1);
661 else if (Ch >= 'A' && Ch <= 'Z')
662 Entry.ANSICode = ulittle16_t(Ch - 'A' + 1);
663 else
664 return createAccError("Control character accelerator event should be"
665 " alphabetic");
666
667 writeObject(Entry);
668 return Error::success();
669 }
670
671 if (Str.size() == 2)
672 return createAccError("Event string should be one-character, possibly"
673 " preceded by '^'");
674
675 uint8_t EventCh = Str[0];
676 // The original tool just warns in this situation. We chose to fail.
677 if (IsVirtKey && !isalnum(EventCh))
678 return createAccError("Non-alphanumeric characters cannot describe virtual"
679 " keys");
680 if (EventCh > 0x7F)
681 return createAccError("Non-ASCII description of accelerator");
682
683 if (IsVirtKey)
684 EventCh = toupper(EventCh);
685 Entry.ANSICode = ulittle16_t(EventCh);
686 writeObject(Entry);
687 return Error::success();
688}
689
690Error ResourceFileWriter::writeAcceleratorsBody(const RCResource *Base) {
691 auto *Res = cast<AcceleratorsResource>(Base);
692 size_t AcceleratorId = 0;
693 for (auto &Acc : Res->Accelerators) {
694 ++AcceleratorId;
695 RETURN_IF_ERROR(
696 writeSingleAccelerator(Acc, AcceleratorId == Res->Accelerators.size()));
697 }
698 return Error::success();
699}
700
Martin Storsjo31dc80f2018-05-07 20:27:37 +0000701// --- BitmapResource helpers. --- //
702
703Error ResourceFileWriter::writeBitmapBody(const RCResource *Base) {
704 StringRef Filename = cast<BitmapResource>(Base)->BitmapLoc;
705 bool IsLong;
706 stripQuotes(Filename, IsLong);
707
708 auto File = loadFile(Filename);
709 if (!File)
710 return File.takeError();
711
712 StringRef Buffer = (*File)->getBuffer();
713
714 // Skip the 14 byte BITMAPFILEHEADER.
715 constexpr size_t BITMAPFILEHEADER_size = 14;
716 if (Buffer.size() < BITMAPFILEHEADER_size || Buffer[0] != 'B' ||
717 Buffer[1] != 'M')
718 return createError("Incorrect bitmap file.");
719
720 *FS << Buffer.substr(BITMAPFILEHEADER_size);
721 return Error::success();
722}
723
Zachary Turner4c72b952017-10-06 21:25:44 +0000724// --- CursorResource and IconResource helpers. --- //
725
726// ICONRESDIR structure. Describes a single icon in resouce group.
727//
728// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648016.aspx
729struct IconResDir {
730 uint8_t Width;
731 uint8_t Height;
732 uint8_t ColorCount;
733 uint8_t Reserved;
734};
735
736// CURSORDIR structure. Describes a single cursor in resource group.
737//
738// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648011(v=vs.85).aspx
739struct CursorDir {
740 ulittle16_t Width;
741 ulittle16_t Height;
742};
743
744// RESDIRENTRY structure, stripped from the last item. Stripping made
745// for compatibility with RESDIR.
746//
747// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026(v=vs.85).aspx
748struct ResourceDirEntryStart {
749 union {
750 CursorDir Cursor; // Used in CURSOR resources.
751 IconResDir Icon; // Used in .ico and .cur files, and ICON resources.
752 };
753 ulittle16_t Planes; // HotspotX (.cur files but not CURSOR resource).
754 ulittle16_t BitCount; // HotspotY (.cur files but not CURSOR resource).
755 ulittle32_t Size;
756 // ulittle32_t ImageOffset; // Offset to image data (ICONDIRENTRY only).
757 // ulittle16_t IconID; // Resource icon ID (RESDIR only).
758};
759
760// BITMAPINFOHEADER structure. Describes basic information about the bitmap
761// being read.
762//
763// Ref: msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
764struct BitmapInfoHeader {
765 ulittle32_t Size;
766 ulittle32_t Width;
767 ulittle32_t Height;
768 ulittle16_t Planes;
769 ulittle16_t BitCount;
770 ulittle32_t Compression;
771 ulittle32_t SizeImage;
772 ulittle32_t XPelsPerMeter;
773 ulittle32_t YPelsPerMeter;
774 ulittle32_t ClrUsed;
775 ulittle32_t ClrImportant;
776};
777
778// Group icon directory header. Called ICONDIR in .ico/.cur files and
779// NEWHEADER in .res files.
780//
781// Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648023(v=vs.85).aspx
782struct GroupIconDir {
783 ulittle16_t Reserved; // Always 0.
784 ulittle16_t ResType; // 1 for icons, 2 for cursors.
785 ulittle16_t ResCount; // Number of items.
786};
787
788enum class IconCursorGroupType { Icon, Cursor };
789
790class SingleIconCursorResource : public RCResource {
791public:
792 IconCursorGroupType Type;
793 const ResourceDirEntryStart &Header;
794 ArrayRef<uint8_t> Image;
795
796 SingleIconCursorResource(IconCursorGroupType ResourceType,
797 const ResourceDirEntryStart &HeaderEntry,
Martin Storsjo370633f2018-05-15 06:35:29 +0000798 ArrayRef<uint8_t> ImageData, uint16_t Flags)
799 : RCResource(Flags), Type(ResourceType), Header(HeaderEntry),
800 Image(ImageData) {}
Zachary Turner4c72b952017-10-06 21:25:44 +0000801
802 Twine getResourceTypeName() const override { return "Icon/cursor image"; }
803 IntOrString getResourceType() const override {
804 return Type == IconCursorGroupType::Icon ? RkSingleIcon : RkSingleCursor;
805 }
Zachary Turner4c72b952017-10-06 21:25:44 +0000806 ResourceKind getKind() const override { return RkSingleCursorOrIconRes; }
807 static bool classof(const RCResource *Res) {
808 return Res->getKind() == RkSingleCursorOrIconRes;
809 }
810};
811
812class IconCursorGroupResource : public RCResource {
813public:
814 IconCursorGroupType Type;
815 GroupIconDir Header;
816 std::vector<ResourceDirEntryStart> ItemEntries;
817
818 IconCursorGroupResource(IconCursorGroupType ResourceType,
819 const GroupIconDir &HeaderData,
820 std::vector<ResourceDirEntryStart> &&Entries)
821 : Type(ResourceType), Header(HeaderData),
822 ItemEntries(std::move(Entries)) {}
823
824 Twine getResourceTypeName() const override { return "Icon/cursor group"; }
825 IntOrString getResourceType() const override {
826 return Type == IconCursorGroupType::Icon ? RkIconGroup : RkCursorGroup;
827 }
828 ResourceKind getKind() const override { return RkCursorOrIconGroupRes; }
829 static bool classof(const RCResource *Res) {
830 return Res->getKind() == RkCursorOrIconGroupRes;
831 }
832};
833
834Error ResourceFileWriter::writeSingleIconOrCursorBody(const RCResource *Base) {
835 auto *Res = cast<SingleIconCursorResource>(Base);
836 if (Res->Type == IconCursorGroupType::Cursor) {
837 // In case of cursors, two WORDS are appended to the beginning
838 // of the resource: HotspotX (Planes in RESDIRENTRY),
839 // and HotspotY (BitCount).
840 //
841 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648026.aspx
842 // (Remarks section).
843 writeObject(Res->Header.Planes);
844 writeObject(Res->Header.BitCount);
845 }
846
847 writeObject(Res->Image);
848 return Error::success();
849}
850
851Error ResourceFileWriter::writeIconOrCursorGroupBody(const RCResource *Base) {
852 auto *Res = cast<IconCursorGroupResource>(Base);
853 writeObject(Res->Header);
854 for (auto Item : Res->ItemEntries) {
855 writeObject(Item);
Zachary Turner88aa4b22017-10-06 23:21:43 +0000856 writeInt(IconCursorID++);
Zachary Turner4c72b952017-10-06 21:25:44 +0000857 }
858 return Error::success();
859}
860
861Error ResourceFileWriter::visitSingleIconOrCursor(const RCResource *Res) {
862 return writeResource(Res, &ResourceFileWriter::writeSingleIconOrCursorBody);
863}
864
865Error ResourceFileWriter::visitIconOrCursorGroup(const RCResource *Res) {
866 return writeResource(Res, &ResourceFileWriter::writeIconOrCursorGroupBody);
867}
868
869Error ResourceFileWriter::visitIconOrCursorResource(const RCResource *Base) {
870 IconCursorGroupType Type;
871 StringRef FileStr;
872 IntOrString ResName = Base->ResName;
873
874 if (auto *IconRes = dyn_cast<IconResource>(Base)) {
875 FileStr = IconRes->IconLoc;
876 Type = IconCursorGroupType::Icon;
877 } else {
878 auto *CursorRes = dyn_cast<CursorResource>(Base);
879 FileStr = CursorRes->CursorLoc;
880 Type = IconCursorGroupType::Cursor;
881 }
882
883 bool IsLong;
884 stripQuotes(FileStr, IsLong);
Zachary Turnere315d732017-10-11 20:12:09 +0000885 auto File = loadFile(FileStr);
Zachary Turner4c72b952017-10-06 21:25:44 +0000886
887 if (!File)
Zachary Turnere315d732017-10-11 20:12:09 +0000888 return File.takeError();
Zachary Turner4c72b952017-10-06 21:25:44 +0000889
890 BinaryStreamReader Reader((*File)->getBuffer(), support::little);
891
892 // Read the file headers.
893 // - At the beginning, ICONDIR/NEWHEADER header.
894 // - Then, a number of RESDIR headers follow. These contain offsets
895 // to data.
896 const GroupIconDir *Header;
897
898 RETURN_IF_ERROR(Reader.readObject(Header));
899 if (Header->Reserved != 0)
900 return createError("Incorrect icon/cursor Reserved field; should be 0.");
901 uint16_t NeededType = Type == IconCursorGroupType::Icon ? 1 : 2;
902 if (Header->ResType != NeededType)
903 return createError("Incorrect icon/cursor ResType field; should be " +
904 Twine(NeededType) + ".");
905
906 uint16_t NumItems = Header->ResCount;
907
908 // Read single ico/cur headers.
909 std::vector<ResourceDirEntryStart> ItemEntries;
910 ItemEntries.reserve(NumItems);
911 std::vector<uint32_t> ItemOffsets(NumItems);
912 for (size_t ID = 0; ID < NumItems; ++ID) {
913 const ResourceDirEntryStart *Object;
914 RETURN_IF_ERROR(Reader.readObject(Object));
915 ItemEntries.push_back(*Object);
916 RETURN_IF_ERROR(Reader.readInteger(ItemOffsets[ID]));
917 }
918
919 // Now write each icon/cursors one by one. At first, all the contents
920 // without ICO/CUR header. This is described by SingleIconCursorResource.
921 for (size_t ID = 0; ID < NumItems; ++ID) {
922 // Load the fragment of file.
923 Reader.setOffset(ItemOffsets[ID]);
924 ArrayRef<uint8_t> Image;
925 RETURN_IF_ERROR(Reader.readArray(Image, ItemEntries[ID].Size));
Martin Storsjo370633f2018-05-15 06:35:29 +0000926 SingleIconCursorResource SingleRes(Type, ItemEntries[ID], Image,
927 Base->MemoryFlags);
Zachary Turner4c72b952017-10-06 21:25:44 +0000928 SingleRes.setName(IconCursorID + ID);
929 RETURN_IF_ERROR(visitSingleIconOrCursor(&SingleRes));
930 }
931
932 // Now, write all the headers concatenated into a separate resource.
933 for (size_t ID = 0; ID < NumItems; ++ID) {
Martin Storsjoce638742018-05-15 06:35:20 +0000934 // We need to rewrite the cursor headers, and fetch actual values
935 // for Planes/BitCount.
Zachary Turner4c72b952017-10-06 21:25:44 +0000936 const auto &OldHeader = ItemEntries[ID];
Martin Storsjoce638742018-05-15 06:35:20 +0000937 ResourceDirEntryStart NewHeader = OldHeader;
938
939 if (Type == IconCursorGroupType::Cursor) {
940 NewHeader.Cursor.Width = OldHeader.Icon.Width;
941 // Each cursor in fact stores two bitmaps, one under another.
942 // Height provided in cursor definition describes the height of the
943 // cursor, whereas the value existing in resource definition describes
944 // the height of the bitmap. Therefore, we need to double this height.
945 NewHeader.Cursor.Height = OldHeader.Icon.Height * 2;
946
947 // Two WORDs were written at the beginning of the resource (hotspot
948 // location). This is reflected in Size field.
949 NewHeader.Size += 2 * sizeof(uint16_t);
950 }
Zachary Turner4c72b952017-10-06 21:25:44 +0000951
952 // Now, we actually need to read the bitmap header to find
953 // the number of planes and the number of bits per pixel.
954 Reader.setOffset(ItemOffsets[ID]);
955 const BitmapInfoHeader *BMPHeader;
956 RETURN_IF_ERROR(Reader.readObject(BMPHeader));
Martin Storsjoce638742018-05-15 06:35:20 +0000957 if (BMPHeader->Size == sizeof(BitmapInfoHeader)) {
958 NewHeader.Planes = BMPHeader->Planes;
959 NewHeader.BitCount = BMPHeader->BitCount;
960 } else {
961 // A PNG .ico file.
962 // https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473
963 // "The image must be in 32bpp"
964 NewHeader.Planes = 1;
965 NewHeader.BitCount = 32;
966 }
Zachary Turner4c72b952017-10-06 21:25:44 +0000967
968 ItemEntries[ID] = NewHeader;
969 }
970
971 IconCursorGroupResource HeaderRes(Type, *Header, std::move(ItemEntries));
972 HeaderRes.setName(ResName);
Martin Storsjo370633f2018-05-15 06:35:29 +0000973 if (Base->MemoryFlags & MfPreload) {
974 HeaderRes.MemoryFlags |= MfPreload;
975 HeaderRes.MemoryFlags &= ~MfPure;
976 }
Zachary Turner4c72b952017-10-06 21:25:44 +0000977 RETURN_IF_ERROR(visitIconOrCursorGroup(&HeaderRes));
978
979 return Error::success();
980}
981
Marek Sokolowskibbf12f32017-09-30 00:38:52 +0000982// --- DialogResource helpers. --- //
Marek Sokolowskib121e772017-09-29 19:07:44 +0000983
Marek Sokolowskibbf12f32017-09-30 00:38:52 +0000984Error ResourceFileWriter::writeSingleDialogControl(const Control &Ctl,
985 bool IsExtended) {
986 // Each control should be aligned to DWORD.
987 padStream(sizeof(uint32_t));
988
989 auto TypeInfo = Control::SupportedCtls.lookup(Ctl.Type);
Martin Storsjoe96cf5f2018-12-05 13:22:56 +0000990 IntWithNotMask CtlStyle(TypeInfo.Style);
991 CtlStyle |= Ctl.Style.getValueOr(RCInt(0));
Marek Sokolowskibbf12f32017-09-30 00:38:52 +0000992 uint32_t CtlExtStyle = Ctl.ExtStyle.getValueOr(0);
993
994 // DIALOG(EX) item header prefix.
995 if (!IsExtended) {
996 struct {
997 ulittle32_t Style;
998 ulittle32_t ExtStyle;
Martin Storsjoe96cf5f2018-12-05 13:22:56 +0000999 } Prefix{ulittle32_t(CtlStyle.getValue()), ulittle32_t(CtlExtStyle)};
Marek Sokolowskibbf12f32017-09-30 00:38:52 +00001000 writeObject(Prefix);
1001 } else {
1002 struct {
1003 ulittle32_t HelpID;
1004 ulittle32_t ExtStyle;
1005 ulittle32_t Style;
1006 } Prefix{ulittle32_t(Ctl.HelpID.getValueOr(0)), ulittle32_t(CtlExtStyle),
Martin Storsjoe96cf5f2018-12-05 13:22:56 +00001007 ulittle32_t(CtlStyle.getValue())};
Marek Sokolowskibbf12f32017-09-30 00:38:52 +00001008 writeObject(Prefix);
1009 }
1010
1011 // Common fixed-length part.
1012 RETURN_IF_ERROR(checkSignedNumberFits<int16_t>(
1013 Ctl.X, "Dialog control x-coordinate", true));
1014 RETURN_IF_ERROR(checkSignedNumberFits<int16_t>(
1015 Ctl.Y, "Dialog control y-coordinate", true));
1016 RETURN_IF_ERROR(
1017 checkSignedNumberFits<int16_t>(Ctl.Width, "Dialog control width", false));
1018 RETURN_IF_ERROR(checkSignedNumberFits<int16_t>(
1019 Ctl.Height, "Dialog control height", false));
1020 struct {
1021 ulittle16_t X;
1022 ulittle16_t Y;
1023 ulittle16_t Width;
1024 ulittle16_t Height;
1025 } Middle{ulittle16_t(Ctl.X), ulittle16_t(Ctl.Y), ulittle16_t(Ctl.Width),
1026 ulittle16_t(Ctl.Height)};
1027 writeObject(Middle);
1028
1029 // ID; it's 16-bit in DIALOG and 32-bit in DIALOGEX.
1030 if (!IsExtended) {
Martin Storsjo1b557b32018-05-09 18:20:49 +00001031 // It's common to use -1, i.e. UINT32_MAX, for controls one doesn't
1032 // want to refer to later.
1033 if (Ctl.ID != static_cast<uint32_t>(-1))
1034 RETURN_IF_ERROR(checkNumberFits<uint16_t>(
1035 Ctl.ID, "Control ID in simple DIALOG resource"));
Marek Sokolowskibbf12f32017-09-30 00:38:52 +00001036 writeInt<uint16_t>(Ctl.ID);
1037 } else {
1038 writeInt<uint32_t>(Ctl.ID);
1039 }
1040
1041 // Window class - either 0xFFFF + 16-bit integer or a string.
Martin Storsjo793104b2018-05-08 20:55:58 +00001042 RETURN_IF_ERROR(writeIntOrString(Ctl.Class));
Marek Sokolowskibbf12f32017-09-30 00:38:52 +00001043
1044 // Element caption/reference ID. ID is preceded by 0xFFFF.
1045 RETURN_IF_ERROR(checkIntOrString(Ctl.Title, "Control reference ID"));
1046 RETURN_IF_ERROR(writeIntOrString(Ctl.Title));
1047
1048 // # bytes of extra creation data count. Don't pass any.
1049 writeInt<uint16_t>(0);
1050
1051 return Error::success();
1052}
1053
1054Error ResourceFileWriter::writeDialogBody(const RCResource *Base) {
1055 auto *Res = cast<DialogResource>(Base);
1056
1057 // Default style: WS_POPUP | WS_BORDER | WS_SYSMENU.
Zachary Turner44bde8d2017-10-06 20:51:20 +00001058 const uint32_t DefaultStyle = 0x80880000;
1059 const uint32_t StyleFontFlag = 0x40;
1060 const uint32_t StyleCaptionFlag = 0x00C00000;
1061
1062 uint32_t UsedStyle = ObjectData.Style.getValueOr(DefaultStyle);
1063 if (ObjectData.Font)
1064 UsedStyle |= StyleFontFlag;
1065 else
1066 UsedStyle &= ~StyleFontFlag;
1067
1068 // Actually, in case of empty (but existent) caption, the examined field
1069 // is equal to "\"\"". That's why empty captions are still noticed.
1070 if (ObjectData.Caption != "")
1071 UsedStyle |= StyleCaptionFlag;
1072
1073 const uint16_t DialogExMagic = 0xFFFF;
Martin Storsjocec32ed2018-11-29 12:17:39 +00001074 uint32_t ExStyle = ObjectData.ExStyle.getValueOr(0);
Marek Sokolowskibbf12f32017-09-30 00:38:52 +00001075
1076 // Write DIALOG(EX) header prefix. These are pretty different.
1077 if (!Res->IsExtended) {
Zachary Turner44bde8d2017-10-06 20:51:20 +00001078 // We cannot let the higher word of DefaultStyle be equal to 0xFFFF.
1079 // In such a case, whole object (in .res file) is equivalent to a
1080 // DIALOGEX. It might lead to access violation/segmentation fault in
1081 // resource readers. For example,
1082 // 1 DIALOG 0, 0, 0, 65432
1083 // STYLE 0xFFFF0001 {}
1084 // would be compiled to a DIALOGEX with 65432 controls.
1085 if ((UsedStyle >> 16) == DialogExMagic)
1086 return createError("16 higher bits of DIALOG resource style cannot be"
1087 " equal to 0xFFFF");
1088
Marek Sokolowskibbf12f32017-09-30 00:38:52 +00001089 struct {
1090 ulittle32_t Style;
1091 ulittle32_t ExtStyle;
1092 } Prefix{ulittle32_t(UsedStyle),
Martin Storsjocec32ed2018-11-29 12:17:39 +00001093 ulittle32_t(ExStyle)};
Marek Sokolowskibbf12f32017-09-30 00:38:52 +00001094
1095 writeObject(Prefix);
1096 } else {
Marek Sokolowskibbf12f32017-09-30 00:38:52 +00001097 struct {
1098 ulittle16_t Version;
1099 ulittle16_t Magic;
1100 ulittle32_t HelpID;
1101 ulittle32_t ExtStyle;
1102 ulittle32_t Style;
1103 } Prefix{ulittle16_t(1), ulittle16_t(DialogExMagic),
Martin Storsjocec32ed2018-11-29 12:17:39 +00001104 ulittle32_t(Res->HelpID), ulittle32_t(ExStyle), ulittle32_t(UsedStyle)};
Marek Sokolowskibbf12f32017-09-30 00:38:52 +00001105
1106 writeObject(Prefix);
1107 }
1108
1109 // Now, a common part. First, fixed-length fields.
1110 RETURN_IF_ERROR(checkNumberFits<uint16_t>(Res->Controls.size(),
1111 "Number of dialog controls"));
1112 RETURN_IF_ERROR(
1113 checkSignedNumberFits<int16_t>(Res->X, "Dialog x-coordinate", true));
1114 RETURN_IF_ERROR(
1115 checkSignedNumberFits<int16_t>(Res->Y, "Dialog y-coordinate", true));
1116 RETURN_IF_ERROR(
1117 checkSignedNumberFits<int16_t>(Res->Width, "Dialog width", false));
1118 RETURN_IF_ERROR(
1119 checkSignedNumberFits<int16_t>(Res->Height, "Dialog height", false));
1120 struct {
1121 ulittle16_t Count;
1122 ulittle16_t PosX;
1123 ulittle16_t PosY;
1124 ulittle16_t DialogWidth;
1125 ulittle16_t DialogHeight;
1126 } Middle{ulittle16_t(Res->Controls.size()), ulittle16_t(Res->X),
1127 ulittle16_t(Res->Y), ulittle16_t(Res->Width),
1128 ulittle16_t(Res->Height)};
1129 writeObject(Middle);
1130
1131 // MENU field. As of now, we don't keep them in the state and can peacefully
1132 // think there is no menu attached to the dialog.
1133 writeInt<uint16_t>(0);
1134
Martin Storsjo65de7bd2018-05-15 19:21:28 +00001135 // Window CLASS field.
1136 RETURN_IF_ERROR(writeIntOrString(ObjectData.Class));
Marek Sokolowskibbf12f32017-09-30 00:38:52 +00001137
Zachary Turner44bde8d2017-10-06 20:51:20 +00001138 // Window title or a single word equal to 0.
1139 RETURN_IF_ERROR(writeCString(ObjectData.Caption));
1140
1141 // If there *is* a window font declared, output its data.
1142 auto &Font = ObjectData.Font;
1143 if (Font) {
1144 writeInt<uint16_t>(Font->Size);
1145 // Additional description occurs only in DIALOGEX.
1146 if (Res->IsExtended) {
1147 writeInt<uint16_t>(Font->Weight);
1148 writeInt<uint8_t>(Font->IsItalic);
1149 writeInt<uint8_t>(Font->Charset);
1150 }
1151 RETURN_IF_ERROR(writeCString(Font->Typeface));
1152 }
Marek Sokolowskibbf12f32017-09-30 00:38:52 +00001153
1154 auto handleCtlError = [&](Error &&Err, const Control &Ctl) -> Error {
1155 if (!Err)
1156 return Error::success();
1157 return joinErrors(createError("Error in " + Twine(Ctl.Type) +
1158 " control (ID " + Twine(Ctl.ID) + "):"),
1159 std::move(Err));
1160 };
1161
1162 for (auto &Ctl : Res->Controls)
1163 RETURN_IF_ERROR(
1164 handleCtlError(writeSingleDialogControl(Ctl, Res->IsExtended), Ctl));
1165
1166 return Error::success();
1167}
1168
1169// --- HTMLResource helpers. --- //
Marek Sokolowskib121e772017-09-29 19:07:44 +00001170
Marek Sokolowskie37621b2017-09-29 17:14:09 +00001171Error ResourceFileWriter::writeHTMLBody(const RCResource *Base) {
1172 return appendFile(cast<HTMLResource>(Base)->HTMLLoc);
1173}
1174
Marek Sokolowskiea529932017-09-29 22:25:05 +00001175// --- MenuResource helpers. --- //
1176
1177Error ResourceFileWriter::writeMenuDefinition(
1178 const std::unique_ptr<MenuDefinition> &Def, uint16_t Flags) {
1179 assert(Def);
1180 const MenuDefinition *DefPtr = Def.get();
1181
1182 if (auto *MenuItemPtr = dyn_cast<MenuItem>(DefPtr)) {
1183 writeInt<uint16_t>(Flags);
1184 RETURN_IF_ERROR(
1185 checkNumberFits<uint16_t>(MenuItemPtr->Id, "MENUITEM action ID"));
1186 writeInt<uint16_t>(MenuItemPtr->Id);
1187 RETURN_IF_ERROR(writeCString(MenuItemPtr->Name));
1188 return Error::success();
1189 }
1190
1191 if (isa<MenuSeparator>(DefPtr)) {
1192 writeInt<uint16_t>(Flags);
1193 writeInt<uint32_t>(0);
1194 return Error::success();
1195 }
1196
1197 auto *PopupPtr = cast<PopupItem>(DefPtr);
1198 writeInt<uint16_t>(Flags);
1199 RETURN_IF_ERROR(writeCString(PopupPtr->Name));
1200 return writeMenuDefinitionList(PopupPtr->SubItems);
1201}
1202
1203Error ResourceFileWriter::writeMenuDefinitionList(
1204 const MenuDefinitionList &List) {
1205 for (auto &Def : List.Definitions) {
1206 uint16_t Flags = Def->getResFlags();
1207 // Last element receives an additional 0x80 flag.
1208 const uint16_t LastElementFlag = 0x0080;
1209 if (&Def == &List.Definitions.back())
1210 Flags |= LastElementFlag;
1211
1212 RETURN_IF_ERROR(writeMenuDefinition(Def, Flags));
1213 }
1214 return Error::success();
1215}
1216
1217Error ResourceFileWriter::writeMenuBody(const RCResource *Base) {
1218 // At first, MENUHEADER structure. In fact, these are two WORDs equal to 0.
1219 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms648018.aspx
Zachary Turner75412552017-10-09 22:59:40 +00001220 writeInt<uint32_t>(0);
Marek Sokolowskiea529932017-09-29 22:25:05 +00001221
1222 return writeMenuDefinitionList(cast<MenuResource>(Base)->Elements);
1223}
1224
Zachary Turner84ad96b2017-10-06 21:30:55 +00001225// --- StringTableResource helpers. --- //
1226
1227class BundleResource : public RCResource {
1228public:
1229 using BundleType = ResourceFileWriter::StringTableInfo::Bundle;
1230 BundleType Bundle;
1231
Martin Storsjo370633f2018-05-15 06:35:29 +00001232 BundleResource(const BundleType &StrBundle)
1233 : RCResource(StrBundle.MemoryFlags), Bundle(StrBundle) {}
Zachary Turner84ad96b2017-10-06 21:30:55 +00001234 IntOrString getResourceType() const override { return 6; }
1235
1236 ResourceKind getKind() const override { return RkStringTableBundle; }
1237 static bool classof(const RCResource *Res) {
1238 return Res->getKind() == RkStringTableBundle;
1239 }
Martin Storsjoe99f5b42018-05-02 19:43:44 +00001240 Twine getResourceTypeName() const override { return "STRINGTABLE"; }
Zachary Turner84ad96b2017-10-06 21:30:55 +00001241};
1242
1243Error ResourceFileWriter::visitStringTableBundle(const RCResource *Res) {
1244 return writeResource(Res, &ResourceFileWriter::writeStringTableBundleBody);
1245}
1246
1247Error ResourceFileWriter::insertStringIntoBundle(
1248 StringTableInfo::Bundle &Bundle, uint16_t StringID, StringRef String) {
1249 uint16_t StringLoc = StringID & 15;
1250 if (Bundle.Data[StringLoc])
1251 return createError("Multiple STRINGTABLE strings located under ID " +
1252 Twine(StringID));
1253 Bundle.Data[StringLoc] = String;
1254 return Error::success();
1255}
1256
1257Error ResourceFileWriter::writeStringTableBundleBody(const RCResource *Base) {
1258 auto *Res = cast<BundleResource>(Base);
1259 for (size_t ID = 0; ID < Res->Bundle.Data.size(); ++ID) {
1260 // The string format is a tiny bit different here. We
1261 // first output the size of the string, and then the string itself
1262 // (which is not null-terminated).
1263 bool IsLongString;
1264 SmallVector<UTF16, 128> Data;
1265 RETURN_IF_ERROR(processString(Res->Bundle.Data[ID].getValueOr(StringRef()),
1266 NullHandlingMethod::CutAtDoubleNull,
Martin Storsjoe99f5b42018-05-02 19:43:44 +00001267 IsLongString, Data, Params.CodePage));
Zachary Turner84ad96b2017-10-06 21:30:55 +00001268 if (AppendNull && Res->Bundle.Data[ID])
1269 Data.push_back('\0');
1270 RETURN_IF_ERROR(
1271 checkNumberFits<uint16_t>(Data.size(), "STRINGTABLE string size"));
Zachary Turner88aa4b22017-10-06 23:21:43 +00001272 writeInt<uint16_t>(Data.size());
Zachary Turner84ad96b2017-10-06 21:30:55 +00001273 for (auto Char : Data)
Zachary Turner88aa4b22017-10-06 23:21:43 +00001274 writeInt(Char);
Zachary Turner84ad96b2017-10-06 21:30:55 +00001275 }
1276 return Error::success();
1277}
1278
1279Error ResourceFileWriter::dumpAllStringTables() {
1280 for (auto Key : StringTableData.BundleList) {
1281 auto Iter = StringTableData.BundleData.find(Key);
1282 assert(Iter != StringTableData.BundleData.end());
1283
1284 // For a moment, revert the context info to moment of bundle declaration.
1285 ContextKeeper RAII(this);
1286 ObjectData = Iter->second.DeclTimeInfo;
1287
1288 BundleResource Res(Iter->second);
1289 // Bundle #(k+1) contains keys [16k, 16k + 15].
1290 Res.setName(Key.first + 1);
1291 RETURN_IF_ERROR(visitStringTableBundle(&Res));
1292 }
1293 return Error::success();
1294}
1295
Zachary Turner080f10e2017-10-06 21:52:15 +00001296// --- UserDefinedResource helpers. --- //
1297
1298Error ResourceFileWriter::writeUserDefinedBody(const RCResource *Base) {
1299 auto *Res = cast<UserDefinedResource>(Base);
1300
1301 if (Res->IsFileResource)
1302 return appendFile(Res->FileLoc);
1303
1304 for (auto &Elem : Res->Contents) {
1305 if (Elem.isInt()) {
1306 RETURN_IF_ERROR(
1307 checkRCInt(Elem.getInt(), "Number in user-defined resource"));
1308 writeRCInt(Elem.getInt());
1309 continue;
1310 }
1311
1312 SmallVector<UTF16, 128> ProcessedString;
1313 bool IsLongString;
Martin Storsjoe99f5b42018-05-02 19:43:44 +00001314 RETURN_IF_ERROR(
1315 processString(Elem.getString(), NullHandlingMethod::UserResource,
1316 IsLongString, ProcessedString, Params.CodePage));
Zachary Turner080f10e2017-10-06 21:52:15 +00001317
1318 for (auto Ch : ProcessedString) {
1319 if (IsLongString) {
Zachary Turner88aa4b22017-10-06 23:21:43 +00001320 writeInt(Ch);
Zachary Turner080f10e2017-10-06 21:52:15 +00001321 continue;
1322 }
1323
1324 RETURN_IF_ERROR(checkNumberFits<uint8_t>(
Simon Dardisf60024e2017-10-11 10:14:22 +00001325 Ch, "Character in narrow string in user-defined resource"));
Zachary Turner88aa4b22017-10-06 23:21:43 +00001326 writeInt<uint8_t>(Ch);
Zachary Turner080f10e2017-10-06 21:52:15 +00001327 }
1328 }
1329
1330 return Error::success();
1331}
1332
Zachary Turner93bb30d2017-10-06 21:26:06 +00001333// --- VersionInfoResourceResource helpers. --- //
1334
1335Error ResourceFileWriter::writeVersionInfoBlock(const VersionInfoBlock &Blk) {
1336 // Output the header if the block has name.
1337 bool OutputHeader = Blk.Name != "";
1338 uint64_t LengthLoc;
1339
Martin Storsjo52603332018-05-07 20:27:23 +00001340 padStream(sizeof(uint32_t));
Zachary Turner93bb30d2017-10-06 21:26:06 +00001341 if (OutputHeader) {
Zachary Turner75412552017-10-09 22:59:40 +00001342 LengthLoc = writeInt<uint16_t>(0);
1343 writeInt<uint16_t>(0);
1344 writeInt<uint16_t>(1); // true
Zachary Turner93bb30d2017-10-06 21:26:06 +00001345 RETURN_IF_ERROR(writeCString(Blk.Name));
1346 padStream(sizeof(uint32_t));
1347 }
1348
1349 for (const std::unique_ptr<VersionInfoStmt> &Item : Blk.Stmts) {
1350 VersionInfoStmt *ItemPtr = Item.get();
1351
1352 if (auto *BlockPtr = dyn_cast<VersionInfoBlock>(ItemPtr)) {
1353 RETURN_IF_ERROR(writeVersionInfoBlock(*BlockPtr));
1354 continue;
1355 }
1356
1357 auto *ValuePtr = cast<VersionInfoValue>(ItemPtr);
1358 RETURN_IF_ERROR(writeVersionInfoValue(*ValuePtr));
1359 }
1360
1361 if (OutputHeader) {
1362 uint64_t CurLoc = tell();
1363 writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc);
1364 }
1365
Zachary Turner93bb30d2017-10-06 21:26:06 +00001366 return Error::success();
1367}
1368
1369Error ResourceFileWriter::writeVersionInfoValue(const VersionInfoValue &Val) {
1370 // rc has a peculiar algorithm to output VERSIONINFO VALUEs. Each VALUE
1371 // is a mapping from the key (string) to the value (a sequence of ints or
1372 // a sequence of strings).
1373 //
1374 // If integers are to be written: width of each integer written depends on
1375 // whether it's been declared 'long' (it's DWORD then) or not (it's WORD).
1376 // ValueLength defined in structure referenced below is then the total
1377 // number of bytes taken by these integers.
1378 //
1379 // If strings are to be written: characters are always WORDs.
1380 // Moreover, '\0' character is written after the last string, and between
1381 // every two strings separated by comma (if strings are not comma-separated,
1382 // they're simply concatenated). ValueLength is equal to the number of WORDs
1383 // written (that is, half of the bytes written).
1384 //
1385 // Ref: msdn.microsoft.com/en-us/library/windows/desktop/ms646994.aspx
1386 bool HasStrings = false, HasInts = false;
1387 for (auto &Item : Val.Values)
1388 (Item.isInt() ? HasInts : HasStrings) = true;
1389
1390 assert((HasStrings || HasInts) && "VALUE must have at least one argument");
1391 if (HasStrings && HasInts)
1392 return createError(Twine("VALUE ") + Val.Key +
1393 " cannot contain both strings and integers");
1394
Martin Storsjo52603332018-05-07 20:27:23 +00001395 padStream(sizeof(uint32_t));
Zachary Turner75412552017-10-09 22:59:40 +00001396 auto LengthLoc = writeInt<uint16_t>(0);
1397 auto ValLengthLoc = writeInt<uint16_t>(0);
1398 writeInt<uint16_t>(HasStrings);
Zachary Turner93bb30d2017-10-06 21:26:06 +00001399 RETURN_IF_ERROR(writeCString(Val.Key));
1400 padStream(sizeof(uint32_t));
1401
1402 auto DataLoc = tell();
1403 for (size_t Id = 0; Id < Val.Values.size(); ++Id) {
1404 auto &Item = Val.Values[Id];
1405 if (Item.isInt()) {
1406 auto Value = Item.getInt();
1407 RETURN_IF_ERROR(checkRCInt(Value, "VERSIONINFO integer value"));
1408 writeRCInt(Value);
1409 continue;
1410 }
1411
1412 bool WriteTerminator =
1413 Id == Val.Values.size() - 1 || Val.HasPrecedingComma[Id + 1];
1414 RETURN_IF_ERROR(writeCString(Item.getString(), WriteTerminator));
1415 }
1416
1417 auto CurLoc = tell();
1418 auto ValueLength = CurLoc - DataLoc;
1419 if (HasStrings) {
1420 assert(ValueLength % 2 == 0);
1421 ValueLength /= 2;
1422 }
1423 writeObjectAt(ulittle16_t(CurLoc - LengthLoc), LengthLoc);
1424 writeObjectAt(ulittle16_t(ValueLength), ValLengthLoc);
Zachary Turner93bb30d2017-10-06 21:26:06 +00001425 return Error::success();
1426}
1427
1428template <typename Ty>
1429static Ty getWithDefault(const StringMap<Ty> &Map, StringRef Key,
1430 const Ty &Default) {
1431 auto Iter = Map.find(Key);
1432 if (Iter != Map.end())
1433 return Iter->getValue();
1434 return Default;
1435}
1436
1437Error ResourceFileWriter::writeVersionInfoBody(const RCResource *Base) {
1438 auto *Res = cast<VersionInfoResource>(Base);
1439
1440 const auto &FixedData = Res->FixedData;
1441
1442 struct /* VS_FIXEDFILEINFO */ {
1443 ulittle32_t Signature = ulittle32_t(0xFEEF04BD);
1444 ulittle32_t StructVersion = ulittle32_t(0x10000);
1445 // It's weird to have most-significant DWORD first on the little-endian
1446 // machines, but let it be this way.
1447 ulittle32_t FileVersionMS;
1448 ulittle32_t FileVersionLS;
1449 ulittle32_t ProductVersionMS;
1450 ulittle32_t ProductVersionLS;
1451 ulittle32_t FileFlagsMask;
1452 ulittle32_t FileFlags;
1453 ulittle32_t FileOS;
1454 ulittle32_t FileType;
1455 ulittle32_t FileSubtype;
1456 // MS implementation seems to always set these fields to 0.
1457 ulittle32_t FileDateMS = ulittle32_t(0);
1458 ulittle32_t FileDateLS = ulittle32_t(0);
1459 } FixedInfo;
1460
1461 // First, VS_VERSIONINFO.
Zachary Turner88aa4b22017-10-06 23:21:43 +00001462 auto LengthLoc = writeInt<uint16_t>(0);
1463 writeInt<uint16_t>(sizeof(FixedInfo));
1464 writeInt<uint16_t>(0);
Zachary Turner93bb30d2017-10-06 21:26:06 +00001465 cantFail(writeCString("VS_VERSION_INFO"));
1466 padStream(sizeof(uint32_t));
1467
1468 using VersionInfoFixed = VersionInfoResource::VersionInfoFixed;
1469 auto GetField = [&](VersionInfoFixed::VersionInfoFixedType Type) {
1470 static const SmallVector<uint32_t, 4> DefaultOut{0, 0, 0, 0};
1471 if (!FixedData.IsTypePresent[(int)Type])
1472 return DefaultOut;
1473 return FixedData.FixedInfo[(int)Type];
1474 };
1475
1476 auto FileVer = GetField(VersionInfoFixed::FtFileVersion);
1477 RETURN_IF_ERROR(checkNumberFits<uint16_t>(
1478 *std::max_element(FileVer.begin(), FileVer.end()), "FILEVERSION fields"));
1479 FixedInfo.FileVersionMS = (FileVer[0] << 16) | FileVer[1];
1480 FixedInfo.FileVersionLS = (FileVer[2] << 16) | FileVer[3];
1481
1482 auto ProdVer = GetField(VersionInfoFixed::FtProductVersion);
1483 RETURN_IF_ERROR(checkNumberFits<uint16_t>(
1484 *std::max_element(ProdVer.begin(), ProdVer.end()),
1485 "PRODUCTVERSION fields"));
1486 FixedInfo.ProductVersionMS = (ProdVer[0] << 16) | ProdVer[1];
1487 FixedInfo.ProductVersionLS = (ProdVer[2] << 16) | ProdVer[3];
1488
1489 FixedInfo.FileFlagsMask = GetField(VersionInfoFixed::FtFileFlagsMask)[0];
1490 FixedInfo.FileFlags = GetField(VersionInfoFixed::FtFileFlags)[0];
1491 FixedInfo.FileOS = GetField(VersionInfoFixed::FtFileOS)[0];
1492 FixedInfo.FileType = GetField(VersionInfoFixed::FtFileType)[0];
1493 FixedInfo.FileSubtype = GetField(VersionInfoFixed::FtFileSubtype)[0];
1494
1495 writeObject(FixedInfo);
1496 padStream(sizeof(uint32_t));
1497
1498 RETURN_IF_ERROR(writeVersionInfoBlock(Res->MainBlock));
1499
1500 // FIXME: check overflow?
1501 writeObjectAt(ulittle16_t(tell() - LengthLoc), LengthLoc);
1502
1503 return Error::success();
1504}
1505
Zachary Turnere315d732017-10-11 20:12:09 +00001506Expected<std::unique_ptr<MemoryBuffer>>
1507ResourceFileWriter::loadFile(StringRef File) const {
1508 SmallString<128> Path;
1509 SmallString<128> Cwd;
1510 std::unique_ptr<MemoryBuffer> Result;
1511
Petr Hosek8c1c5682018-11-08 23:45:00 +00001512 // 0. The file path is absolute and the file exists.
1513 if (sys::path::is_absolute(File))
1514 return errorOrToExpected(MemoryBuffer::getFile(File, -1, false));
1515
Zachary Turnere315d732017-10-11 20:12:09 +00001516 // 1. The current working directory.
1517 sys::fs::current_path(Cwd);
1518 Path.assign(Cwd.begin(), Cwd.end());
1519 sys::path::append(Path, File);
1520 if (sys::fs::exists(Path))
Zachary Turnera398a712017-10-11 20:23:38 +00001521 return errorOrToExpected(MemoryBuffer::getFile(Path, -1, false));
Zachary Turnere315d732017-10-11 20:12:09 +00001522
1523 // 2. The directory of the input resource file, if it is different from the
Petr Hosek8c1c5682018-11-08 23:45:00 +00001524 // current working directory.
Zachary Turnere315d732017-10-11 20:12:09 +00001525 StringRef InputFileDir = sys::path::parent_path(Params.InputFilePath);
1526 Path.assign(InputFileDir.begin(), InputFileDir.end());
1527 sys::path::append(Path, File);
1528 if (sys::fs::exists(Path))
Zachary Turnera398a712017-10-11 20:23:38 +00001529 return errorOrToExpected(MemoryBuffer::getFile(Path, -1, false));
Zachary Turnere315d732017-10-11 20:12:09 +00001530
1531 // 3. All of the include directories specified on the command line.
1532 for (StringRef ForceInclude : Params.Include) {
1533 Path.assign(ForceInclude.begin(), ForceInclude.end());
1534 sys::path::append(Path, File);
1535 if (sys::fs::exists(Path))
Zachary Turnera398a712017-10-11 20:23:38 +00001536 return errorOrToExpected(MemoryBuffer::getFile(Path, -1, false));
Zachary Turnere315d732017-10-11 20:12:09 +00001537 }
1538
1539 if (auto Result =
1540 llvm::sys::Process::FindInEnvPath("INCLUDE", File, Params.NoInclude))
Zachary Turnera398a712017-10-11 20:23:38 +00001541 return errorOrToExpected(MemoryBuffer::getFile(*Result, -1, false));
Zachary Turnere315d732017-10-11 20:12:09 +00001542
1543 return make_error<StringError>("error : file not found : " + Twine(File),
1544 inconvertibleErrorCode());
1545}
1546
Marek Sokolowskie37621b2017-09-29 17:14:09 +00001547} // namespace rc
1548} // namespace llvm