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