1// Copyright (C) 2018 The Qt Company Ltd.
2// Copyright (C) 2018 Intel Corporation.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
4
5#include "rcc.h"
6
7#include <qbytearray.h>
8#include <qdatetime.h>
9#include <qdebug.h>
10#include <qdir.h>
11#include <qdiriterator.h>
12#include <qfile.h>
13#include <qiodevice.h>
14#include <qlocale.h>
15#include <qstack.h>
16#include <qxmlstream.h>
17
18#include <algorithm>
19
20#if QT_CONFIG(zstd)
21# include <zstd.h>
22#endif
23
24// Note: A copy of this file is used in Qt Designer (qttools/src/designer/src/lib/shared/rcc.cpp)
25
26QT_BEGIN_NAMESPACE
27
28using namespace Qt::StringLiterals;
29
30enum {
31 CONSTANT_USENAMESPACE = 1,
32 CONSTANT_COMPRESSLEVEL_DEFAULT = -1,
33 CONSTANT_ZSTDCOMPRESSLEVEL_CHECK = 1, // Zstd level to check if compressing is a good idea
34 CONSTANT_ZSTDCOMPRESSLEVEL_STORE = 14, // Zstd level to actually store the data
35 CONSTANT_COMPRESSTHRESHOLD_DEFAULT = 70
36};
37
38void RCCResourceLibrary::write(const char *str, int len)
39{
40 int n = m_out.size();
41 m_out.resize(size: n + len);
42 memcpy(dest: m_out.data() + n, src: str, n: len);
43}
44
45void RCCResourceLibrary::writeByteArray(const QByteArray &other)
46{
47 if (m_format == Pass2) {
48 m_outDevice->write(data: other);
49 } else {
50 m_out.append(a: other);
51 }
52}
53
54static inline QString msgOpenReadFailed(const QString &fname, const QString &why)
55{
56 return QString::fromLatin1(ba: "Unable to open %1 for reading: %2\n").arg(args: fname, args: why);
57}
58
59
60///////////////////////////////////////////////////////////
61//
62// RCCFileInfo
63//
64///////////////////////////////////////////////////////////
65
66class RCCFileInfo
67{
68public:
69 enum Flags
70 {
71 // must match qresource.cpp
72 NoFlags = 0x00,
73 Compressed = 0x01,
74 Directory = 0x02,
75 CompressedZstd = 0x04
76 };
77
78
79 RCCFileInfo() = default;
80 RCCFileInfo(const QString &name, const QFileInfo &fileInfo, QLocale::Language language,
81 QLocale::Territory territory, uint flags,
82 RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel,
83 int compressThreshold, bool noZstd, bool isEmpty);
84
85 ~RCCFileInfo();
86 RCCFileInfo(const RCCFileInfo &) = delete;
87 RCCFileInfo &operator=(const RCCFileInfo &) = delete;
88 RCCFileInfo(RCCFileInfo &&) = default;
89 RCCFileInfo &operator=(RCCFileInfo &&other) = delete;
90
91 QString resourceName() const;
92
93public:
94 qint64 writeDataBlob(RCCResourceLibrary &lib, qint64 offset, QString *errorMessage);
95 qint64 writeDataName(RCCResourceLibrary &, qint64 offset);
96 void writeDataInfo(RCCResourceLibrary &lib);
97
98 int m_flags = NoFlags;
99 QLocale::Language m_language = QLocale::C;
100 QLocale::Territory m_territory = QLocale::AnyTerritory;
101 QString m_name;
102 QFileInfo m_fileInfo;
103 RCCFileInfo *m_parent = nullptr;
104 QMultiHash<QString, RCCFileInfo *> m_children;
105
106 RCCResourceLibrary::CompressionAlgorithm m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Best;
107 int m_compressLevel = CONSTANT_COMPRESSLEVEL_DEFAULT;
108 int m_compressThreshold = CONSTANT_COMPRESSTHRESHOLD_DEFAULT;
109 bool m_noZstd = false;
110 bool m_isEmpty = false;
111
112 qint64 m_nameOffset = 0;
113 qint64 m_dataOffset = 0;
114 qint64 m_childOffset = 0;
115};
116
117RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo, QLocale::Language language,
118 QLocale::Territory territory, uint flags,
119 RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel,
120 int compressThreshold, bool noZstd, bool isEmpty)
121 : m_flags(flags),
122 m_language(language),
123 m_territory(territory),
124 m_name(name),
125 m_fileInfo(fileInfo),
126 m_compressAlgo(compressAlgo),
127 m_compressLevel(compressLevel),
128 m_compressThreshold(compressThreshold),
129 m_noZstd(noZstd),
130 m_isEmpty(isEmpty)
131{
132}
133
134RCCFileInfo::~RCCFileInfo()
135{
136 qDeleteAll(c: m_children);
137}
138
139QString RCCFileInfo::resourceName() const
140{
141 QString resource = m_name;
142 for (RCCFileInfo *p = m_parent; p; p = p->m_parent)
143 resource = resource.prepend(s: p->m_name + u'/');
144 resource.prepend(c: u':');
145 return resource;
146}
147
148void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib)
149{
150 const bool text = lib.m_format == RCCResourceLibrary::C_Code;
151 const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1;
152 const bool python = lib.m_format == RCCResourceLibrary::Python_Code;
153 //some info
154 if (text || pass1) {
155 if (m_language != QLocale::C) {
156 lib.writeString(s: " // ");
157 lib.writeByteArray(other: resourceName().toLocal8Bit());
158 lib.writeString(s: " [");
159 lib.writeByteArray(other: QByteArray::number(m_territory));
160 lib.writeString(s: "::");
161 lib.writeByteArray(other: QByteArray::number(m_language));
162 lib.writeString(s: "[\n ");
163 } else {
164 lib.writeString(s: " // ");
165 lib.writeByteArray(other: resourceName().toLocal8Bit());
166 lib.writeString(s: "\n ");
167 }
168 }
169
170 //pointer data
171 if (m_flags & RCCFileInfo::Directory) {
172 // name offset
173 lib.writeNumber4(number: m_nameOffset);
174
175 // flags
176 lib.writeNumber2(number: m_flags);
177
178 // child count
179 lib.writeNumber4(number: m_children.size());
180
181 // first child offset
182 lib.writeNumber4(number: m_childOffset);
183 } else {
184 // name offset
185 lib.writeNumber4(number: m_nameOffset);
186
187 // flags
188 lib.writeNumber2(number: m_flags);
189
190 // locale
191 lib.writeNumber2(number: m_territory);
192 lib.writeNumber2(number: m_language);
193
194 //data offset
195 lib.writeNumber4(number: m_dataOffset);
196 }
197 if (text || pass1)
198 lib.writeChar(c: '\n');
199 else if (python)
200 lib.writeString(s: "\\\n");
201
202 if (lib.formatVersion() >= 2) {
203 // last modified time stamp
204 const QDateTime lastModified = m_fileInfo.lastModified(tz: QTimeZone::UTC);
205 quint64 lastmod = quint64(lastModified.isValid() ? lastModified.toMSecsSinceEpoch() : 0);
206 static const quint64 sourceDate = 1000 * qgetenv(varName: "QT_RCC_SOURCE_DATE_OVERRIDE").toULongLong();
207 if (sourceDate != 0)
208 lastmod = sourceDate;
209 static const quint64 sourceDate2 = 1000 * qgetenv(varName: "SOURCE_DATE_EPOCH").toULongLong();
210 if (sourceDate2 != 0)
211 lastmod = sourceDate2;
212 lib.writeNumber8(number: lastmod);
213 if (text || pass1)
214 lib.writeChar(c: '\n');
215 else if (python)
216 lib.writeString(s: "\\\n");
217 }
218}
219
220qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset,
221 QString *errorMessage)
222{
223 const bool text = lib.m_format == RCCResourceLibrary::C_Code;
224 const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1;
225 const bool pass2 = lib.m_format == RCCResourceLibrary::Pass2;
226 const bool binary = lib.m_format == RCCResourceLibrary::Binary;
227 const bool python = lib.m_format == RCCResourceLibrary::Python_Code;
228
229 //capture the offset
230 m_dataOffset = offset;
231 QByteArray data;
232
233 if (!m_isEmpty) {
234 //find the data to be written
235 QFile file(m_fileInfo.absoluteFilePath());
236 if (!file.open(flags: QFile::ReadOnly)) {
237 *errorMessage = msgOpenReadFailed(fname: m_fileInfo.absoluteFilePath(), why: file.errorString());
238 return 0;
239 }
240
241 data = file.readAll();
242 }
243
244 // Check if compression is useful for this file
245 if (data.size() != 0) {
246#if QT_CONFIG(zstd)
247 if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best && !m_noZstd) {
248 m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zstd;
249 m_compressLevel = 19; // not ZSTD_maxCLevel(), as 20+ are experimental
250 }
251 if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zstd && !m_noZstd) {
252 if (lib.m_zstdCCtx == nullptr)
253 lib.m_zstdCCtx = ZSTD_createCCtx();
254 qsizetype size = data.size();
255 size = ZSTD_COMPRESSBOUND(size);
256
257 int compressLevel = m_compressLevel;
258 if (compressLevel < 0)
259 compressLevel = CONSTANT_ZSTDCOMPRESSLEVEL_CHECK;
260
261 QByteArray compressed(size, Qt::Uninitialized);
262 char *dst = const_cast<char *>(compressed.constData());
263 size_t n = ZSTD_compressCCtx(cctx: lib.m_zstdCCtx, dst, dstCapacity: size,
264 src: data.constData(), srcSize: data.size(),
265 compressionLevel: compressLevel);
266 if (n * 100.0 < data.size() * 1.0 * (100 - m_compressThreshold) ) {
267 // compressing is worth it
268 if (m_compressLevel < 0) {
269 // heuristic compression, so recompress
270 n = ZSTD_compressCCtx(cctx: lib.m_zstdCCtx, dst, dstCapacity: size,
271 src: data.constData(), srcSize: data.size(),
272 compressionLevel: CONSTANT_ZSTDCOMPRESSLEVEL_STORE);
273 }
274 if (ZSTD_isError(code: n)) {
275 QString msg = QString::fromLatin1(ba: "%1: error: compression with zstd failed: %2\n")
276 .arg(args&: m_name, args: QString::fromUtf8(utf8: ZSTD_getErrorName(code: n)));
277 lib.m_errorDevice->write(data: msg.toUtf8());
278 } else if (lib.verbose()) {
279 QString msg = QString::fromLatin1(ba: "%1: note: compressed using zstd (%2 -> %3)\n")
280 .arg(a: m_name).arg(a: data.size()).arg(a: n);
281 lib.m_errorDevice->write(data: msg.toUtf8());
282 }
283
284 lib.m_overallFlags |= CompressedZstd;
285 m_flags |= CompressedZstd;
286 data = std::move(compressed);
287 data.truncate(pos: n);
288 } else if (lib.verbose()) {
289 QString msg = QString::fromLatin1(ba: "%1: note: not compressed\n").arg(a: m_name);
290 lib.m_errorDevice->write(data: msg.toUtf8());
291 }
292 }
293#endif
294#ifndef QT_NO_COMPRESS
295 if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best) {
296 m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zlib;
297 m_compressLevel = 9;
298 }
299 if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zlib) {
300 QByteArray compressed =
301 qCompress(data: reinterpret_cast<uchar *>(data.data()), nbytes: data.size(), compressionLevel: m_compressLevel);
302
303 int compressRatio = int(100.0 * (data.size() - compressed.size()) / data.size());
304 if (compressRatio >= m_compressThreshold) {
305 if (lib.verbose()) {
306 QString msg = QString::fromLatin1(ba: "%1: note: compressed using zlib (%2 -> %3)\n")
307 .arg(a: m_name).arg(a: data.size()).arg(a: compressed.size());
308 lib.m_errorDevice->write(data: msg.toUtf8());
309 }
310 data = compressed;
311 lib.m_overallFlags |= Compressed;
312 m_flags |= Compressed;
313 } else if (lib.verbose()) {
314 QString msg = QString::fromLatin1(ba: "%1: note: not compressed\n").arg(a: m_name);
315 lib.m_errorDevice->write(data: msg.toUtf8());
316 }
317 }
318#endif // QT_NO_COMPRESS
319 }
320
321 // some info
322 if (text || pass1) {
323 lib.writeString(s: " // ");
324 lib.writeByteArray(other: m_fileInfo.absoluteFilePath().toLocal8Bit());
325 lib.writeString(s: "\n ");
326 }
327
328 // write the length
329 if (text || binary || pass2 || python)
330 lib.writeNumber4(number: data.size());
331 if (text || pass1)
332 lib.writeString(s: "\n ");
333 else if (python)
334 lib.writeString(s: "\\\n");
335 offset += 4;
336
337 // write the payload
338 const char *p = data.constData();
339 if (text || python) {
340 for (int i = data.size(), j = 0; --i >= 0; --j) {
341 lib.writeHex(number: *p++);
342 if (j == 0) {
343 if (text)
344 lib.writeString(s: "\n ");
345 else
346 lib.writeString(s: "\\\n");
347 j = 16;
348 }
349 }
350 } else if (binary || pass2) {
351 lib.writeByteArray(other: data);
352 }
353 offset += data.size();
354
355 // done
356 if (text || pass1)
357 lib.writeString(s: "\n ");
358 else if (python)
359 lib.writeString(s: "\\\n");
360
361 return offset;
362}
363
364qint64 RCCFileInfo::writeDataName(RCCResourceLibrary &lib, qint64 offset)
365{
366 const bool text = lib.m_format == RCCResourceLibrary::C_Code;
367 const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1;
368 const bool python = lib.m_format == RCCResourceLibrary::Python_Code;
369
370 // capture the offset
371 m_nameOffset = offset;
372
373 // some info
374 if (text || pass1) {
375 lib.writeString(s: " // ");
376 lib.writeByteArray(other: m_name.toLocal8Bit());
377 lib.writeString(s: "\n ");
378 }
379
380 // write the length
381 lib.writeNumber2(number: m_name.size());
382 if (text || pass1)
383 lib.writeString(s: "\n ");
384 else if (python)
385 lib.writeString(s: "\\\n");
386 offset += 2;
387
388 // write the hash
389 lib.writeNumber4(number: qt_hash(key: m_name));
390 if (text || pass1)
391 lib.writeString(s: "\n ");
392 else if (python)
393 lib.writeString(s: "\\\n");
394 offset += 4;
395
396 // write the m_name
397 const QChar *unicode = m_name.unicode();
398 for (int i = 0; i < m_name.size(); ++i) {
399 lib.writeNumber2(number: unicode[i].unicode());
400 if ((text || pass1) && i % 16 == 0)
401 lib.writeString(s: "\n ");
402 else if (python && i % 16 == 0)
403 lib.writeString(s: "\\\n");
404 }
405 offset += m_name.size()*2;
406
407 // done
408 if (text || pass1)
409 lib.writeString(s: "\n ");
410 else if (python)
411 lib.writeString(s: "\\\n");
412
413 return offset;
414}
415
416
417///////////////////////////////////////////////////////////
418//
419// RCCResourceLibrary
420//
421///////////////////////////////////////////////////////////
422
423RCCResourceLibrary::Strings::Strings() :
424 TAG_RCC("RCC"_L1),
425 TAG_RESOURCE("qresource"_L1),
426 TAG_FILE("file"_L1),
427 ATTRIBUTE_LANG("lang"_L1),
428 ATTRIBUTE_PREFIX("prefix"_L1),
429 ATTRIBUTE_ALIAS("alias"_L1),
430 ATTRIBUTE_EMPTY("empty"_L1),
431 ATTRIBUTE_THRESHOLD("threshold"_L1),
432 ATTRIBUTE_COMPRESS("compress"_L1),
433 ATTRIBUTE_COMPRESSALGO(QStringLiteral("compression-algorithm"))
434{
435}
436
437RCCResourceLibrary::RCCResourceLibrary(quint8 formatVersion)
438 : m_root(nullptr),
439 m_format(C_Code),
440 m_verbose(false),
441 m_compressionAlgo(CompressionAlgorithm::Best),
442 m_compressLevel(CONSTANT_COMPRESSLEVEL_DEFAULT),
443 m_compressThreshold(CONSTANT_COMPRESSTHRESHOLD_DEFAULT),
444 m_treeOffset(0),
445 m_namesOffset(0),
446 m_dataOffset(0),
447 m_overallFlags(0),
448 m_useNameSpace(CONSTANT_USENAMESPACE),
449 m_errorDevice(nullptr),
450 m_outDevice(nullptr),
451 m_formatVersion(formatVersion),
452 m_noZstd(false)
453{
454 m_out.reserve(asize: 30 * 1000 * 1000);
455#if QT_CONFIG(zstd)
456 m_zstdCCtx = nullptr;
457#endif
458}
459
460RCCResourceLibrary::~RCCResourceLibrary()
461{
462 delete m_root;
463#if QT_CONFIG(zstd)
464 ZSTD_freeCCtx(cctx: m_zstdCCtx);
465#endif
466}
467
468enum RCCXmlTag {
469 RccTag,
470 ResourceTag,
471 FileTag
472};
473Q_DECLARE_TYPEINFO(RCCXmlTag, Q_PRIMITIVE_TYPE);
474
475static bool parseBoolean(QStringView value, QString *errorMsg)
476{
477 if (value.compare(s: "true"_L1, cs: Qt::CaseInsensitive) == 0)
478 return true;
479 if (value.compare(s: "false"_L1, cs: Qt::CaseInsensitive) == 0)
480 return false;
481
482 *errorMsg = QString::fromLatin1(ba: "Invalid value for boolean attribute: '%1'").arg(a: value);
483 return false;
484}
485
486bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice,
487 const QString &fname, QString currentPath, bool listMode)
488{
489 Q_ASSERT(m_errorDevice);
490 const QChar slash = u'/';
491 if (!currentPath.isEmpty() && !currentPath.endsWith(c: slash))
492 currentPath += slash;
493
494 QXmlStreamReader reader(inputDevice);
495 QStack<RCCXmlTag> tokens;
496
497 QString prefix;
498 QLocale::Language language = QLocale::c().language();
499 QLocale::Territory territory = QLocale::c().territory();
500 QString alias;
501 bool empty = false;
502 auto compressAlgo = m_compressionAlgo;
503 int compressLevel = m_compressLevel;
504 int compressThreshold = m_compressThreshold;
505
506 while (!reader.atEnd()) {
507 QXmlStreamReader::TokenType t = reader.readNext();
508 switch (t) {
509 case QXmlStreamReader::StartElement:
510 if (reader.name() == m_strings.TAG_RCC) {
511 if (!tokens.isEmpty())
512 reader.raiseError(message: "expected <RCC> tag"_L1);
513 else
514 tokens.push(t: RccTag);
515 } else if (reader.name() == m_strings.TAG_RESOURCE) {
516 if (tokens.isEmpty() || tokens.top() != RccTag) {
517 reader.raiseError(message: "unexpected <RESOURCE> tag"_L1);
518 } else {
519 tokens.push(t: ResourceTag);
520
521 QXmlStreamAttributes attributes = reader.attributes();
522 language = QLocale::c().language();
523 territory = QLocale::c().territory();
524
525 if (attributes.hasAttribute(qualifiedName: m_strings.ATTRIBUTE_LANG)) {
526 QString attribute = attributes.value(qualifiedName: m_strings.ATTRIBUTE_LANG).toString();
527 QLocale lang = QLocale(attribute);
528 language = lang.language();
529 if (2 == attribute.size()) {
530 // Language only
531 territory = QLocale::AnyTerritory;
532 } else {
533 territory = lang.territory();
534 }
535 }
536
537 prefix.clear();
538 if (attributes.hasAttribute(qualifiedName: m_strings.ATTRIBUTE_PREFIX))
539 prefix = attributes.value(qualifiedName: m_strings.ATTRIBUTE_PREFIX).toString();
540 if (!prefix.startsWith(c: slash))
541 prefix.prepend(c: slash);
542 if (!prefix.endsWith(c: slash))
543 prefix += slash;
544 }
545 } else if (reader.name() == m_strings.TAG_FILE) {
546 if (tokens.isEmpty() || tokens.top() != ResourceTag) {
547 reader.raiseError(message: "unexpected <FILE> tag"_L1);
548 } else {
549 tokens.push(t: FileTag);
550
551 QXmlStreamAttributes attributes = reader.attributes();
552 alias.clear();
553 if (attributes.hasAttribute(qualifiedName: m_strings.ATTRIBUTE_ALIAS))
554 alias = attributes.value(qualifiedName: m_strings.ATTRIBUTE_ALIAS).toString();
555
556 compressAlgo = m_compressionAlgo;
557 compressLevel = m_compressLevel;
558 compressThreshold = m_compressThreshold;
559
560 QString errorString;
561 if (attributes.hasAttribute(qualifiedName: m_strings.ATTRIBUTE_EMPTY))
562 empty = parseBoolean(value: attributes.value(qualifiedName: m_strings.ATTRIBUTE_EMPTY), errorMsg: &errorString);
563 else
564 empty = false;
565
566 if (attributes.hasAttribute(qualifiedName: m_strings.ATTRIBUTE_COMPRESSALGO))
567 compressAlgo = parseCompressionAlgorithm(algo: attributes.value(qualifiedName: m_strings.ATTRIBUTE_COMPRESSALGO), errorMsg: &errorString);
568 if (errorString.isEmpty() && attributes.hasAttribute(qualifiedName: m_strings.ATTRIBUTE_COMPRESS)) {
569 QString value = attributes.value(qualifiedName: m_strings.ATTRIBUTE_COMPRESS).toString();
570 compressLevel = parseCompressionLevel(algo: compressAlgo, level: value, errorMsg: &errorString);
571 }
572
573 // Special case for -no-compress
574 if (m_compressLevel == -2)
575 compressAlgo = CompressionAlgorithm::None;
576
577 if (attributes.hasAttribute(qualifiedName: m_strings.ATTRIBUTE_THRESHOLD))
578 compressThreshold = attributes.value(qualifiedName: m_strings.ATTRIBUTE_THRESHOLD).toString().toInt();
579
580 if (!errorString.isEmpty())
581 reader.raiseError(message: errorString);
582 }
583 } else {
584 reader.raiseError(message: "unexpected tag: %1"_L1.arg(args: reader.name().toString()));
585 }
586 break;
587
588 case QXmlStreamReader::EndElement:
589 if (reader.name() == m_strings.TAG_RCC) {
590 if (!tokens.isEmpty() && tokens.top() == RccTag)
591 tokens.pop();
592 else
593 reader.raiseError(message: "unexpected closing tag"_L1);
594 } else if (reader.name() == m_strings.TAG_RESOURCE) {
595 if (!tokens.isEmpty() && tokens.top() == ResourceTag)
596 tokens.pop();
597 else
598 reader.raiseError(message: "unexpected closing tag"_L1);
599 } else if (reader.name() == m_strings.TAG_FILE) {
600 if (!tokens.isEmpty() && tokens.top() == FileTag)
601 tokens.pop();
602 else
603 reader.raiseError(message: "unexpected closing tag"_L1);
604 }
605 break;
606
607 case QXmlStreamReader::Characters:
608 if (reader.isWhitespace())
609 break;
610 if (tokens.isEmpty() || tokens.top() != FileTag) {
611 reader.raiseError(message: "unexpected text"_L1);
612 } else {
613 QString fileName = reader.text().toString();
614 if (fileName.isEmpty()) {
615 const QString msg = QString::fromLatin1(ba: "RCC: Warning: Null node in XML of '%1'\n").arg(a: fname);
616 m_errorDevice->write(data: msg.toUtf8());
617 }
618
619 if (alias.isNull())
620 alias = fileName;
621
622 alias = QDir::cleanPath(path: alias);
623 while (alias.startsWith(s: "../"_L1))
624 alias.remove(i: 0, len: 3);
625 alias = QDir::cleanPath(path: m_resourceRoot) + prefix + alias;
626
627 QString absFileName = fileName;
628 if (QDir::isRelativePath(path: absFileName))
629 absFileName.prepend(s: currentPath);
630 QFileInfo file(absFileName);
631 if (file.isDir()) {
632 QDir dir(file.filePath());
633 if (!alias.endsWith(c: slash))
634 alias += slash;
635
636 QStringList filePaths;
637 QDirIterator it(dir, QDirIterator::FollowSymlinks|QDirIterator::Subdirectories);
638 while (it.hasNext()) {
639 it.next();
640 if (it.fileName() == "."_L1 || it.fileName() == ".."_L1)
641 continue;
642 filePaths.append(t: it.filePath());
643 }
644
645 // make rcc output deterministic
646 std::sort(first: filePaths.begin(), last: filePaths.end());
647
648 for (const QString &filePath : filePaths) {
649 QFileInfo child(filePath);
650 const bool arc =
651 addFile(alias: alias + child.fileName(),
652 file: RCCFileInfo(child.fileName(), child, language, territory,
653 child.isDir() ? RCCFileInfo::Directory
654 : RCCFileInfo::NoFlags,
655 compressAlgo, compressLevel, compressThreshold,
656 m_noZstd, empty));
657 if (!arc)
658 m_failedResources.push_back(t: child.fileName());
659 }
660 } else if (listMode || file.isFile()) {
661 const bool arc =
662 addFile(alias,
663 file: RCCFileInfo(alias.section(asep: slash, astart: -1),
664 file,
665 language,
666 territory,
667 RCCFileInfo::NoFlags,
668 compressAlgo,
669 compressLevel,
670 compressThreshold,
671 m_noZstd, empty)
672 );
673 if (!arc)
674 m_failedResources.push_back(t: absFileName);
675 } else if (file.exists()) {
676 m_failedResources.push_back(t: absFileName);
677 const QString msg = QString::fromLatin1(ba: "RCC: Error in '%1': Entry '%2' is neither a file nor a directory\n")
678 .arg(args: fname, args&: fileName);
679 m_errorDevice->write(data: msg.toUtf8());
680 return false;
681 } else {
682 m_failedResources.push_back(t: absFileName);
683 const QString msg = QString::fromLatin1(ba: "RCC: Error in '%1': Cannot find file '%2'\n")
684 .arg(args: fname, args&: fileName);
685 m_errorDevice->write(data: msg.toUtf8());
686 return false;
687 }
688 }
689 break;
690
691 default:
692 break;
693 }
694 }
695
696 if (reader.hasError()) {
697 int errorLine = reader.lineNumber();
698 int errorColumn = reader.columnNumber();
699 QString errorMessage = reader.errorString();
700 QString msg = QString::fromLatin1(ba: "RCC Parse Error: '%1' Line: %2 Column: %3 [%4]\n").arg(a: fname).arg(a: errorLine).arg(a: errorColumn).arg(a: errorMessage);
701 m_errorDevice->write(data: msg.toUtf8());
702 return false;
703 }
704
705 if (m_root == nullptr) {
706 const QString msg = QString::fromLatin1(ba: "RCC: Warning: No resources in '%1'.\n").arg(a: fname);
707 m_errorDevice->write(data: msg.toUtf8());
708 if (!listMode && m_format == Binary) {
709 // create dummy entry, otherwise loading with QResource will crash
710 m_root = new RCCFileInfo{};
711 m_root->m_flags = RCCFileInfo::Directory;
712 }
713 }
714
715 return true;
716}
717
718bool RCCResourceLibrary::addFile(const QString &alias, RCCFileInfo file)
719{
720 Q_ASSERT(m_errorDevice);
721 if (file.m_fileInfo.size() > 0xffffffff) {
722 const QString msg = QString::fromLatin1(ba: "File too big: %1\n").arg(a: file.m_fileInfo.absoluteFilePath());
723 m_errorDevice->write(data: msg.toUtf8());
724 return false;
725 }
726 if (!m_root) {
727 m_root = new RCCFileInfo{};
728 m_root->m_flags = RCCFileInfo::Directory;
729 }
730
731 RCCFileInfo *parent = m_root;
732 const QStringList nodes = alias.split(sep: u'/');
733 for (int i = 1; i < nodes.size()-1; ++i) {
734 const QString node = nodes.at(i);
735 if (node.isEmpty())
736 continue;
737 if (!parent->m_children.contains(key: node)) {
738 RCCFileInfo *s = new RCCFileInfo{};
739 s->m_name = node;
740 s->m_flags = RCCFileInfo::Directory;
741 s->m_parent = parent;
742 parent->m_children.insert(key: node, value: s);
743 parent = s;
744 } else {
745 parent = *parent->m_children.constFind(key: node);
746 }
747 }
748
749 const QString filename = nodes.at(i: nodes.size()-1);
750 RCCFileInfo *s = new RCCFileInfo(std::move(file));
751 s->m_parent = parent;
752 auto cbegin = parent->m_children.constFind(key: filename);
753 auto cend = parent->m_children.constEnd();
754 for (auto it = cbegin; it != cend; ++it) {
755 if (it.key() == filename && it.value()->m_language == s->m_language &&
756 it.value()->m_territory == s->m_territory) {
757 for (const QString &name : std::as_const(t&: m_fileNames)) {
758 qWarning(msg: "%s: Warning: potential duplicate alias detected: '%s'",
759 qPrintable(name), qPrintable(filename));
760 }
761 break;
762 }
763 }
764 parent->m_children.insert(key: filename, value: s);
765 return true;
766}
767
768void RCCResourceLibrary::reset()
769{
770 if (m_root) {
771 delete m_root;
772 m_root = nullptr;
773 }
774 m_errorDevice = nullptr;
775 m_failedResources.clear();
776}
777
778
779bool RCCResourceLibrary::readFiles(bool listMode, QIODevice &errorDevice)
780{
781 reset();
782 m_errorDevice = &errorDevice;
783 //read in data
784 if (m_verbose) {
785 const QString msg = QString::fromLatin1(ba: "Processing %1 files [listMode=%2]\n")
786 .arg(a: m_fileNames.size()).arg(a: static_cast<int>(listMode));
787 m_errorDevice->write(data: msg.toUtf8());
788 }
789 for (int i = 0; i < m_fileNames.size(); ++i) {
790 QFile fileIn;
791 QString fname = m_fileNames.at(i);
792 QString pwd;
793 if (fname == "-"_L1) {
794 fname = "(stdin)"_L1;
795 pwd = QDir::currentPath();
796 fileIn.setFileName(fname);
797 if (!fileIn.open(stdin, ioFlags: QIODevice::ReadOnly)) {
798 m_errorDevice->write(data: msgOpenReadFailed(fname, why: fileIn.errorString()).toUtf8());
799 return false;
800 }
801 } else {
802 pwd = QFileInfo(fname).path();
803 fileIn.setFileName(fname);
804 if (!fileIn.open(flags: QIODevice::ReadOnly)) {
805 m_errorDevice->write(data: msgOpenReadFailed(fname, why: fileIn.errorString()).toUtf8());
806 return false;
807 }
808 }
809 if (m_verbose) {
810 const QString msg = QString::fromLatin1(ba: "Interpreting %1\n").arg(a: fname);
811 m_errorDevice->write(data: msg.toUtf8());
812 }
813
814 if (!interpretResourceFile(inputDevice: &fileIn, fname, currentPath: pwd, listMode))
815 return false;
816 }
817 return true;
818}
819
820QStringList RCCResourceLibrary::dataFiles() const
821{
822 QStringList ret;
823 QStack<RCCFileInfo*> pending;
824
825 if (!m_root)
826 return ret;
827 pending.push(t: m_root);
828 while (!pending.isEmpty()) {
829 RCCFileInfo *file = pending.pop();
830 for (auto it = file->m_children.begin();
831 it != file->m_children.end(); ++it) {
832 RCCFileInfo *child = it.value();
833 if (child->m_flags & RCCFileInfo::Directory)
834 pending.push(t: child);
835 else
836 ret.append(t: child->m_fileInfo.filePath());
837 }
838 }
839 return ret;
840}
841
842// Determine map of resource identifier (':/newPrefix/images/p1.png') to file via recursion
843static void resourceDataFileMapRecursion(const RCCFileInfo *m_root, const QString &path, RCCResourceLibrary::ResourceDataFileMap &m)
844{
845 const QChar slash = u'/';
846 const auto cend = m_root->m_children.constEnd();
847 for (auto it = m_root->m_children.constBegin(); it != cend; ++it) {
848 const RCCFileInfo *child = it.value();
849 const QString childName = path + slash + child->m_name;
850 if (child->m_flags & RCCFileInfo::Directory) {
851 resourceDataFileMapRecursion(m_root: child, path: childName, m);
852 } else {
853 m.insert(key: childName, value: child->m_fileInfo.filePath());
854 }
855 }
856}
857
858RCCResourceLibrary::ResourceDataFileMap RCCResourceLibrary::resourceDataFileMap() const
859{
860 ResourceDataFileMap rc;
861 if (m_root)
862 resourceDataFileMapRecursion(m_root, path: QString(u':'), m&: rc);
863 return rc;
864}
865
866RCCResourceLibrary::CompressionAlgorithm RCCResourceLibrary::parseCompressionAlgorithm(QStringView value, QString *errorMsg)
867{
868 if (value == "best"_L1)
869 return CompressionAlgorithm::Best;
870 if (value == "zlib"_L1) {
871#ifdef QT_NO_COMPRESS
872 *errorMsg = "zlib support not compiled in"_L1;
873#else
874 return CompressionAlgorithm::Zlib;
875#endif
876 } else if (value == "zstd"_L1) {
877#if QT_CONFIG(zstd)
878 return CompressionAlgorithm::Zstd;
879#else
880 *errorMsg = "Zstandard support not compiled in"_L1;
881#endif
882 } else if (value != "none"_L1) {
883 *errorMsg = QString::fromLatin1(ba: "Unknown compression algorithm '%1'").arg(a: value);
884 }
885
886 return CompressionAlgorithm::None;
887}
888
889int RCCResourceLibrary::parseCompressionLevel(CompressionAlgorithm algo, const QString &level, QString *errorMsg)
890{
891 bool ok;
892 int c = level.toInt(ok: &ok);
893 if (ok) {
894 switch (algo) {
895 case CompressionAlgorithm::None:
896 case CompressionAlgorithm::Best:
897 return 0;
898 case CompressionAlgorithm::Zlib:
899 if (c >= 1 && c <= 9)
900 return c;
901 break;
902 case CompressionAlgorithm::Zstd:
903#if QT_CONFIG(zstd)
904 if (c >= 0 && c <= ZSTD_maxCLevel())
905 return c;
906#endif
907 break;
908 }
909 }
910
911 *errorMsg = QString::fromLatin1(ba: "invalid compression level '%1'").arg(a: level);
912 return 0;
913}
914
915bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &tempDevice, QIODevice &errorDevice)
916{
917 m_errorDevice = &errorDevice;
918
919 if (m_format == Pass2) {
920 const char pattern[] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' };
921 bool foundSignature = false;
922
923 while (true) {
924 char c;
925 for (int i = 0; i < 8; ) {
926 if (!tempDevice.getChar(c: &c)) {
927 if (foundSignature)
928 return true;
929 m_errorDevice->write(data: "No data signature found\n");
930 return false;
931 }
932 if (c == pattern[i]) {
933 ++i;
934 } else {
935 for (int k = 0; k < i; ++k)
936 outDevice.putChar(c: pattern[k]);
937 outDevice.putChar(c);
938 i = 0;
939 }
940 }
941
942 m_outDevice = &outDevice;
943 quint64 start = outDevice.pos();
944 writeDataBlobs();
945 quint64 len = outDevice.pos() - start;
946
947 tempDevice.seek(pos: tempDevice.pos() + len - 8);
948 foundSignature = true;
949 }
950 }
951
952 //write out
953 if (m_verbose)
954 m_errorDevice->write(data: "Outputting code\n");
955 if (!writeHeader()) {
956 m_errorDevice->write(data: "Could not write header\n");
957 return false;
958 }
959 if (m_root) {
960 if (!writeDataBlobs()) {
961 m_errorDevice->write(data: "Could not write data blobs.\n");
962 return false;
963 }
964 if (!writeDataNames()) {
965 m_errorDevice->write(data: "Could not write file names\n");
966 return false;
967 }
968 if (!writeDataStructure()) {
969 m_errorDevice->write(data: "Could not write data tree\n");
970 return false;
971 }
972 }
973 if (!writeInitializer()) {
974 m_errorDevice->write(data: "Could not write footer\n");
975 return false;
976 }
977 outDevice.write(data: m_out.constData(), len: m_out.size());
978 return true;
979}
980
981void RCCResourceLibrary::writeDecimal(int value)
982{
983 Q_ASSERT(m_format != RCCResourceLibrary::Binary);
984 char buf[std::numeric_limits<int>::digits10 + 2];
985 int n = snprintf(s: buf, maxlen: sizeof(buf), format: "%d", value);
986 write(str: buf, len: n);
987}
988
989static const char hexDigits[] = "0123456789abcdef";
990
991inline void RCCResourceLibrary::write2HexDigits(quint8 number)
992{
993 writeChar(c: hexDigits[number >> 4]);
994 writeChar(c: hexDigits[number & 0xf]);
995}
996
997void RCCResourceLibrary::writeHex(quint8 tmp)
998{
999 switch (m_format) {
1000 case RCCResourceLibrary::Python_Code:
1001 if (tmp >= 32 && tmp < 127 && tmp != '"' && tmp != '\\') {
1002 writeChar(c: char(tmp));
1003 } else {
1004 writeChar(c: '\\');
1005 writeChar(c: 'x');
1006 write2HexDigits(number: tmp);
1007 }
1008 break;
1009 default:
1010 writeChar(c: '0');
1011 writeChar(c: 'x');
1012 if (tmp < 16)
1013 writeChar(c: hexDigits[tmp]);
1014 else
1015 write2HexDigits(number: tmp);
1016 writeChar(c: ',');
1017 break;
1018 }
1019}
1020
1021void RCCResourceLibrary::writeNumber2(quint16 number)
1022{
1023 if (m_format == RCCResourceLibrary::Binary) {
1024 writeChar(c: number >> 8);
1025 writeChar(c: number);
1026 } else {
1027 writeHex(tmp: number >> 8);
1028 writeHex(tmp: number);
1029 }
1030}
1031
1032void RCCResourceLibrary::writeNumber4(quint32 number)
1033{
1034 if (m_format == RCCResourceLibrary::Pass2) {
1035 m_outDevice->putChar(c: char(number >> 24));
1036 m_outDevice->putChar(c: char(number >> 16));
1037 m_outDevice->putChar(c: char(number >> 8));
1038 m_outDevice->putChar(c: char(number));
1039 } else if (m_format == RCCResourceLibrary::Binary) {
1040 writeChar(c: number >> 24);
1041 writeChar(c: number >> 16);
1042 writeChar(c: number >> 8);
1043 writeChar(c: number);
1044 } else {
1045 writeHex(tmp: number >> 24);
1046 writeHex(tmp: number >> 16);
1047 writeHex(tmp: number >> 8);
1048 writeHex(tmp: number);
1049 }
1050}
1051
1052void RCCResourceLibrary::writeNumber8(quint64 number)
1053{
1054 if (m_format == RCCResourceLibrary::Pass2) {
1055 m_outDevice->putChar(c: char(number >> 56));
1056 m_outDevice->putChar(c: char(number >> 48));
1057 m_outDevice->putChar(c: char(number >> 40));
1058 m_outDevice->putChar(c: char(number >> 32));
1059 m_outDevice->putChar(c: char(number >> 24));
1060 m_outDevice->putChar(c: char(number >> 16));
1061 m_outDevice->putChar(c: char(number >> 8));
1062 m_outDevice->putChar(c: char(number));
1063 } else if (m_format == RCCResourceLibrary::Binary) {
1064 writeChar(c: number >> 56);
1065 writeChar(c: number >> 48);
1066 writeChar(c: number >> 40);
1067 writeChar(c: number >> 32);
1068 writeChar(c: number >> 24);
1069 writeChar(c: number >> 16);
1070 writeChar(c: number >> 8);
1071 writeChar(c: number);
1072 } else {
1073 writeHex(tmp: number >> 56);
1074 writeHex(tmp: number >> 48);
1075 writeHex(tmp: number >> 40);
1076 writeHex(tmp: number >> 32);
1077 writeHex(tmp: number >> 24);
1078 writeHex(tmp: number >> 16);
1079 writeHex(tmp: number >> 8);
1080 writeHex(tmp: number);
1081 }
1082}
1083
1084bool RCCResourceLibrary::writeHeader()
1085{
1086 switch (m_format) {
1087 case C_Code:
1088 case Pass1:
1089 writeString(s: "/****************************************************************************\n");
1090 writeString(s: "** Resource object code\n");
1091 writeString(s: "**\n");
1092 writeString(s: "** Created by: The Resource Compiler for Qt version ");
1093 writeByteArray(QT_VERSION_STR);
1094 writeString(s: "\n**\n");
1095 writeString(s: "** WARNING! All changes made in this file will be lost!\n");
1096 writeString( s: "*****************************************************************************/\n\n");
1097 break;
1098 case Python_Code:
1099 writeString(s: "# Resource object code (Python 3)\n");
1100 writeString(s: "# Created by: object code\n");
1101 writeString(s: "# Created by: The Resource Compiler for Qt version ");
1102 writeByteArray(QT_VERSION_STR);
1103 writeString(s: "\n");
1104 writeString(s: "# WARNING! All changes made in this file will be lost!\n\n");
1105 writeString(s: "from PySide");
1106 writeByteArray(other: QByteArray::number(QT_VERSION_MAJOR));
1107 writeString(s: " import QtCore\n\n");
1108 break;
1109 case Binary:
1110 writeString(s: "qres");
1111 writeNumber4(number: 0);
1112 writeNumber4(number: 0);
1113 writeNumber4(number: 0);
1114 writeNumber4(number: 0);
1115 if (m_formatVersion >= 3)
1116 writeNumber4(number: m_overallFlags);
1117 break;
1118 default:
1119 break;
1120 }
1121 return true;
1122}
1123
1124bool RCCResourceLibrary::writeDataBlobs()
1125{
1126 Q_ASSERT(m_errorDevice);
1127 switch (m_format) {
1128 case C_Code:
1129 writeString(s: "static const unsigned char qt_resource_data[] = {\n");
1130 break;
1131 case Python_Code:
1132 writeString(s: "qt_resource_data = b\"\\\n");
1133 break;
1134 case Binary:
1135 m_dataOffset = m_out.size();
1136 break;
1137 default:
1138 break;
1139 }
1140
1141 if (!m_root)
1142 return false;
1143
1144 QStack<RCCFileInfo*> pending;
1145 pending.push(t: m_root);
1146 qint64 offset = 0;
1147 QString errorMessage;
1148 while (!pending.isEmpty()) {
1149 RCCFileInfo *file = pending.pop();
1150 for (auto it = file->m_children.cbegin(); it != file->m_children.cend(); ++it) {
1151 RCCFileInfo *child = it.value();
1152 if (child->m_flags & RCCFileInfo::Directory)
1153 pending.push(t: child);
1154 else {
1155 offset = child->writeDataBlob(lib&: *this, offset, errorMessage: &errorMessage);
1156 if (offset == 0) {
1157 m_errorDevice->write(data: errorMessage.toUtf8());
1158 return false;
1159 }
1160 }
1161 }
1162 }
1163 switch (m_format) {
1164 case C_Code:
1165 writeString(s: "\n};\n\n");
1166 break;
1167 case Python_Code:
1168 writeString(s: "\"\n\n");
1169 break;
1170 case Pass1:
1171 if (offset < 8)
1172 offset = 8;
1173 writeString(s: "\nstatic const unsigned char qt_resource_data[");
1174 writeByteArray(other: QByteArray::number(offset));
1175 writeString(s: "] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' };\n\n");
1176 break;
1177 default:
1178 break;
1179 }
1180 return true;
1181}
1182
1183bool RCCResourceLibrary::writeDataNames()
1184{
1185 switch (m_format) {
1186 case C_Code:
1187 case Pass1:
1188 writeString(s: "static const unsigned char qt_resource_name[] = {\n");
1189 break;
1190 case Python_Code:
1191 writeString(s: "qt_resource_name = b\"\\\n");
1192 break;
1193 case Binary:
1194 m_namesOffset = m_out.size();
1195 break;
1196 default:
1197 break;
1198 }
1199
1200 QHash<QString, int> names;
1201 QStack<RCCFileInfo*> pending;
1202
1203 if (!m_root)
1204 return false;
1205
1206 pending.push(t: m_root);
1207 qint64 offset = 0;
1208 while (!pending.isEmpty()) {
1209 RCCFileInfo *file = pending.pop();
1210 for (auto it = file->m_children.cbegin(); it != file->m_children.cend(); ++it) {
1211 RCCFileInfo *child = it.value();
1212 if (child->m_flags & RCCFileInfo::Directory)
1213 pending.push(t: child);
1214 if (names.contains(key: child->m_name)) {
1215 child->m_nameOffset = names.value(key: child->m_name);
1216 } else {
1217 names.insert(key: child->m_name, value: offset);
1218 offset = child->writeDataName(lib&: *this, offset);
1219 }
1220 }
1221 }
1222 switch (m_format) {
1223 case C_Code:
1224 case Pass1:
1225 writeString(s: "\n};\n\n");
1226 break;
1227 case Python_Code:
1228 writeString(s: "\"\n\n");
1229 break;
1230 default:
1231 break;
1232 }
1233 return true;
1234}
1235
1236struct qt_rcc_compare_hash
1237{
1238 typedef bool result_type;
1239 result_type operator()(const RCCFileInfo *left, const RCCFileInfo *right) const
1240 {
1241 return qt_hash(key: left->m_name) < qt_hash(key: right->m_name);
1242 }
1243};
1244
1245bool RCCResourceLibrary::writeDataStructure()
1246{
1247 switch (m_format) {
1248 case C_Code:
1249 case Pass1:
1250 writeString(s: "static const unsigned char qt_resource_struct[] = {\n");
1251 break;
1252 case Python_Code:
1253 writeString(s: "qt_resource_struct = b\"\\\n");
1254 break;
1255 case Binary:
1256 m_treeOffset = m_out.size();
1257 break;
1258 default:
1259 break;
1260 }
1261
1262 QStack<RCCFileInfo*> pending;
1263
1264 if (!m_root)
1265 return false;
1266
1267 //calculate the child offsets (flat)
1268 pending.push(t: m_root);
1269 int offset = 1;
1270 while (!pending.isEmpty()) {
1271 RCCFileInfo *file = pending.pop();
1272 file->m_childOffset = offset;
1273
1274 //sort by hash value for binary lookup
1275 QList<RCCFileInfo*> m_children = file->m_children.values();
1276 std::sort(first: m_children.begin(), last: m_children.end(), comp: qt_rcc_compare_hash());
1277
1278 //write out the actual data now
1279 for (int i = 0; i < m_children.size(); ++i) {
1280 RCCFileInfo *child = m_children.at(i);
1281 ++offset;
1282 if (child->m_flags & RCCFileInfo::Directory)
1283 pending.push(t: child);
1284 }
1285 }
1286
1287 //write out the structure (ie iterate again!)
1288 pending.push(t: m_root);
1289 m_root->writeDataInfo(lib&: *this);
1290 while (!pending.isEmpty()) {
1291 RCCFileInfo *file = pending.pop();
1292
1293 //sort by hash value for binary lookup
1294 QList<RCCFileInfo*> m_children = file->m_children.values();
1295 std::sort(first: m_children.begin(), last: m_children.end(), comp: qt_rcc_compare_hash());
1296
1297 //write out the actual data now
1298 for (int i = 0; i < m_children.size(); ++i) {
1299 RCCFileInfo *child = m_children.at(i);
1300 child->writeDataInfo(lib&: *this);
1301 if (child->m_flags & RCCFileInfo::Directory)
1302 pending.push(t: child);
1303 }
1304 }
1305 switch (m_format) {
1306 case C_Code:
1307 case Pass1:
1308 writeString(s: "\n};\n\n");
1309 break;
1310 case Python_Code:
1311 writeString(s: "\"\n\n");
1312 break;
1313 default:
1314 break;
1315 }
1316
1317 return true;
1318}
1319
1320void RCCResourceLibrary::writeMangleNamespaceFunction(const QByteArray &name)
1321{
1322 if (m_useNameSpace) {
1323 writeString(s: "QT_RCC_MANGLE_NAMESPACE(");
1324 writeByteArray(other: name);
1325 writeChar(c: ')');
1326 } else {
1327 writeByteArray(other: name);
1328 }
1329}
1330
1331void RCCResourceLibrary::writeAddNamespaceFunction(const QByteArray &name)
1332{
1333 if (m_useNameSpace) {
1334 writeString(s: "QT_RCC_PREPEND_NAMESPACE(");
1335 writeByteArray(other: name);
1336 writeChar(c: ')');
1337 } else {
1338 writeByteArray(other: name);
1339 }
1340}
1341
1342bool RCCResourceLibrary::writeInitializer()
1343{
1344 if (m_format == C_Code || m_format == Pass1) {
1345 //write("\nQT_BEGIN_NAMESPACE\n");
1346 QString initNameStr = m_initName;
1347 if (!initNameStr.isEmpty()) {
1348 initNameStr.prepend(c: u'_');
1349 auto isAsciiLetterOrNumber = [] (QChar c) -> bool {
1350 ushort ch = c.unicode();
1351 return (ch >= '0' && ch <= '9') ||
1352 (ch >= 'A' && ch <= 'Z') ||
1353 (ch >= 'a' && ch <= 'z') ||
1354 ch == '_';
1355 };
1356 for (QChar &c : initNameStr) {
1357 if (!isAsciiLetterOrNumber(c))
1358 c = u'_';
1359 }
1360 }
1361 QByteArray initName = initNameStr.toLatin1();
1362
1363 //init
1364 if (m_useNameSpace) {
1365 writeString(s: "#ifdef QT_NAMESPACE\n"
1366 "# define QT_RCC_PREPEND_NAMESPACE(name) ::QT_NAMESPACE::name\n"
1367 "# define QT_RCC_MANGLE_NAMESPACE0(x) x\n"
1368 "# define QT_RCC_MANGLE_NAMESPACE1(a, b) a##_##b\n"
1369 "# define QT_RCC_MANGLE_NAMESPACE2(a, b) QT_RCC_MANGLE_NAMESPACE1(a,b)\n"
1370 "# define QT_RCC_MANGLE_NAMESPACE(name) QT_RCC_MANGLE_NAMESPACE2( \\\n"
1371 " QT_RCC_MANGLE_NAMESPACE0(name), QT_RCC_MANGLE_NAMESPACE0(QT_NAMESPACE))\n"
1372 "#else\n"
1373 "# define QT_RCC_PREPEND_NAMESPACE(name) name\n"
1374 "# define QT_RCC_MANGLE_NAMESPACE(name) name\n"
1375 "#endif\n\n");
1376
1377 writeString(s: "#ifdef QT_NAMESPACE\n"
1378 "namespace QT_NAMESPACE {\n"
1379 "#endif\n\n");
1380 }
1381
1382 if (m_root) {
1383 writeString(s: "bool qRegisterResourceData"
1384 "(int, const unsigned char *, "
1385 "const unsigned char *, const unsigned char *);\n");
1386 writeString(s: "bool qUnregisterResourceData"
1387 "(int, const unsigned char *, "
1388 "const unsigned char *, const unsigned char *);\n\n");
1389
1390 if (m_overallFlags & (RCCFileInfo::Compressed | RCCFileInfo::CompressedZstd)) {
1391 // use variable relocations with ELF and Mach-O
1392 writeString(s: "#if defined(__ELF__) || defined(__APPLE__)\n");
1393 if (m_overallFlags & RCCFileInfo::Compressed) {
1394 writeString(s: "static inline unsigned char qResourceFeatureZlib()\n"
1395 "{\n"
1396 " extern const unsigned char qt_resourceFeatureZlib;\n"
1397 " return qt_resourceFeatureZlib;\n"
1398 "}\n");
1399 }
1400 if (m_overallFlags & RCCFileInfo::CompressedZstd) {
1401 writeString(s: "static inline unsigned char qResourceFeatureZstd()\n"
1402 "{\n"
1403 " extern const unsigned char qt_resourceFeatureZstd;\n"
1404 " return qt_resourceFeatureZstd;\n"
1405 "}\n");
1406 }
1407 writeString(s: "#else\n");
1408 if (m_overallFlags & RCCFileInfo::Compressed)
1409 writeString(s: "unsigned char qResourceFeatureZlib();\n");
1410 if (m_overallFlags & RCCFileInfo::CompressedZstd)
1411 writeString(s: "unsigned char qResourceFeatureZstd();\n");
1412 writeString(s: "#endif\n\n");
1413 }
1414 }
1415
1416 if (m_useNameSpace)
1417 writeString(s: "#ifdef QT_NAMESPACE\n}\n#endif\n\n");
1418
1419 QByteArray initResources = "qInitResources";
1420 initResources += initName;
1421
1422 // Work around -Wmissing-declarations warnings.
1423 writeString(s: "int ");
1424 writeMangleNamespaceFunction(name: initResources);
1425 writeString(s: "();\n");
1426
1427 writeString(s: "int ");
1428 writeMangleNamespaceFunction(name: initResources);
1429 writeString(s: "()\n{\n");
1430
1431 if (m_root) {
1432 writeString(s: " int version = ");
1433 writeDecimal(value: m_formatVersion);
1434 writeString(s: ";\n ");
1435 writeAddNamespaceFunction(name: "qRegisterResourceData");
1436 writeString(s: "\n (version, qt_resource_struct, "
1437 "qt_resource_name, qt_resource_data);\n");
1438 }
1439 writeString(s: " return 1;\n");
1440 writeString(s: "}\n\n");
1441
1442 //cleanup
1443 QByteArray cleanResources = "qCleanupResources";
1444 cleanResources += initName;
1445
1446 // Work around -Wmissing-declarations warnings.
1447 writeString(s: "int ");
1448 writeMangleNamespaceFunction(name: cleanResources);
1449 writeString(s: "();\n");
1450
1451 writeString(s: "int ");
1452 writeMangleNamespaceFunction(name: cleanResources);
1453 writeString(s: "()\n{\n");
1454 if (m_root) {
1455 writeString(s: " int version = ");
1456 writeDecimal(value: m_formatVersion);
1457 writeString(s: ";\n ");
1458
1459 // ODR-use certain symbols from QtCore if we require optional features
1460 if (m_overallFlags & RCCFileInfo::Compressed) {
1461 writeString(s: "version += ");
1462 writeAddNamespaceFunction(name: "qResourceFeatureZlib()");
1463 writeString(s: ";\n ");
1464 }
1465 if (m_overallFlags & RCCFileInfo::CompressedZstd) {
1466 writeString(s: "version += ");
1467 writeAddNamespaceFunction(name: "qResourceFeatureZstd()");
1468 writeString(s: ";\n ");
1469 }
1470
1471 writeAddNamespaceFunction(name: "qUnregisterResourceData");
1472 writeString(s: "\n (version, qt_resource_struct, "
1473 "qt_resource_name, qt_resource_data);\n");
1474 }
1475 writeString(s: " return 1;\n");
1476 writeString(s: "}\n\n");
1477
1478 // -Wexit-time-destructors was added to clang 3.0.0 in 2011.
1479 writeString(s: "#ifdef __clang__\n"
1480 "# pragma clang diagnostic push\n"
1481 "# pragma clang diagnostic ignored \"-Wexit-time-destructors\"\n"
1482 "#endif\n\n");
1483
1484 writeString(s: "namespace {\n"
1485 " struct initializer {\n");
1486
1487 if (m_useNameSpace) {
1488 writeByteArray(other: " initializer() { QT_RCC_MANGLE_NAMESPACE(" + initResources + ")(); }\n"
1489 " ~initializer() { QT_RCC_MANGLE_NAMESPACE(" + cleanResources + ")(); }\n");
1490 } else {
1491 writeByteArray(other: " initializer() { " + initResources + "(); }\n"
1492 " ~initializer() { " + cleanResources + "(); }\n");
1493 }
1494 writeString(s: " } dummy;\n"
1495 "}\n\n");
1496
1497 writeString(s: "#ifdef __clang__\n"
1498 "# pragma clang diagnostic pop\n"
1499 "#endif\n");
1500
1501
1502 } else if (m_format == Binary) {
1503 int i = 4;
1504 char *p = m_out.data();
1505 p[i++] = 0;
1506 p[i++] = 0;
1507 p[i++] = 0;
1508 p[i++] = m_formatVersion;
1509
1510 p[i++] = (m_treeOffset >> 24) & 0xff;
1511 p[i++] = (m_treeOffset >> 16) & 0xff;
1512 p[i++] = (m_treeOffset >> 8) & 0xff;
1513 p[i++] = (m_treeOffset >> 0) & 0xff;
1514
1515 p[i++] = (m_dataOffset >> 24) & 0xff;
1516 p[i++] = (m_dataOffset >> 16) & 0xff;
1517 p[i++] = (m_dataOffset >> 8) & 0xff;
1518 p[i++] = (m_dataOffset >> 0) & 0xff;
1519
1520 p[i++] = (m_namesOffset >> 24) & 0xff;
1521 p[i++] = (m_namesOffset >> 16) & 0xff;
1522 p[i++] = (m_namesOffset >> 8) & 0xff;
1523 p[i++] = (m_namesOffset >> 0) & 0xff;
1524
1525 if (m_formatVersion >= 3) {
1526 p[i++] = (m_overallFlags >> 24) & 0xff;
1527 p[i++] = (m_overallFlags >> 16) & 0xff;
1528 p[i++] = (m_overallFlags >> 8) & 0xff;
1529 p[i++] = (m_overallFlags >> 0) & 0xff;
1530 }
1531 } else if (m_format == Python_Code) {
1532 writeString(s: "def qInitResources():\n");
1533 writeString(s: " QtCore.qRegisterResourceData(0x");
1534 write2HexDigits(number: m_formatVersion);
1535 writeString(s: ", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n");
1536 writeString(s: "def qCleanupResources():\n");
1537 writeString(s: " QtCore.qUnregisterResourceData(0x");
1538 write2HexDigits(number: m_formatVersion);
1539 writeString(s: ", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n");
1540 writeString(s: "qInitResources()\n");
1541 }
1542 return true;
1543}
1544
1545QT_END_NAMESPACE
1546

source code of qtbase/src/tools/rcc/rcc.cpp