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