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