1*8f19343aSMarek Sokolowski //===-- ResourceFileWriter.cpp --------------------------------*- C++-*-===//
2*8f19343aSMarek Sokolowski //
3*8f19343aSMarek Sokolowski //                     The LLVM Compiler Infrastructure
4*8f19343aSMarek Sokolowski //
5*8f19343aSMarek Sokolowski // This file is distributed under the University of Illinois Open Source
6*8f19343aSMarek Sokolowski // License. See LICENSE.TXT for details.
7*8f19343aSMarek Sokolowski //
8*8f19343aSMarek Sokolowski //===---------------------------------------------------------------------===//
9*8f19343aSMarek Sokolowski //
10*8f19343aSMarek Sokolowski // This implements the visitor serializing resources to a .res stream.
11*8f19343aSMarek Sokolowski //
12*8f19343aSMarek Sokolowski //===---------------------------------------------------------------------===//
13*8f19343aSMarek Sokolowski 
14*8f19343aSMarek Sokolowski #include "ResourceFileWriter.h"
15*8f19343aSMarek Sokolowski 
16*8f19343aSMarek Sokolowski #include "llvm/Object/WindowsResource.h"
17*8f19343aSMarek Sokolowski #include "llvm/Support/ConvertUTF.h"
18*8f19343aSMarek Sokolowski #include "llvm/Support/Endian.h"
19*8f19343aSMarek Sokolowski #include "llvm/Support/EndianStream.h"
20*8f19343aSMarek Sokolowski 
21*8f19343aSMarek Sokolowski using namespace llvm::support;
22*8f19343aSMarek Sokolowski 
23*8f19343aSMarek Sokolowski // Take an expression returning llvm::Error and forward the error if it exists.
24*8f19343aSMarek Sokolowski #define RETURN_IF_ERROR(Expr)                                                  \
25*8f19343aSMarek Sokolowski   if (auto Err = (Expr))                                                       \
26*8f19343aSMarek Sokolowski     return Err;
27*8f19343aSMarek Sokolowski 
28*8f19343aSMarek Sokolowski namespace llvm {
29*8f19343aSMarek Sokolowski namespace rc {
30*8f19343aSMarek Sokolowski 
31*8f19343aSMarek Sokolowski static Error createError(Twine Message,
32*8f19343aSMarek Sokolowski                          std::errc Type = std::errc::invalid_argument) {
33*8f19343aSMarek Sokolowski   return make_error<StringError>(Message, std::make_error_code(Type));
34*8f19343aSMarek Sokolowski }
35*8f19343aSMarek Sokolowski 
36*8f19343aSMarek Sokolowski static Error checkNumberFits(uint32_t Number, size_t MaxBits, Twine FieldName) {
37*8f19343aSMarek Sokolowski   assert(1 <= MaxBits && MaxBits <= 32);
38*8f19343aSMarek Sokolowski   if (!(Number >> MaxBits))
39*8f19343aSMarek Sokolowski     return Error::success();
40*8f19343aSMarek Sokolowski   return createError(FieldName + " (" + Twine(Number) + ") does not fit in " +
41*8f19343aSMarek Sokolowski                          Twine(MaxBits) + " bits.",
42*8f19343aSMarek Sokolowski                      std::errc::value_too_large);
43*8f19343aSMarek Sokolowski }
44*8f19343aSMarek Sokolowski 
45*8f19343aSMarek Sokolowski template <typename FitType>
46*8f19343aSMarek Sokolowski static Error checkNumberFits(uint32_t Number, Twine FieldName) {
47*8f19343aSMarek Sokolowski   return checkNumberFits(Number, sizeof(FitType) * 8, FieldName);
48*8f19343aSMarek Sokolowski }
49*8f19343aSMarek Sokolowski 
50*8f19343aSMarek Sokolowski static Error checkIntOrString(IntOrString Value, Twine FieldName) {
51*8f19343aSMarek Sokolowski   if (!Value.isInt())
52*8f19343aSMarek Sokolowski     return Error::success();
53*8f19343aSMarek Sokolowski   return checkNumberFits<uint16_t>(Value.getInt(), FieldName);
54*8f19343aSMarek Sokolowski }
55*8f19343aSMarek Sokolowski 
56*8f19343aSMarek Sokolowski static bool stripQuotes(StringRef &Str, bool &IsLongString) {
57*8f19343aSMarek Sokolowski   if (!Str.contains('"'))
58*8f19343aSMarek Sokolowski     return false;
59*8f19343aSMarek Sokolowski 
60*8f19343aSMarek Sokolowski   // Just take the contents of the string, checking if it's been marked long.
61*8f19343aSMarek Sokolowski   IsLongString = Str.startswith_lower("L");
62*8f19343aSMarek Sokolowski   if (IsLongString)
63*8f19343aSMarek Sokolowski     Str = Str.drop_front();
64*8f19343aSMarek Sokolowski 
65*8f19343aSMarek Sokolowski   bool StripSuccess = Str.consume_front("\"") && Str.consume_back("\"");
66*8f19343aSMarek Sokolowski   (void)StripSuccess;
67*8f19343aSMarek Sokolowski   assert(StripSuccess && "Strings should be enclosed in quotes.");
68*8f19343aSMarek Sokolowski   return true;
69*8f19343aSMarek Sokolowski }
70*8f19343aSMarek Sokolowski 
71*8f19343aSMarek Sokolowski // Describes a way to handle '\0' characters when processing the string.
72*8f19343aSMarek Sokolowski // rc.exe tool sometimes behaves in a weird way in postprocessing.
73*8f19343aSMarek Sokolowski // If the string to be output is equivalent to a C-string (e.g. in MENU
74*8f19343aSMarek Sokolowski // titles), string is (predictably) truncated after first 0-byte.
75*8f19343aSMarek Sokolowski // When outputting a string table, the behavior is equivalent to appending
76*8f19343aSMarek Sokolowski // '\0\0' at the end of the string, and then stripping the string
77*8f19343aSMarek Sokolowski // before the first '\0\0' occurrence.
78*8f19343aSMarek Sokolowski // Finally, when handling strings in user-defined resources, 0-bytes
79*8f19343aSMarek Sokolowski // aren't stripped, nor do they terminate the string.
80*8f19343aSMarek Sokolowski 
81*8f19343aSMarek Sokolowski enum class NullHandlingMethod {
82*8f19343aSMarek Sokolowski   UserResource,   // Don't terminate string on '\0'.
83*8f19343aSMarek Sokolowski   CutAtNull,      // Terminate string on '\0'.
84*8f19343aSMarek Sokolowski   CutAtDoubleNull // Terminate string on '\0\0'; strip final '\0'.
85*8f19343aSMarek Sokolowski };
86*8f19343aSMarek Sokolowski 
87*8f19343aSMarek Sokolowski // Parses an identifier or string and returns a processed version of it.
88*8f19343aSMarek Sokolowski // For now, it only strips the string boundaries, but TODO:
89*8f19343aSMarek Sokolowski //   * Squash "" to a single ".
90*8f19343aSMarek Sokolowski //   * Replace the escape sequences with their processed version.
91*8f19343aSMarek Sokolowski // For identifiers, this is no-op.
92*8f19343aSMarek Sokolowski static Error processString(StringRef Str, NullHandlingMethod NullHandler,
93*8f19343aSMarek Sokolowski                            bool &IsLongString, SmallVectorImpl<UTF16> &Result) {
94*8f19343aSMarek Sokolowski   assert(NullHandler == NullHandlingMethod::CutAtNull);
95*8f19343aSMarek Sokolowski 
96*8f19343aSMarek Sokolowski   bool IsString = stripQuotes(Str, IsLongString);
97*8f19343aSMarek Sokolowski   convertUTF8ToUTF16String(Str, Result);
98*8f19343aSMarek Sokolowski 
99*8f19343aSMarek Sokolowski   if (!IsString) {
100*8f19343aSMarek Sokolowski     // It's an identifier if it's not a string. Make all characters uppercase.
101*8f19343aSMarek Sokolowski     for (UTF16 &Ch : Result) {
102*8f19343aSMarek Sokolowski       assert(Ch <= 0x7F && "We didn't allow identifiers to be non-ASCII");
103*8f19343aSMarek Sokolowski       Ch = toupper(Ch);
104*8f19343aSMarek Sokolowski     }
105*8f19343aSMarek Sokolowski     return Error::success();
106*8f19343aSMarek Sokolowski   }
107*8f19343aSMarek Sokolowski 
108*8f19343aSMarek Sokolowski   // We don't process the string contents. Only cut at '\0'.
109*8f19343aSMarek Sokolowski 
110*8f19343aSMarek Sokolowski   for (size_t Pos = 0; Pos < Result.size(); ++Pos)
111*8f19343aSMarek Sokolowski     if (Result[Pos] == '\0')
112*8f19343aSMarek Sokolowski       Result.resize(Pos);
113*8f19343aSMarek Sokolowski 
114*8f19343aSMarek Sokolowski   return Error::success();
115*8f19343aSMarek Sokolowski }
116*8f19343aSMarek Sokolowski 
117*8f19343aSMarek Sokolowski uint64_t ResourceFileWriter::writeObject(const ArrayRef<uint8_t> Data) {
118*8f19343aSMarek Sokolowski   uint64_t Result = tell();
119*8f19343aSMarek Sokolowski   FS->write((const char *)Data.begin(), Data.size());
120*8f19343aSMarek Sokolowski   return Result;
121*8f19343aSMarek Sokolowski }
122*8f19343aSMarek Sokolowski 
123*8f19343aSMarek Sokolowski Error ResourceFileWriter::writeCString(StringRef Str, bool WriteTerminator) {
124*8f19343aSMarek Sokolowski   SmallVector<UTF16, 128> ProcessedString;
125*8f19343aSMarek Sokolowski   bool IsLongString;
126*8f19343aSMarek Sokolowski   RETURN_IF_ERROR(processString(Str, NullHandlingMethod::CutAtNull,
127*8f19343aSMarek Sokolowski                                 IsLongString, ProcessedString));
128*8f19343aSMarek Sokolowski   for (auto Ch : ProcessedString)
129*8f19343aSMarek Sokolowski     writeInt<uint16_t>(Ch);
130*8f19343aSMarek Sokolowski   if (WriteTerminator)
131*8f19343aSMarek Sokolowski     writeInt<uint16_t>(0);
132*8f19343aSMarek Sokolowski   return Error::success();
133*8f19343aSMarek Sokolowski }
134*8f19343aSMarek Sokolowski 
135*8f19343aSMarek Sokolowski Error ResourceFileWriter::writeIdentifier(const IntOrString &Ident) {
136*8f19343aSMarek Sokolowski   return writeIntOrString(Ident);
137*8f19343aSMarek Sokolowski }
138*8f19343aSMarek Sokolowski 
139*8f19343aSMarek Sokolowski Error ResourceFileWriter::writeIntOrString(const IntOrString &Value) {
140*8f19343aSMarek Sokolowski   if (!Value.isInt())
141*8f19343aSMarek Sokolowski     return writeCString(Value.getString());
142*8f19343aSMarek Sokolowski 
143*8f19343aSMarek Sokolowski   writeInt<uint16_t>(0xFFFF);
144*8f19343aSMarek Sokolowski   writeInt<uint16_t>(Value.getInt());
145*8f19343aSMarek Sokolowski   return Error::success();
146*8f19343aSMarek Sokolowski }
147*8f19343aSMarek Sokolowski 
148*8f19343aSMarek Sokolowski Error ResourceFileWriter::appendFile(StringRef Filename) {
149*8f19343aSMarek Sokolowski   bool IsLong;
150*8f19343aSMarek Sokolowski   stripQuotes(Filename, IsLong);
151*8f19343aSMarek Sokolowski 
152*8f19343aSMarek Sokolowski   // Filename path should be relative to the current working directory.
153*8f19343aSMarek Sokolowski   // FIXME: docs say so, but reality is more complicated, script
154*8f19343aSMarek Sokolowski   // location and include paths must be taken into account.
155*8f19343aSMarek Sokolowski   ErrorOr<std::unique_ptr<MemoryBuffer>> File =
156*8f19343aSMarek Sokolowski       MemoryBuffer::getFile(Filename, -1, false);
157*8f19343aSMarek Sokolowski   if (!File)
158*8f19343aSMarek Sokolowski     return make_error<StringError>("Error opening file '" + Filename +
159*8f19343aSMarek Sokolowski                                        "': " + File.getError().message(),
160*8f19343aSMarek Sokolowski                                    File.getError());
161*8f19343aSMarek Sokolowski   *FS << (*File)->getBuffer();
162*8f19343aSMarek Sokolowski   return Error::success();
163*8f19343aSMarek Sokolowski }
164*8f19343aSMarek Sokolowski 
165*8f19343aSMarek Sokolowski void ResourceFileWriter::padStream(uint64_t Length) {
166*8f19343aSMarek Sokolowski   assert(Length > 0);
167*8f19343aSMarek Sokolowski   uint64_t Location = tell();
168*8f19343aSMarek Sokolowski   Location %= Length;
169*8f19343aSMarek Sokolowski   uint64_t Pad = (Length - Location) % Length;
170*8f19343aSMarek Sokolowski   for (uint64_t i = 0; i < Pad; ++i)
171*8f19343aSMarek Sokolowski     writeInt<uint8_t>(0);
172*8f19343aSMarek Sokolowski }
173*8f19343aSMarek Sokolowski 
174*8f19343aSMarek Sokolowski Error ResourceFileWriter::handleError(Error &&Err, const RCResource *Res) {
175*8f19343aSMarek Sokolowski   if (Err)
176*8f19343aSMarek Sokolowski     return joinErrors(createError("Error in " + Res->getResourceTypeName() +
177*8f19343aSMarek Sokolowski                                   " statement (ID " + Twine(Res->ResName) +
178*8f19343aSMarek Sokolowski                                   "): "),
179*8f19343aSMarek Sokolowski                       std::move(Err));
180*8f19343aSMarek Sokolowski   return Error::success();
181*8f19343aSMarek Sokolowski }
182*8f19343aSMarek Sokolowski 
183*8f19343aSMarek Sokolowski Error ResourceFileWriter::visitNullResource(const RCResource *Res) {
184*8f19343aSMarek Sokolowski   return writeResource(Res, &ResourceFileWriter::writeNullBody);
185*8f19343aSMarek Sokolowski }
186*8f19343aSMarek Sokolowski 
187*8f19343aSMarek Sokolowski Error ResourceFileWriter::visitHTMLResource(const RCResource *Res) {
188*8f19343aSMarek Sokolowski   return writeResource(Res, &ResourceFileWriter::writeHTMLBody);
189*8f19343aSMarek Sokolowski }
190*8f19343aSMarek Sokolowski 
191*8f19343aSMarek Sokolowski Error ResourceFileWriter::visitLanguageStmt(const LanguageResource *Stmt) {
192*8f19343aSMarek Sokolowski   RETURN_IF_ERROR(checkNumberFits(Stmt->Lang, 10, "Primary language ID"));
193*8f19343aSMarek Sokolowski   RETURN_IF_ERROR(checkNumberFits(Stmt->SubLang, 6, "Sublanguage ID"));
194*8f19343aSMarek Sokolowski   ObjectData.LanguageInfo = Stmt->Lang | (Stmt->SubLang << 10);
195*8f19343aSMarek Sokolowski   return Error::success();
196*8f19343aSMarek Sokolowski }
197*8f19343aSMarek Sokolowski 
198*8f19343aSMarek Sokolowski Error ResourceFileWriter::writeResource(
199*8f19343aSMarek Sokolowski     const RCResource *Res,
200*8f19343aSMarek Sokolowski     Error (ResourceFileWriter::*BodyWriter)(const RCResource *)) {
201*8f19343aSMarek Sokolowski   // We don't know the sizes yet.
202*8f19343aSMarek Sokolowski   object::WinResHeaderPrefix HeaderPrefix{ulittle32_t(0U), ulittle32_t(0U)};
203*8f19343aSMarek Sokolowski   uint64_t HeaderLoc = writeObject(HeaderPrefix);
204*8f19343aSMarek Sokolowski 
205*8f19343aSMarek Sokolowski   auto ResType = Res->getResourceType();
206*8f19343aSMarek Sokolowski   RETURN_IF_ERROR(checkIntOrString(ResType, "Resource type"));
207*8f19343aSMarek Sokolowski   RETURN_IF_ERROR(checkIntOrString(Res->ResName, "Resource ID"));
208*8f19343aSMarek Sokolowski   RETURN_IF_ERROR(handleError(writeIdentifier(ResType), Res));
209*8f19343aSMarek Sokolowski   RETURN_IF_ERROR(handleError(writeIdentifier(Res->ResName), Res));
210*8f19343aSMarek Sokolowski 
211*8f19343aSMarek Sokolowski   padStream(sizeof(uint32_t));
212*8f19343aSMarek Sokolowski   object::WinResHeaderSuffix HeaderSuffix{
213*8f19343aSMarek Sokolowski       ulittle32_t(0), // DataVersion; seems to always be 0
214*8f19343aSMarek Sokolowski       ulittle16_t(Res->getMemoryFlags()), ulittle16_t(ObjectData.LanguageInfo),
215*8f19343aSMarek Sokolowski       ulittle32_t(0),  // VersionInfo
216*8f19343aSMarek Sokolowski       ulittle32_t(0)}; // Characteristics
217*8f19343aSMarek Sokolowski   writeObject(HeaderSuffix);
218*8f19343aSMarek Sokolowski 
219*8f19343aSMarek Sokolowski   uint64_t DataLoc = tell();
220*8f19343aSMarek Sokolowski   RETURN_IF_ERROR(handleError((this->*BodyWriter)(Res), Res));
221*8f19343aSMarek Sokolowski   // RETURN_IF_ERROR(handleError(dumpResource(Ctx)));
222*8f19343aSMarek Sokolowski 
223*8f19343aSMarek Sokolowski   // Update the sizes.
224*8f19343aSMarek Sokolowski   HeaderPrefix.DataSize = tell() - DataLoc;
225*8f19343aSMarek Sokolowski   HeaderPrefix.HeaderSize = DataLoc - HeaderLoc;
226*8f19343aSMarek Sokolowski   writeObjectAt(HeaderPrefix, HeaderLoc);
227*8f19343aSMarek Sokolowski   padStream(sizeof(uint32_t));
228*8f19343aSMarek Sokolowski 
229*8f19343aSMarek Sokolowski   return Error::success();
230*8f19343aSMarek Sokolowski }
231*8f19343aSMarek Sokolowski 
232*8f19343aSMarek Sokolowski Error ResourceFileWriter::writeNullBody(const RCResource *) {
233*8f19343aSMarek Sokolowski   return Error::success();
234*8f19343aSMarek Sokolowski }
235*8f19343aSMarek Sokolowski 
236*8f19343aSMarek Sokolowski Error ResourceFileWriter::writeHTMLBody(const RCResource *Base) {
237*8f19343aSMarek Sokolowski   return appendFile(cast<HTMLResource>(Base)->HTMLLoc);
238*8f19343aSMarek Sokolowski }
239*8f19343aSMarek Sokolowski 
240*8f19343aSMarek Sokolowski } // namespace rc
241*8f19343aSMarek Sokolowski } // namespace llvm
242