diff --git a/src/main.cpp b/src/main.cpp index 0afc341..5857bca 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -50,7 +50,7 @@ QT_BEGIN_NAMESPACE -#define RCC_VERSION_STR "5.15.0" +#define RCC_VERSION_STR "5.15.2" void dumpRecursive(const QDir &dir, QTextStream &out) { @@ -142,7 +142,7 @@ int runRcc(int argc, char *argv[]) // If you use this code as an example for a translated app, make sure to translate the strings. QCommandLineParser parser; parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); - parser.setApplicationDescription(QLatin1String("Qt Resource Compiler and Decompiler version " RCC_VERSION_STR " (Build with Qt " QT_VERSION_STR ")")); + parser.setApplicationDescription(QLatin1String("Qt Resource Compiler and Decompiler (ver. " RCC_VERSION_STR ", compiled with Qt " QT_VERSION_STR ")")); parser.addHelpOption(); parser.addVersionOption(); @@ -364,8 +364,7 @@ int runRcc(int argc, char *argv[]) // Make sure QIODevice does not do LF->CRLF, // otherwise we'll end up in CRCRLF instead of // CRLF. - if (list) - mode &= ~QIODevice::Text; + mode &= ~QIODevice::Text; #endif // Q_OS_WIN // using this overload close() only flushes. out.open(stdout, mode); @@ -447,11 +446,10 @@ int main(int argc, char *argv[]) { // rcc uses a QHash to store files in the resource system. // we must force a certain hash order when testing or tst_rcc will fail, see QTBUG-25078 - if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty("QT_RCC_TEST"))) { - qSetGlobalQHashSeed(0); - if (qGlobalQHashSeed() != 0) - qFatal("Cannot force QHash seed for testing as requested"); - } + // similar requirements exist for reproducibly builds. + qSetGlobalQHashSeed(0); + if (qGlobalQHashSeed() != 0) + qWarning("Cannot force QHash seed"); return QT_PREPEND_NAMESPACE(runRcc)(argc, argv); } diff --git a/src/rcc.cpp b/src/rcc.cpp index 7188c81..e6a8a1b 100644 --- a/src/rcc.cpp +++ b/src/rcc.cpp @@ -1,1538 +1,1543 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Copyright (C) 2018 Intel Corporation. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "rcc.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#if QT_CONFIG(zstd) -# include -#endif - -// Note: A copy of this file is used in Qt Designer (qttools/src/designer/src/lib/shared/rcc.cpp) - -QT_BEGIN_NAMESPACE - -enum { - CONSTANT_USENAMESPACE = 1, - CONSTANT_COMPRESSLEVEL_DEFAULT = -1, - CONSTANT_ZSTDCOMPRESSLEVEL_CHECK = 1, // Zstd level to check if compressing is a good idea - CONSTANT_ZSTDCOMPRESSLEVEL_STORE = 14, // Zstd level to actually store the data - CONSTANT_COMPRESSTHRESHOLD_DEFAULT = 70 -}; - -#if QT_CONFIG(zstd) && QT_VERSION >= QT_VERSION_CHECK(6,0,0) -# define CONSTANT_COMPRESSALGO_DEFAULT RCCResourceLibrary::CompressionAlgorithm::Zstd -#elif !defined(QT_NO_COMPRESS) -# define CONSTANT_COMPRESSALGO_DEFAULT RCCResourceLibrary::CompressionAlgorithm::Zlib -#else -# define CONSTANT_COMPRESSALGO_DEFAULT RCCResourceLibrary::CompressionAlgorithm::None -#endif - -void RCCResourceLibrary::write(const char *str, int len) -{ - int n = m_out.size(); - m_out.resize(n + len); - memcpy(m_out.data() + n, str, len); -} - -void RCCResourceLibrary::writeByteArray(const QByteArray &other) -{ - if (m_format == Pass2) { - m_outDevice->write(other); - } else { - m_out.append(other); - } -} - -static inline QString msgOpenReadFailed(const QString &fname, const QString &why) -{ - return QString::fromLatin1("Unable to open %1 for reading: %2\n").arg(fname, why); -} - - -/////////////////////////////////////////////////////////// -// -// RCCFileInfo -// -/////////////////////////////////////////////////////////// - -class RCCFileInfo -{ -public: - enum Flags - { - // must match qresource.cpp - NoFlags = 0x00, - Compressed = 0x01, - Directory = 0x02, - CompressedZstd = 0x04 - }; - - RCCFileInfo(const QString &name = QString(), const QFileInfo &fileInfo = QFileInfo(), - QLocale::Language language = QLocale::C, - QLocale::Country country = QLocale::AnyCountry, - uint flags = NoFlags, - RCCResourceLibrary::CompressionAlgorithm compressAlgo = CONSTANT_COMPRESSALGO_DEFAULT, - int compressLevel = CONSTANT_COMPRESSLEVEL_DEFAULT, - int compressThreshold = CONSTANT_COMPRESSTHRESHOLD_DEFAULT); - ~RCCFileInfo(); - - QString resourceName() const; - -public: - qint64 writeDataBlob(RCCResourceLibrary &lib, qint64 offset, QString *errorMessage); - qint64 writeDataName(RCCResourceLibrary &, qint64 offset); - void writeDataInfo(RCCResourceLibrary &lib); - - int m_flags; - QString m_name; - QLocale::Language m_language; - QLocale::Country m_country; - QFileInfo m_fileInfo; - RCCFileInfo *m_parent; - QMultiHash m_children; - RCCResourceLibrary::CompressionAlgorithm m_compressAlgo; - int m_compressLevel; - int m_compressThreshold; - - qint64 m_nameOffset; - qint64 m_dataOffset; - qint64 m_childOffset; -}; - -RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo, - QLocale::Language language, QLocale::Country country, uint flags, - RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel, int compressThreshold) -{ - m_name = name; - m_fileInfo = fileInfo; - m_language = language; - m_country = country; - m_flags = flags; - m_parent = 0; - m_nameOffset = 0; - m_dataOffset = 0; - m_childOffset = 0; - m_compressAlgo = compressAlgo; - m_compressLevel = compressLevel; - m_compressThreshold = compressThreshold; -} - -RCCFileInfo::~RCCFileInfo() -{ - qDeleteAll(m_children); -} - -QString RCCFileInfo::resourceName() const -{ - QString resource = m_name; - for (RCCFileInfo *p = m_parent; p; p = p->m_parent) - resource = resource.prepend(p->m_name + QLatin1Char('/')); - return QLatin1Char(':') + resource; -} - -void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib) -{ - const bool text = lib.m_format == RCCResourceLibrary::C_Code; - const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1; - const bool python = lib.m_format == RCCResourceLibrary::Python3_Code - || lib.m_format == RCCResourceLibrary::Python2_Code; - //some info - if (text || pass1) { - if (m_language != QLocale::C) { - lib.writeString(" // "); - lib.writeByteArray(resourceName().toLocal8Bit()); - lib.writeString(" ["); - lib.writeByteArray(QByteArray::number(m_country)); - lib.writeString("::"); - lib.writeByteArray(QByteArray::number(m_language)); - lib.writeString("[\n "); - } else { - lib.writeString(" // "); - lib.writeByteArray(resourceName().toLocal8Bit()); - lib.writeString("\n "); - } - } - - //pointer data - if (m_flags & RCCFileInfo::Directory) { - // name offset - lib.writeNumber4(m_nameOffset); - - // flags - lib.writeNumber2(m_flags); - - // child count - lib.writeNumber4(m_children.size()); - - // first child offset - lib.writeNumber4(m_childOffset); - } else { - // name offset - lib.writeNumber4(m_nameOffset); - - // flags - lib.writeNumber2(m_flags); - - // locale - lib.writeNumber2(m_country); - lib.writeNumber2(m_language); - - //data offset - lib.writeNumber4(m_dataOffset); - } - if (text || pass1) - lib.writeChar('\n'); - else if (python) - lib.writeString("\\\n"); - - if (lib.formatVersion() >= 2) { - // last modified time stamp - const QDateTime lastModified = m_fileInfo.lastModified(); - quint64 lastmod = quint64(lastModified.isValid() ? lastModified.toMSecsSinceEpoch() : 0); - static const quint64 sourceDate = 1000 * qgetenv("QT_RCC_SOURCE_DATE_OVERRIDE").toULongLong(); - if (sourceDate != 0) - lastmod = sourceDate; - static const quint64 sourceDate2 = 1000 * qgetenv("SOURCE_DATE_EPOCH").toULongLong(); - if (sourceDate2 != 0) - lastmod = sourceDate2; - lib.writeNumber8(lastmod); - if (text || pass1) - lib.writeChar('\n'); - else if (python) - lib.writeString("\\\n"); - } -} - -qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset, - QString *errorMessage) -{ - const bool text = lib.m_format == RCCResourceLibrary::C_Code; - const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1; - const bool pass2 = lib.m_format == RCCResourceLibrary::Pass2; - const bool binary = lib.m_format == RCCResourceLibrary::Binary; - const bool python = lib.m_format == RCCResourceLibrary::Python3_Code - || lib.m_format == RCCResourceLibrary::Python2_Code; - - //capture the offset - m_dataOffset = offset; - - //find the data to be written - QFile file(m_fileInfo.absoluteFilePath()); - if (!file.open(QFile::ReadOnly)) { - *errorMessage = msgOpenReadFailed(m_fileInfo.absoluteFilePath(), file.errorString()); - return 0; - } - QByteArray data = file.readAll(); - - // Check if compression is useful for this file - if (data.size() != 0) { -#if QT_CONFIG(zstd) - if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best) { - m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zstd; - m_compressLevel = 19; // not ZSTD_maxCLevel(), as 20+ are experimental - } - if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zstd) { - if (lib.m_zstdCCtx == nullptr) - lib.m_zstdCCtx = ZSTD_createCCtx(); - qsizetype size = data.size(); - size = ZSTD_COMPRESSBOUND(size); - - int compressLevel = m_compressLevel; - if (compressLevel < 0) - compressLevel = CONSTANT_ZSTDCOMPRESSLEVEL_CHECK; - - QByteArray compressed(size, Qt::Uninitialized); - char *dst = const_cast(compressed.constData()); - size_t n = ZSTD_compressCCtx(lib.m_zstdCCtx, dst, size, - data.constData(), data.size(), - compressLevel); - if (n * 100.0 < data.size() * 1.0 * (100 - m_compressThreshold) ) { - // compressing is worth it - if (m_compressLevel < 0) { - // heuristic compression, so recompress - n = ZSTD_compressCCtx(lib.m_zstdCCtx, dst, size, - data.constData(), data.size(), - CONSTANT_ZSTDCOMPRESSLEVEL_STORE); - } - if (ZSTD_isError(n)) { - QString msg = QString::fromLatin1("%1: error: compression with zstd failed: %2\n") - .arg(m_name, QString::fromUtf8(ZSTD_getErrorName(n))); - lib.m_errorDevice->write(msg.toUtf8()); - } else if (lib.verbose()) { - QString msg = QString::fromLatin1("%1: note: compressed using zstd (%2 -> %3)\n") - .arg(m_name).arg(data.size()).arg(n); - lib.m_errorDevice->write(msg.toUtf8()); - } - - lib.m_overallFlags |= CompressedZstd; - m_flags |= CompressedZstd; - data = std::move(compressed); - data.truncate(n); - } else if (lib.verbose()) { - QString msg = QString::fromLatin1("%1: note: not compressed\n").arg(m_name); - lib.m_errorDevice->write(msg.toUtf8()); - } - } -#endif -#ifndef QT_NO_COMPRESS - if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best) { - m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zlib; - m_compressLevel = 9; - } - if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zlib) { - QByteArray compressed = - qCompress(reinterpret_cast(data.data()), data.size(), m_compressLevel); - - int compressRatio = int(100.0 * (data.size() - compressed.size()) / data.size()); - if (compressRatio >= m_compressThreshold) { - if (lib.verbose()) { - QString msg = QString::fromLatin1("%1: note: compressed using zlib (%2 -> %3)\n") - .arg(m_name).arg(data.size()).arg(compressed.size()); - lib.m_errorDevice->write(msg.toUtf8()); - } - data = compressed; - lib.m_overallFlags |= Compressed; - m_flags |= Compressed; - } else if (lib.verbose()) { - QString msg = QString::fromLatin1("%1: note: not compressed\n").arg(m_name); - lib.m_errorDevice->write(msg.toUtf8()); - } - } -#endif // QT_NO_COMPRESS - } - - // some info - if (text || pass1) { - lib.writeString(" // "); - lib.writeByteArray(m_fileInfo.absoluteFilePath().toLocal8Bit()); - lib.writeString("\n "); - } - - // write the length - if (text || binary || pass2 || python) - lib.writeNumber4(data.size()); - if (text || pass1) - lib.writeString("\n "); - else if (python) - lib.writeString("\\\n"); - offset += 4; - - // write the payload - const char *p = data.constData(); - if (text || python) { - for (int i = data.size(), j = 0; --i >= 0; --j) { - lib.writeHex(*p++); - if (j == 0) { - if (text) - lib.writeString("\n "); - else - lib.writeString("\\\n"); - j = 16; - } - } - } else if (binary || pass2) { - lib.writeByteArray(data); - } - offset += data.size(); - - // done - if (text || pass1) - lib.writeString("\n "); - else if (python) - lib.writeString("\\\n"); - - return offset; -} - -qint64 RCCFileInfo::writeDataName(RCCResourceLibrary &lib, qint64 offset) -{ - const bool text = lib.m_format == RCCResourceLibrary::C_Code; - const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1; - const bool python = lib.m_format == RCCResourceLibrary::Python3_Code - || lib.m_format == RCCResourceLibrary::Python2_Code; - - // capture the offset - m_nameOffset = offset; - - // some info - if (text || pass1) { - lib.writeString(" // "); - lib.writeByteArray(m_name.toLocal8Bit()); - lib.writeString("\n "); - } - - // write the length - lib.writeNumber2(m_name.length()); - if (text || pass1) - lib.writeString("\n "); - else if (python) - lib.writeString("\\\n"); - offset += 2; - - // write the hash - lib.writeNumber4(qt_hash(m_name)); - if (text || pass1) - lib.writeString("\n "); - else if (python) - lib.writeString("\\\n"); - offset += 4; - - // write the m_name - const QChar *unicode = m_name.unicode(); - for (int i = 0; i < m_name.length(); ++i) { - lib.writeNumber2(unicode[i].unicode()); - if ((text || pass1) && i % 16 == 0) - lib.writeString("\n "); - else if (python && i % 16 == 0) - lib.writeString("\\\n"); - } - offset += m_name.length()*2; - - // done - if (text || pass1) - lib.writeString("\n "); - else if (python) - lib.writeString("\\\n"); - - return offset; -} - - -/////////////////////////////////////////////////////////// -// -// RCCResourceLibrary -// -/////////////////////////////////////////////////////////// - -RCCResourceLibrary::Strings::Strings() : - TAG_RCC(QLatin1String("RCC")), - TAG_RESOURCE(QLatin1String("qresource")), - TAG_FILE(QLatin1String("file")), - ATTRIBUTE_LANG(QLatin1String("lang")), - ATTRIBUTE_PREFIX(QLatin1String("prefix")), - ATTRIBUTE_ALIAS(QLatin1String("alias")), - ATTRIBUTE_THRESHOLD(QLatin1String("threshold")), - ATTRIBUTE_COMPRESS(QLatin1String("compress")), - ATTRIBUTE_COMPRESSALGO(QStringLiteral("compression-algorithm")) -{ -} - -RCCResourceLibrary::RCCResourceLibrary(quint8 formatVersion) - : m_root(0), - m_format(C_Code), - m_verbose(false), - m_compressionAlgo(CONSTANT_COMPRESSALGO_DEFAULT), - m_compressLevel(CONSTANT_COMPRESSLEVEL_DEFAULT), - m_compressThreshold(CONSTANT_COMPRESSTHRESHOLD_DEFAULT), - m_treeOffset(0), - m_namesOffset(0), - m_dataOffset(0), - m_overallFlags(0), - m_useNameSpace(CONSTANT_USENAMESPACE), - m_errorDevice(0), - m_outDevice(0), - m_formatVersion(formatVersion) -{ - m_out.reserve(30 * 1000 * 1000); -#if QT_CONFIG(zstd) - m_zstdCCtx = nullptr; -#endif -} - -RCCResourceLibrary::~RCCResourceLibrary() -{ - delete m_root; -#if QT_CONFIG(zstd) - ZSTD_freeCCtx(m_zstdCCtx); -#endif -} - -enum RCCXmlTag { - RccTag, - ResourceTag, - FileTag -}; -Q_DECLARE_TYPEINFO(RCCXmlTag, Q_PRIMITIVE_TYPE); - -bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, - const QString &fname, QString currentPath, bool listMode) -{ - Q_ASSERT(m_errorDevice); - const QChar slash = QLatin1Char('/'); - if (!currentPath.isEmpty() && !currentPath.endsWith(slash)) - currentPath += slash; - - QXmlStreamReader reader(inputDevice); - QStack tokens; - - QString prefix; - QLocale::Language language = QLocale::c().language(); - QLocale::Country country = QLocale::c().country(); - QString alias; - auto compressAlgo = m_compressionAlgo; - int compressLevel = m_compressLevel; - int compressThreshold = m_compressThreshold; - - while (!reader.atEnd()) { - QXmlStreamReader::TokenType t = reader.readNext(); - switch (t) { - case QXmlStreamReader::StartElement: - if (reader.name() == m_strings.TAG_RCC) { - if (!tokens.isEmpty()) - reader.raiseError(QLatin1String("expected tag")); - else - tokens.push(RccTag); - } else if (reader.name() == m_strings.TAG_RESOURCE) { - if (tokens.isEmpty() || tokens.top() != RccTag) { - reader.raiseError(QLatin1String("unexpected tag")); - } else { - tokens.push(ResourceTag); - - QXmlStreamAttributes attributes = reader.attributes(); - language = QLocale::c().language(); - country = QLocale::c().country(); - - if (attributes.hasAttribute(m_strings.ATTRIBUTE_LANG)) { - QString attribute = attributes.value(m_strings.ATTRIBUTE_LANG).toString(); - QLocale lang = QLocale(attribute); - language = lang.language(); - if (2 == attribute.length()) { - // Language only - country = QLocale::AnyCountry; - } else { - country = lang.country(); - } - } - - prefix.clear(); - if (attributes.hasAttribute(m_strings.ATTRIBUTE_PREFIX)) - prefix = attributes.value(m_strings.ATTRIBUTE_PREFIX).toString(); - if (!prefix.startsWith(slash)) - prefix.prepend(slash); - if (!prefix.endsWith(slash)) - prefix += slash; - } - } else if (reader.name() == m_strings.TAG_FILE) { - if (tokens.isEmpty() || tokens.top() != ResourceTag) { - reader.raiseError(QLatin1String("unexpected tag")); - } else { - tokens.push(FileTag); - - QXmlStreamAttributes attributes = reader.attributes(); - alias.clear(); - if (attributes.hasAttribute(m_strings.ATTRIBUTE_ALIAS)) - alias = attributes.value(m_strings.ATTRIBUTE_ALIAS).toString(); - - compressAlgo = m_compressionAlgo; - compressLevel = m_compressLevel; - compressThreshold = m_compressThreshold; - - QString errorString; - if (attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESSALGO)) - compressAlgo = parseCompressionAlgorithm(attributes.value(m_strings.ATTRIBUTE_COMPRESSALGO), &errorString); - if (errorString.isEmpty() && attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESS)) { - QString value = attributes.value(m_strings.ATTRIBUTE_COMPRESS).toString(); - compressLevel = parseCompressionLevel(compressAlgo, value, &errorString); - } - - // Special case for -no-compress - if (m_compressLevel == -2) - compressAlgo = CompressionAlgorithm::None; - - if (attributes.hasAttribute(m_strings.ATTRIBUTE_THRESHOLD)) - compressThreshold = attributes.value(m_strings.ATTRIBUTE_THRESHOLD).toString().toInt(); - - if (!errorString.isEmpty()) - reader.raiseError(errorString); - } - } else { - reader.raiseError(QString(QLatin1String("unexpected tag: %1")).arg(reader.name().toString())); - } - break; - - case QXmlStreamReader::EndElement: - if (reader.name() == m_strings.TAG_RCC) { - if (!tokens.isEmpty() && tokens.top() == RccTag) - tokens.pop(); - else - reader.raiseError(QLatin1String("unexpected closing tag")); - } else if (reader.name() == m_strings.TAG_RESOURCE) { - if (!tokens.isEmpty() && tokens.top() == ResourceTag) - tokens.pop(); - else - reader.raiseError(QLatin1String("unexpected closing tag")); - } else if (reader.name() == m_strings.TAG_FILE) { - if (!tokens.isEmpty() && tokens.top() == FileTag) - tokens.pop(); - else - reader.raiseError(QLatin1String("unexpected closing tag")); - } - break; - - case QXmlStreamReader::Characters: - if (reader.isWhitespace()) - break; - if (tokens.isEmpty() || tokens.top() != FileTag) { - reader.raiseError(QLatin1String("unexpected text")); - } else { - QString fileName = reader.text().toString(); - if (fileName.isEmpty()) { - const QString msg = QString::fromLatin1("RCC: Warning: Null node in XML of '%1'\n").arg(fname); - m_errorDevice->write(msg.toUtf8()); - } - - if (alias.isNull()) - alias = fileName; - - alias = QDir::cleanPath(alias); - while (alias.startsWith(QLatin1String("../"))) - alias.remove(0, 3); - alias = QDir::cleanPath(m_resourceRoot) + prefix + alias; - - QString absFileName = fileName; - if (QDir::isRelativePath(absFileName)) - absFileName.prepend(currentPath); - QFileInfo file(absFileName); - if (file.isDir()) { - QDir dir(file.filePath()); - if (!alias.endsWith(slash)) - alias += slash; - QDirIterator it(dir, QDirIterator::FollowSymlinks|QDirIterator::Subdirectories); - while (it.hasNext()) { - it.next(); - QFileInfo child(it.fileInfo()); - if (child.fileName() != QLatin1String(".") && child.fileName() != QLatin1String("..")) { - const bool arc = - addFile(alias + child.fileName(), - RCCFileInfo(child.fileName(), - child, - language, - country, - child.isDir() ? RCCFileInfo::Directory : RCCFileInfo::NoFlags, - compressAlgo, - compressLevel, - compressThreshold) - ); - if (!arc) - m_failedResources.push_back(child.fileName()); - } - } - } else if (listMode || file.isFile()) { - const bool arc = - addFile(alias, - RCCFileInfo(alias.section(slash, -1), - file, - language, - country, - RCCFileInfo::NoFlags, - compressAlgo, - compressLevel, - compressThreshold) - ); - if (!arc) - m_failedResources.push_back(absFileName); - } else if (file.exists()) { - m_failedResources.push_back(absFileName); - const QString msg = QString::fromLatin1("RCC: Error in '%1': Entry '%2' is neither a file nor a directory\n") - .arg(fname, fileName); - m_errorDevice->write(msg.toUtf8()); - return false; - } else { - m_failedResources.push_back(absFileName); - const QString msg = QString::fromLatin1("RCC: Error in '%1': Cannot find file '%2'\n") - .arg(fname, fileName); - m_errorDevice->write(msg.toUtf8()); - return false; - } - } - break; - - default: - break; - } - } - - if (reader.hasError()) { - int errorLine = reader.lineNumber(); - int errorColumn = reader.columnNumber(); - QString errorMessage = reader.errorString(); - QString msg = QString::fromLatin1("RCC Parse Error: '%1' Line: %2 Column: %3 [%4]\n").arg(fname).arg(errorLine).arg(errorColumn).arg(errorMessage); - m_errorDevice->write(msg.toUtf8()); - return false; - } - - if (m_root == 0) { - const QString msg = QString::fromLatin1("RCC: Warning: No resources in '%1'.\n").arg(fname); - m_errorDevice->write(msg.toUtf8()); - if (!listMode && m_format == Binary) { - // create dummy entry, otherwise loading with QResource will crash - m_root = new RCCFileInfo(QString(), QFileInfo(), - QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); - } - } - - return true; -} - -bool RCCResourceLibrary::addFile(const QString &alias, const RCCFileInfo &file) -{ - Q_ASSERT(m_errorDevice); - if (file.m_fileInfo.size() > 0xffffffff) { - const QString msg = QString::fromLatin1("File too big: %1\n").arg(file.m_fileInfo.absoluteFilePath()); - m_errorDevice->write(msg.toUtf8()); - return false; - } - if (!m_root) - m_root = new RCCFileInfo(QString(), QFileInfo(), QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); - - RCCFileInfo *parent = m_root; - const QStringList nodes = alias.split(QLatin1Char('/')); - for (int i = 1; i < nodes.size()-1; ++i) { - const QString node = nodes.at(i); - if (node.isEmpty()) - continue; - if (!parent->m_children.contains(node)) { - RCCFileInfo *s = new RCCFileInfo(node, QFileInfo(), QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); - s->m_parent = parent; - parent->m_children.insert(node, s); - parent = s; - } else { - parent = *parent->m_children.constFind(node); - } - } - - const QString filename = nodes.at(nodes.size()-1); - RCCFileInfo *s = new RCCFileInfo(file); - s->m_parent = parent; - typedef QHash::const_iterator ChildConstIterator; - const ChildConstIterator cbegin = parent->m_children.constFind(filename); - const ChildConstIterator cend = parent->m_children.constEnd(); - for (ChildConstIterator it = cbegin; it != cend; ++it) { - if (it.key() == filename && it.value()->m_language == s->m_language && - it.value()->m_country == s->m_country) { - for (const QString &name : qAsConst(m_fileNames)) { - qWarning("%s: Warning: potential duplicate alias detected: '%s'", - qPrintable(name), qPrintable(filename)); - } - break; - } - } - parent->m_children.insert(filename, s); - return true; -} - -void RCCResourceLibrary::reset() -{ - if (m_root) { - delete m_root; - m_root = 0; - } - m_errorDevice = 0; - m_failedResources.clear(); -} - - -bool RCCResourceLibrary::readFiles(bool listMode, QIODevice &errorDevice) -{ - reset(); - m_errorDevice = &errorDevice; - //read in data - if (m_verbose) { - const QString msg = QString::fromLatin1("Processing %1 files [listMode=%2]\n") - .arg(m_fileNames.size()).arg(static_cast(listMode)); - m_errorDevice->write(msg.toUtf8()); - } - for (int i = 0; i < m_fileNames.size(); ++i) { - QFile fileIn; - QString fname = m_fileNames.at(i); - QString pwd; - if (fname == QLatin1String("-")) { - fname = QLatin1String("(stdin)"); - pwd = QDir::currentPath(); - fileIn.setFileName(fname); - if (!fileIn.open(stdin, QIODevice::ReadOnly)) { - m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8()); - return false; - } - } else { - pwd = QFileInfo(fname).path(); - fileIn.setFileName(fname); - if (!fileIn.open(QIODevice::ReadOnly)) { - m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8()); - return false; - } - } - if (m_verbose) { - const QString msg = QString::fromLatin1("Interpreting %1\n").arg(fname); - m_errorDevice->write(msg.toUtf8()); - } - - if (!interpretResourceFile(&fileIn, fname, pwd, listMode)) - return false; - } - return true; -} - -QStringList RCCResourceLibrary::dataFiles() const -{ - QStringList ret; - QStack pending; - - if (!m_root) - return ret; - pending.push(m_root); - while (!pending.isEmpty()) { - RCCFileInfo *file = pending.pop(); - for (QHash::iterator it = file->m_children.begin(); - it != file->m_children.end(); ++it) { - RCCFileInfo *child = it.value(); - if (child->m_flags & RCCFileInfo::Directory) - pending.push(child); - else - ret.append(child->m_fileInfo.filePath()); - } - } - return ret; -} - -// Determine map of resource identifier (':/newPrefix/images/p1.png') to file via recursion -static void resourceDataFileMapRecursion(const RCCFileInfo *m_root, const QString &path, RCCResourceLibrary::ResourceDataFileMap &m) -{ - typedef QHash::const_iterator ChildConstIterator; - const QChar slash = QLatin1Char('/'); - const ChildConstIterator cend = m_root->m_children.constEnd(); - for (ChildConstIterator it = m_root->m_children.constBegin(); it != cend; ++it) { - const RCCFileInfo *child = it.value(); - const QString childName = path + slash + child->m_name; - if (child->m_flags & RCCFileInfo::Directory) { - resourceDataFileMapRecursion(child, childName, m); - } else { - m.insert(childName, child->m_fileInfo.filePath()); - } - } -} - -RCCResourceLibrary::ResourceDataFileMap RCCResourceLibrary::resourceDataFileMap() const -{ - ResourceDataFileMap rc; - if (m_root) - resourceDataFileMapRecursion(m_root, QString(QLatin1Char(':')), rc); - return rc; -} - -RCCResourceLibrary::CompressionAlgorithm RCCResourceLibrary::parseCompressionAlgorithm(QStringView value, QString *errorMsg) -{ - if (value == QLatin1String("best")) - return CompressionAlgorithm::Best; - if (value == QLatin1String("zlib")) { -#ifdef QT_NO_COMPRESS - *errorMsg = QLatin1String("zlib support not compiled in"); -#else - return CompressionAlgorithm::Zlib; -#endif - } else if (value == QLatin1String("zstd")) { -#if QT_CONFIG(zstd) - return CompressionAlgorithm::Zstd; -#else - *errorMsg = QLatin1String("Zstandard support not compiled in"); -#endif - } else if (value != QLatin1String("none")) { - *errorMsg = QString::fromLatin1("Unknown compression algorithm '%1'").arg(value); - } - - return CompressionAlgorithm::None; -} - -int RCCResourceLibrary::parseCompressionLevel(CompressionAlgorithm algo, const QString &level, QString *errorMsg) -{ - bool ok; - int c = level.toInt(&ok); - if (ok) { - switch (algo) { - case CompressionAlgorithm::None: - case CompressionAlgorithm::Best: - return 0; - case CompressionAlgorithm::Zlib: - if (c >= 1 && c <= 9) - return c; - break; - case CompressionAlgorithm::Zstd: -#if QT_CONFIG(zstd) - if (c >= 0 && c <= ZSTD_maxCLevel()) - return c; -#endif - break; - } - } - - *errorMsg = QString::fromLatin1("invalid compression level '%1'").arg(level); - return 0; -} - -bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &tempDevice, QIODevice &errorDevice) -{ - m_errorDevice = &errorDevice; - - if (m_format == Pass2) { - const char pattern[] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' }; - bool foundSignature = false; - - while (true) { - char c; - for (int i = 0; i < 8; ) { - if (!tempDevice.getChar(&c)) { - if (foundSignature) - return true; - m_errorDevice->write("No data signature found\n"); - return false; - } - if (c == pattern[i]) { - ++i; - } else { - for (int k = 0; k < i; ++k) - outDevice.putChar(pattern[k]); - outDevice.putChar(c); - i = 0; - } - } - - m_outDevice = &outDevice; - quint64 start = outDevice.pos(); - writeDataBlobs(); - quint64 len = outDevice.pos() - start; - - tempDevice.seek(tempDevice.pos() + len - 8); - foundSignature = true; - } - } - - //write out - if (m_verbose) - m_errorDevice->write("Outputting code\n"); - if (!writeHeader()) { - m_errorDevice->write("Could not write header\n"); - return false; - } - if (m_root) { - if (!writeDataBlobs()) { - m_errorDevice->write("Could not write data blobs.\n"); - return false; - } - if (!writeDataNames()) { - m_errorDevice->write("Could not write file names\n"); - return false; - } - if (!writeDataStructure()) { - m_errorDevice->write("Could not write data tree\n"); - return false; - } - } - if (!writeInitializer()) { - m_errorDevice->write("Could not write footer\n"); - return false; - } - outDevice.write(m_out.constData(), m_out.size()); - return true; -} - -void RCCResourceLibrary::writeDecimal(int value) -{ - Q_ASSERT(m_format != RCCResourceLibrary::Binary); - char buf[std::numeric_limits::digits10 + 2]; - int n = snprintf(buf, sizeof(buf), "%d", value); - write(buf, n); -} - -static const char hexDigits[] = "0123456789abcdef"; - -inline void RCCResourceLibrary::write2HexDigits(quint8 number) -{ - writeChar(hexDigits[number >> 4]); - writeChar(hexDigits[number & 0xf]); -} - -void RCCResourceLibrary::writeHex(quint8 tmp) -{ - switch (m_format) { - case RCCResourceLibrary::Python3_Code: - case RCCResourceLibrary::Python2_Code: - if (tmp >= 32 && tmp < 127 && tmp != '"' && tmp != '\\') { - writeChar(char(tmp)); - } else { - writeChar('\\'); - writeChar('x'); - write2HexDigits(tmp); - } - break; - default: - writeChar('0'); - writeChar('x'); - if (tmp < 16) - writeChar(hexDigits[tmp]); - else - write2HexDigits(tmp); - writeChar(','); - break; - } -} - -void RCCResourceLibrary::writeNumber2(quint16 number) -{ - if (m_format == RCCResourceLibrary::Binary) { - writeChar(number >> 8); - writeChar(number); - } else { - writeHex(number >> 8); - writeHex(number); - } -} - -void RCCResourceLibrary::writeNumber4(quint32 number) -{ - if (m_format == RCCResourceLibrary::Pass2) { - m_outDevice->putChar(char(number >> 24)); - m_outDevice->putChar(char(number >> 16)); - m_outDevice->putChar(char(number >> 8)); - m_outDevice->putChar(char(number)); - } else if (m_format == RCCResourceLibrary::Binary) { - writeChar(number >> 24); - writeChar(number >> 16); - writeChar(number >> 8); - writeChar(number); - } else { - writeHex(number >> 24); - writeHex(number >> 16); - writeHex(number >> 8); - writeHex(number); - } -} - -void RCCResourceLibrary::writeNumber8(quint64 number) -{ - if (m_format == RCCResourceLibrary::Pass2) { - m_outDevice->putChar(char(number >> 56)); - m_outDevice->putChar(char(number >> 48)); - m_outDevice->putChar(char(number >> 40)); - m_outDevice->putChar(char(number >> 32)); - m_outDevice->putChar(char(number >> 24)); - m_outDevice->putChar(char(number >> 16)); - m_outDevice->putChar(char(number >> 8)); - m_outDevice->putChar(char(number)); - } else if (m_format == RCCResourceLibrary::Binary) { - writeChar(number >> 56); - writeChar(number >> 48); - writeChar(number >> 40); - writeChar(number >> 32); - writeChar(number >> 24); - writeChar(number >> 16); - writeChar(number >> 8); - writeChar(number); - } else { - writeHex(number >> 56); - writeHex(number >> 48); - writeHex(number >> 40); - writeHex(number >> 32); - writeHex(number >> 24); - writeHex(number >> 16); - writeHex(number >> 8); - writeHex(number); - } -} - -bool RCCResourceLibrary::writeHeader() -{ - switch (m_format) { - case C_Code: - case Pass1: - writeString("/****************************************************************************\n"); - writeString("** Resource object code\n"); - writeString("**\n"); - writeString("** Created by: The Resource Compiler for Qt version "); - writeByteArray(QT_VERSION_STR); - writeString("\n**\n"); - writeString("** WARNING! All changes made in this file will be lost!\n"); - writeString( "*****************************************************************************/\n\n"); - break; - case Python3_Code: - case Python2_Code: - writeString("# Resource object code (Python "); - writeChar(m_format == Python3_Code ? '3' : '2'); - writeString(")\n"); - writeString("# Created by: object code\n"); - writeString("# Created by: The Resource Compiler for Qt version "); - writeByteArray(QT_VERSION_STR); - writeString("\n"); - writeString("# WARNING! All changes made in this file will be lost!\n\n"); - writeString("from PySide2 import QtCore\n\n"); - break; - case Binary: - writeString("qres"); - writeNumber4(0); - writeNumber4(0); - writeNumber4(0); - writeNumber4(0); - if (m_formatVersion >= 3) - writeNumber4(m_overallFlags); - break; - default: - break; - } - return true; -} - -bool RCCResourceLibrary::writeDataBlobs() -{ - Q_ASSERT(m_errorDevice); - switch (m_format) { - case C_Code: - writeString("static const unsigned char qt_resource_data[] = {\n"); - break; - case Python3_Code: - writeString("qt_resource_data = b\"\\\n"); - break; - case Python2_Code: - writeString("qt_resource_data = \"\\\n"); - break; - case Binary: - m_dataOffset = m_out.size(); - break; - default: - break; - } - - if (!m_root) - return false; - - QStack pending; - pending.push(m_root); - qint64 offset = 0; - QString errorMessage; - while (!pending.isEmpty()) { - RCCFileInfo *file = pending.pop(); - for (QHash::iterator it = file->m_children.begin(); - it != file->m_children.end(); ++it) { - RCCFileInfo *child = it.value(); - if (child->m_flags & RCCFileInfo::Directory) - pending.push(child); - else { - offset = child->writeDataBlob(*this, offset, &errorMessage); - if (offset == 0) { - m_errorDevice->write(errorMessage.toUtf8()); - return false; - } - } - } - } - switch (m_format) { - case C_Code: - writeString("\n};\n\n"); - break; - case Python3_Code: - case Python2_Code: - writeString("\"\n\n"); - break; - case Pass1: - if (offset < 8) - offset = 8; - writeString("\nstatic const unsigned char qt_resource_data["); - writeByteArray(QByteArray::number(offset)); - writeString("] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' };\n\n"); - break; - default: - break; - } - return true; -} - -bool RCCResourceLibrary::writeDataNames() -{ - switch (m_format) { - case C_Code: - case Pass1: - writeString("static const unsigned char qt_resource_name[] = {\n"); - break; - case Python3_Code: - writeString("qt_resource_name = b\"\\\n"); - break; - case Python2_Code: - writeString("qt_resource_name = \"\\\n"); - break; - case Binary: - m_namesOffset = m_out.size(); - break; - default: - break; - } - - QHash names; - QStack pending; - - if (!m_root) - return false; - - pending.push(m_root); - qint64 offset = 0; - while (!pending.isEmpty()) { - RCCFileInfo *file = pending.pop(); - for (QHash::iterator it = file->m_children.begin(); - it != file->m_children.end(); ++it) { - RCCFileInfo *child = it.value(); - if (child->m_flags & RCCFileInfo::Directory) - pending.push(child); - if (names.contains(child->m_name)) { - child->m_nameOffset = names.value(child->m_name); - } else { - names.insert(child->m_name, offset); - offset = child->writeDataName(*this, offset); - } - } - } - switch (m_format) { - case C_Code: - case Pass1: - writeString("\n};\n\n"); - break; - case Python3_Code: - case Python2_Code: - writeString("\"\n\n"); - break; - default: - break; - } - return true; -} - -struct qt_rcc_compare_hash -{ - typedef bool result_type; - result_type operator()(const RCCFileInfo *left, const RCCFileInfo *right) const - { - return qt_hash(left->m_name) < qt_hash(right->m_name); - } -}; - -bool RCCResourceLibrary::writeDataStructure() -{ - switch (m_format) { - case C_Code: - case Pass1: - writeString("static const unsigned char qt_resource_struct[] = {\n"); - break; - case Python3_Code: - writeString("qt_resource_struct = b\"\\\n"); - break; - case Python2_Code: - writeString("qt_resource_struct = \"\\\n"); - break; - case Binary: - m_treeOffset = m_out.size(); - break; - default: - break; - } - - QStack pending; - - if (!m_root) - return false; - - //calculate the child offsets (flat) - pending.push(m_root); - int offset = 1; - while (!pending.isEmpty()) { - RCCFileInfo *file = pending.pop(); - file->m_childOffset = offset; - - //sort by hash value for binary lookup - QList m_children = file->m_children.values(); - std::sort(m_children.begin(), m_children.end(), qt_rcc_compare_hash()); - - //write out the actual data now - for (int i = 0; i < m_children.size(); ++i) { - RCCFileInfo *child = m_children.at(i); - ++offset; - if (child->m_flags & RCCFileInfo::Directory) - pending.push(child); - } - } - - //write out the structure (ie iterate again!) - pending.push(m_root); - m_root->writeDataInfo(*this); - while (!pending.isEmpty()) { - RCCFileInfo *file = pending.pop(); - - //sort by hash value for binary lookup - QList m_children = file->m_children.values(); - std::sort(m_children.begin(), m_children.end(), qt_rcc_compare_hash()); - - //write out the actual data now - for (int i = 0; i < m_children.size(); ++i) { - RCCFileInfo *child = m_children.at(i); - child->writeDataInfo(*this); - if (child->m_flags & RCCFileInfo::Directory) - pending.push(child); - } - } - switch (m_format) { - case C_Code: - case Pass1: - writeString("\n};\n\n"); - break; - case Python3_Code: - case Python2_Code: - writeString("\"\n\n"); - break; - default: - break; - } - - return true; -} - -void RCCResourceLibrary::writeMangleNamespaceFunction(const QByteArray &name) -{ - if (m_useNameSpace) { - writeString("QT_RCC_MANGLE_NAMESPACE("); - writeByteArray(name); - writeChar(')'); - } else { - writeByteArray(name); - } -} - -void RCCResourceLibrary::writeAddNamespaceFunction(const QByteArray &name) -{ - if (m_useNameSpace) { - writeString("QT_RCC_PREPEND_NAMESPACE("); - writeByteArray(name); - writeChar(')'); - } else { - writeByteArray(name); - } -} - -bool RCCResourceLibrary::writeInitializer() -{ - if (m_format == C_Code || m_format == Pass1) { - //write("\nQT_BEGIN_NAMESPACE\n"); - QString initNameStr = m_initName; - if (!initNameStr.isEmpty()) { - initNameStr.prepend(QLatin1Char('_')); - initNameStr.replace(QRegExp(QLatin1String("[^a-zA-Z0-9_]")), QLatin1String("_")); - } - QByteArray initName = initNameStr.toLatin1(); - - //init - if (m_useNameSpace) { - writeString("#ifdef QT_NAMESPACE\n" - "# define QT_RCC_PREPEND_NAMESPACE(name) ::QT_NAMESPACE::name\n" - "# define QT_RCC_MANGLE_NAMESPACE0(x) x\n" - "# define QT_RCC_MANGLE_NAMESPACE1(a, b) a##_##b\n" - "# define QT_RCC_MANGLE_NAMESPACE2(a, b) QT_RCC_MANGLE_NAMESPACE1(a,b)\n" - "# define QT_RCC_MANGLE_NAMESPACE(name) QT_RCC_MANGLE_NAMESPACE2( \\\n" - " QT_RCC_MANGLE_NAMESPACE0(name), QT_RCC_MANGLE_NAMESPACE0(QT_NAMESPACE))\n" - "#else\n" - "# define QT_RCC_PREPEND_NAMESPACE(name) name\n" - "# define QT_RCC_MANGLE_NAMESPACE(name) name\n" - "#endif\n\n"); - - writeString("#ifdef QT_NAMESPACE\n" - "namespace QT_NAMESPACE {\n" - "#endif\n\n"); - } - - if (m_root) { - writeString("bool qRegisterResourceData" - "(int, const unsigned char *, " - "const unsigned char *, const unsigned char *);\n"); - writeString("bool qUnregisterResourceData" - "(int, const unsigned char *, " - "const unsigned char *, const unsigned char *);\n\n"); - - if (m_overallFlags & (RCCFileInfo::Compressed | RCCFileInfo::CompressedZstd)) { - // use variable relocations with ELF and Mach-O - writeString("#if defined(__ELF__) || defined(__APPLE__)\n"); - if (m_overallFlags & RCCFileInfo::Compressed) { - writeString("static inline unsigned char qResourceFeatureZlib()\n" - "{\n" - " extern const unsigned char qt_resourceFeatureZlib;\n" - " return qt_resourceFeatureZlib;\n" - "}\n"); - } - if (m_overallFlags & RCCFileInfo::CompressedZstd) { - writeString("static inline unsigned char qResourceFeatureZstd()\n" - "{\n" - " extern const unsigned char qt_resourceFeatureZstd;\n" - " return qt_resourceFeatureZstd;\n" - "}\n"); - } - writeString("#else\n"); - if (m_overallFlags & RCCFileInfo::Compressed) - writeString("unsigned char qResourceFeatureZlib();\n"); - if (m_overallFlags & RCCFileInfo::CompressedZstd) - writeString("unsigned char qResourceFeatureZstd();\n"); - writeString("#endif\n\n"); - } - } - - if (m_useNameSpace) - writeString("#ifdef QT_NAMESPACE\n}\n#endif\n\n"); - - QByteArray initResources = "qInitResources"; - initResources += initName; - - // Work around -Wmissing-declarations warnings. - writeString("int "); - writeMangleNamespaceFunction(initResources); - writeString("();\n"); - - writeString("int "); - writeMangleNamespaceFunction(initResources); - writeString("()\n{\n"); - - if (m_root) { - writeString(" int version = "); - writeDecimal(m_formatVersion); - writeString(";\n "); - writeAddNamespaceFunction("qRegisterResourceData"); - writeString("\n (version, qt_resource_struct, " - "qt_resource_name, qt_resource_data);\n"); - } - writeString(" return 1;\n"); - writeString("}\n\n"); - - //cleanup - QByteArray cleanResources = "qCleanupResources"; - cleanResources += initName; - - // Work around -Wmissing-declarations warnings. - writeString("int "); - writeMangleNamespaceFunction(cleanResources); - writeString("();\n"); - - writeString("int "); - writeMangleNamespaceFunction(cleanResources); - writeString("()\n{\n"); - if (m_root) { - writeString(" int version = "); - writeDecimal(m_formatVersion); - writeString(";\n "); - - // ODR-use certain symbols from QtCore if we require optional features - if (m_overallFlags & RCCFileInfo::Compressed) { - writeString("version += "); - writeAddNamespaceFunction("qResourceFeatureZlib()"); - writeString(";\n "); - } - if (m_overallFlags & RCCFileInfo::CompressedZstd) { - writeString("version += "); - writeAddNamespaceFunction("qResourceFeatureZstd()"); - writeString(";\n "); - } - - writeAddNamespaceFunction("qUnregisterResourceData"); - writeString("\n (version, qt_resource_struct, " - "qt_resource_name, qt_resource_data);\n"); - } - writeString(" return 1;\n"); - writeString("}\n\n"); - - - writeString("namespace {\n" - " struct initializer {\n"); - - if (m_useNameSpace) { - writeByteArray(" initializer() { QT_RCC_MANGLE_NAMESPACE(" + initResources + ")(); }\n" - " ~initializer() { QT_RCC_MANGLE_NAMESPACE(" + cleanResources + ")(); }\n"); - } else { - writeByteArray(" initializer() { " + initResources + "(); }\n" - " ~initializer() { " + cleanResources + "(); }\n"); - } - writeString(" } dummy;\n" - "}\n"); - - } else if (m_format == Binary) { - int i = 4; - char *p = m_out.data(); - p[i++] = 0; - p[i++] = 0; - p[i++] = 0; - p[i++] = m_formatVersion; - - p[i++] = (m_treeOffset >> 24) & 0xff; - p[i++] = (m_treeOffset >> 16) & 0xff; - p[i++] = (m_treeOffset >> 8) & 0xff; - p[i++] = (m_treeOffset >> 0) & 0xff; - - p[i++] = (m_dataOffset >> 24) & 0xff; - p[i++] = (m_dataOffset >> 16) & 0xff; - p[i++] = (m_dataOffset >> 8) & 0xff; - p[i++] = (m_dataOffset >> 0) & 0xff; - - p[i++] = (m_namesOffset >> 24) & 0xff; - p[i++] = (m_namesOffset >> 16) & 0xff; - p[i++] = (m_namesOffset >> 8) & 0xff; - p[i++] = (m_namesOffset >> 0) & 0xff; - - if (m_formatVersion >= 3) { - p[i++] = (m_overallFlags >> 24) & 0xff; - p[i++] = (m_overallFlags >> 16) & 0xff; - p[i++] = (m_overallFlags >> 8) & 0xff; - p[i++] = (m_overallFlags >> 0) & 0xff; - } - } else if (m_format == Python3_Code || m_format == Python2_Code) { - writeString("def qInitResources():\n"); - writeString(" QtCore.qRegisterResourceData(0x"); - write2HexDigits(m_formatVersion); - writeString(", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n"); - writeString("def qCleanupResources():\n"); - writeString(" QtCore.qUnregisterResourceData(0x"); - write2HexDigits(m_formatVersion); - writeString(", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n"); - writeString("qInitResources()\n"); - } - return true; -} - -QT_END_NAMESPACE +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2018 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "rcc.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if QT_CONFIG(zstd) +# include +#endif + +// Note: A copy of this file is used in Qt Designer (qttools/src/designer/src/lib/shared/rcc.cpp) + +QT_BEGIN_NAMESPACE + +enum { + CONSTANT_USENAMESPACE = 1, + CONSTANT_COMPRESSLEVEL_DEFAULT = -1, + CONSTANT_ZSTDCOMPRESSLEVEL_CHECK = 1, // Zstd level to check if compressing is a good idea + CONSTANT_ZSTDCOMPRESSLEVEL_STORE = 14, // Zstd level to actually store the data + CONSTANT_COMPRESSTHRESHOLD_DEFAULT = 70 +}; + +#if QT_CONFIG(zstd) && QT_VERSION >= QT_VERSION_CHECK(6,0,0) +# define CONSTANT_COMPRESSALGO_DEFAULT RCCResourceLibrary::CompressionAlgorithm::Zstd +#elif !defined(QT_NO_COMPRESS) +# define CONSTANT_COMPRESSALGO_DEFAULT RCCResourceLibrary::CompressionAlgorithm::Zlib +#else +# define CONSTANT_COMPRESSALGO_DEFAULT RCCResourceLibrary::CompressionAlgorithm::None +#endif + +void RCCResourceLibrary::write(const char *str, int len) +{ + int n = m_out.size(); + m_out.resize(n + len); + memcpy(m_out.data() + n, str, len); +} + +void RCCResourceLibrary::writeByteArray(const QByteArray &other) +{ + if (m_format == Pass2) { + m_outDevice->write(other); + } else { + m_out.append(other); + } +} + +static inline QString msgOpenReadFailed(const QString &fname, const QString &why) +{ + return QString::fromLatin1("Unable to open %1 for reading: %2\n").arg(fname, why); +} + + +/////////////////////////////////////////////////////////// +// +// RCCFileInfo +// +/////////////////////////////////////////////////////////// + +class RCCFileInfo +{ +public: + enum Flags + { + // must match qresource.cpp + NoFlags = 0x00, + Compressed = 0x01, + Directory = 0x02, + CompressedZstd = 0x04 + }; + + RCCFileInfo(const QString &name = QString(), const QFileInfo &fileInfo = QFileInfo(), + QLocale::Language language = QLocale::C, + QLocale::Country country = QLocale::AnyCountry, + uint flags = NoFlags, + RCCResourceLibrary::CompressionAlgorithm compressAlgo = CONSTANT_COMPRESSALGO_DEFAULT, + int compressLevel = CONSTANT_COMPRESSLEVEL_DEFAULT, + int compressThreshold = CONSTANT_COMPRESSTHRESHOLD_DEFAULT); + ~RCCFileInfo(); + + QString resourceName() const; + +public: + qint64 writeDataBlob(RCCResourceLibrary &lib, qint64 offset, QString *errorMessage); + qint64 writeDataName(RCCResourceLibrary &, qint64 offset); + void writeDataInfo(RCCResourceLibrary &lib); + + int m_flags; + QString m_name; + QLocale::Language m_language; + QLocale::Country m_country; + QFileInfo m_fileInfo; + RCCFileInfo *m_parent; + QMultiHash m_children; + RCCResourceLibrary::CompressionAlgorithm m_compressAlgo; + int m_compressLevel; + int m_compressThreshold; + + qint64 m_nameOffset; + qint64 m_dataOffset; + qint64 m_childOffset; +}; + +RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo, + QLocale::Language language, QLocale::Country country, uint flags, + RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel, int compressThreshold) +{ + m_name = name; + m_fileInfo = fileInfo; + m_language = language; + m_country = country; + m_flags = flags; + m_parent = 0; + m_nameOffset = 0; + m_dataOffset = 0; + m_childOffset = 0; + m_compressAlgo = compressAlgo; + m_compressLevel = compressLevel; + m_compressThreshold = compressThreshold; +} + +RCCFileInfo::~RCCFileInfo() +{ + qDeleteAll(m_children); +} + +QString RCCFileInfo::resourceName() const +{ + QString resource = m_name; + for (RCCFileInfo *p = m_parent; p; p = p->m_parent) + resource = resource.prepend(p->m_name + QLatin1Char('/')); + return QLatin1Char(':') + resource; +} + +void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib) +{ + const bool text = lib.m_format == RCCResourceLibrary::C_Code; + const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1; + const bool python = lib.m_format == RCCResourceLibrary::Python3_Code + || lib.m_format == RCCResourceLibrary::Python2_Code; + //some info + if (text || pass1) { + if (m_language != QLocale::C) { + lib.writeString(" // "); + lib.writeByteArray(resourceName().toLocal8Bit()); + lib.writeString(" ["); + lib.writeByteArray(QByteArray::number(m_country)); + lib.writeString("::"); + lib.writeByteArray(QByteArray::number(m_language)); + lib.writeString("[\n "); + } else { + lib.writeString(" // "); + lib.writeByteArray(resourceName().toLocal8Bit()); + lib.writeString("\n "); + } + } + + //pointer data + if (m_flags & RCCFileInfo::Directory) { + // name offset + lib.writeNumber4(m_nameOffset); + + // flags + lib.writeNumber2(m_flags); + + // child count + lib.writeNumber4(m_children.size()); + + // first child offset + lib.writeNumber4(m_childOffset); + } else { + // name offset + lib.writeNumber4(m_nameOffset); + + // flags + lib.writeNumber2(m_flags); + + // locale + lib.writeNumber2(m_country); + lib.writeNumber2(m_language); + + //data offset + lib.writeNumber4(m_dataOffset); + } + if (text || pass1) + lib.writeChar('\n'); + else if (python) + lib.writeString("\\\n"); + + if (lib.formatVersion() >= 2) { + // last modified time stamp + const QDateTime lastModified = m_fileInfo.lastModified(); + quint64 lastmod = quint64(lastModified.isValid() ? lastModified.toMSecsSinceEpoch() : 0); + static const quint64 sourceDate = 1000 * qgetenv("QT_RCC_SOURCE_DATE_OVERRIDE").toULongLong(); + if (sourceDate != 0) + lastmod = sourceDate; + static const quint64 sourceDate2 = 1000 * qgetenv("SOURCE_DATE_EPOCH").toULongLong(); + if (sourceDate2 != 0) + lastmod = sourceDate2; + lib.writeNumber8(lastmod); + if (text || pass1) + lib.writeChar('\n'); + else if (python) + lib.writeString("\\\n"); + } +} + +qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset, + QString *errorMessage) +{ + const bool text = lib.m_format == RCCResourceLibrary::C_Code; + const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1; + const bool pass2 = lib.m_format == RCCResourceLibrary::Pass2; + const bool binary = lib.m_format == RCCResourceLibrary::Binary; + const bool python = lib.m_format == RCCResourceLibrary::Python3_Code + || lib.m_format == RCCResourceLibrary::Python2_Code; + + //capture the offset + m_dataOffset = offset; + + //find the data to be written + QFile file(m_fileInfo.absoluteFilePath()); + if (!file.open(QFile::ReadOnly)) { + *errorMessage = msgOpenReadFailed(m_fileInfo.absoluteFilePath(), file.errorString()); + return 0; + } + QByteArray data = file.readAll(); + + // Check if compression is useful for this file + if (data.size() != 0) { +#if QT_CONFIG(zstd) + if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best) { + m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zstd; + m_compressLevel = 19; // not ZSTD_maxCLevel(), as 20+ are experimental + } + if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zstd) { + if (lib.m_zstdCCtx == nullptr) + lib.m_zstdCCtx = ZSTD_createCCtx(); + qsizetype size = data.size(); + size = ZSTD_COMPRESSBOUND(size); + + int compressLevel = m_compressLevel; + if (compressLevel < 0) + compressLevel = CONSTANT_ZSTDCOMPRESSLEVEL_CHECK; + + QByteArray compressed(size, Qt::Uninitialized); + char *dst = const_cast(compressed.constData()); + size_t n = ZSTD_compressCCtx(lib.m_zstdCCtx, dst, size, + data.constData(), data.size(), + compressLevel); + if (n * 100.0 < data.size() * 1.0 * (100 - m_compressThreshold) ) { + // compressing is worth it + if (m_compressLevel < 0) { + // heuristic compression, so recompress + n = ZSTD_compressCCtx(lib.m_zstdCCtx, dst, size, + data.constData(), data.size(), + CONSTANT_ZSTDCOMPRESSLEVEL_STORE); + } + if (ZSTD_isError(n)) { + QString msg = QString::fromLatin1("%1: error: compression with zstd failed: %2\n") + .arg(m_name, QString::fromUtf8(ZSTD_getErrorName(n))); + lib.m_errorDevice->write(msg.toUtf8()); + } else if (lib.verbose()) { + QString msg = QString::fromLatin1("%1: note: compressed using zstd (%2 -> %3)\n") + .arg(m_name).arg(data.size()).arg(n); + lib.m_errorDevice->write(msg.toUtf8()); + } + + lib.m_overallFlags |= CompressedZstd; + m_flags |= CompressedZstd; + data = std::move(compressed); + data.truncate(n); + } else if (lib.verbose()) { + QString msg = QString::fromLatin1("%1: note: not compressed\n").arg(m_name); + lib.m_errorDevice->write(msg.toUtf8()); + } + } +#endif +#ifndef QT_NO_COMPRESS + if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best) { + m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zlib; + m_compressLevel = 9; + } + if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zlib) { + QByteArray compressed = + qCompress(reinterpret_cast(data.data()), data.size(), m_compressLevel); + + int compressRatio = int(100.0 * (data.size() - compressed.size()) / data.size()); + if (compressRatio >= m_compressThreshold) { + if (lib.verbose()) { + QString msg = QString::fromLatin1("%1: note: compressed using zlib (%2 -> %3)\n") + .arg(m_name).arg(data.size()).arg(compressed.size()); + lib.m_errorDevice->write(msg.toUtf8()); + } + data = compressed; + lib.m_overallFlags |= Compressed; + m_flags |= Compressed; + } else if (lib.verbose()) { + QString msg = QString::fromLatin1("%1: note: not compressed\n").arg(m_name); + lib.m_errorDevice->write(msg.toUtf8()); + } + } +#endif // QT_NO_COMPRESS + } + + // some info + if (text || pass1) { + lib.writeString(" // "); + lib.writeByteArray(m_fileInfo.absoluteFilePath().toLocal8Bit()); + lib.writeString("\n "); + } + + // write the length + if (text || binary || pass2 || python) + lib.writeNumber4(data.size()); + if (text || pass1) + lib.writeString("\n "); + else if (python) + lib.writeString("\\\n"); + offset += 4; + + // write the payload + const char *p = data.constData(); + if (text || python) { + for (int i = data.size(), j = 0; --i >= 0; --j) { + lib.writeHex(*p++); + if (j == 0) { + if (text) + lib.writeString("\n "); + else + lib.writeString("\\\n"); + j = 16; + } + } + } else if (binary || pass2) { + lib.writeByteArray(data); + } + offset += data.size(); + + // done + if (text || pass1) + lib.writeString("\n "); + else if (python) + lib.writeString("\\\n"); + + return offset; +} + +qint64 RCCFileInfo::writeDataName(RCCResourceLibrary &lib, qint64 offset) +{ + const bool text = lib.m_format == RCCResourceLibrary::C_Code; + const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1; + const bool python = lib.m_format == RCCResourceLibrary::Python3_Code + || lib.m_format == RCCResourceLibrary::Python2_Code; + + // capture the offset + m_nameOffset = offset; + + // some info + if (text || pass1) { + lib.writeString(" // "); + lib.writeByteArray(m_name.toLocal8Bit()); + lib.writeString("\n "); + } + + // write the length + lib.writeNumber2(m_name.length()); + if (text || pass1) + lib.writeString("\n "); + else if (python) + lib.writeString("\\\n"); + offset += 2; + + // write the hash + lib.writeNumber4(qt_hash(m_name)); + if (text || pass1) + lib.writeString("\n "); + else if (python) + lib.writeString("\\\n"); + offset += 4; + + // write the m_name + const QChar *unicode = m_name.unicode(); + for (int i = 0; i < m_name.length(); ++i) { + lib.writeNumber2(unicode[i].unicode()); + if ((text || pass1) && i % 16 == 0) + lib.writeString("\n "); + else if (python && i % 16 == 0) + lib.writeString("\\\n"); + } + offset += m_name.length()*2; + + // done + if (text || pass1) + lib.writeString("\n "); + else if (python) + lib.writeString("\\\n"); + + return offset; +} + + +/////////////////////////////////////////////////////////// +// +// RCCResourceLibrary +// +/////////////////////////////////////////////////////////// + +RCCResourceLibrary::Strings::Strings() : + TAG_RCC(QLatin1String("RCC")), + TAG_RESOURCE(QLatin1String("qresource")), + TAG_FILE(QLatin1String("file")), + ATTRIBUTE_LANG(QLatin1String("lang")), + ATTRIBUTE_PREFIX(QLatin1String("prefix")), + ATTRIBUTE_ALIAS(QLatin1String("alias")), + ATTRIBUTE_THRESHOLD(QLatin1String("threshold")), + ATTRIBUTE_COMPRESS(QLatin1String("compress")), + ATTRIBUTE_COMPRESSALGO(QStringLiteral("compression-algorithm")) +{ +} + +RCCResourceLibrary::RCCResourceLibrary(quint8 formatVersion) + : m_root(0), + m_format(C_Code), + m_verbose(false), + m_compressionAlgo(CONSTANT_COMPRESSALGO_DEFAULT), + m_compressLevel(CONSTANT_COMPRESSLEVEL_DEFAULT), + m_compressThreshold(CONSTANT_COMPRESSTHRESHOLD_DEFAULT), + m_treeOffset(0), + m_namesOffset(0), + m_dataOffset(0), + m_overallFlags(0), + m_useNameSpace(CONSTANT_USENAMESPACE), + m_errorDevice(0), + m_outDevice(0), + m_formatVersion(formatVersion) +{ + m_out.reserve(30 * 1000 * 1000); +#if QT_CONFIG(zstd) + m_zstdCCtx = nullptr; +#endif +} + +RCCResourceLibrary::~RCCResourceLibrary() +{ + delete m_root; +#if QT_CONFIG(zstd) + ZSTD_freeCCtx(m_zstdCCtx); +#endif +} + +enum RCCXmlTag { + RccTag, + ResourceTag, + FileTag +}; +Q_DECLARE_TYPEINFO(RCCXmlTag, Q_PRIMITIVE_TYPE); + +bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, + const QString &fname, QString currentPath, bool listMode) +{ + Q_ASSERT(m_errorDevice); + const QChar slash = QLatin1Char('/'); + if (!currentPath.isEmpty() && !currentPath.endsWith(slash)) + currentPath += slash; + + QXmlStreamReader reader(inputDevice); + QStack tokens; + + QString prefix; + QLocale::Language language = QLocale::c().language(); + QLocale::Country country = QLocale::c().country(); + QString alias; + auto compressAlgo = m_compressionAlgo; + int compressLevel = m_compressLevel; + int compressThreshold = m_compressThreshold; + + while (!reader.atEnd()) { + QXmlStreamReader::TokenType t = reader.readNext(); + switch (t) { + case QXmlStreamReader::StartElement: + if (reader.name() == m_strings.TAG_RCC) { + if (!tokens.isEmpty()) + reader.raiseError(QLatin1String("expected tag")); + else + tokens.push(RccTag); + } else if (reader.name() == m_strings.TAG_RESOURCE) { + if (tokens.isEmpty() || tokens.top() != RccTag) { + reader.raiseError(QLatin1String("unexpected tag")); + } else { + tokens.push(ResourceTag); + + QXmlStreamAttributes attributes = reader.attributes(); + language = QLocale::c().language(); + country = QLocale::c().country(); + + if (attributes.hasAttribute(m_strings.ATTRIBUTE_LANG)) { + QString attribute = attributes.value(m_strings.ATTRIBUTE_LANG).toString(); + QLocale lang = QLocale(attribute); + language = lang.language(); + if (2 == attribute.length()) { + // Language only + country = QLocale::AnyCountry; + } else { + country = lang.country(); + } + } + + prefix.clear(); + if (attributes.hasAttribute(m_strings.ATTRIBUTE_PREFIX)) + prefix = attributes.value(m_strings.ATTRIBUTE_PREFIX).toString(); + if (!prefix.startsWith(slash)) + prefix.prepend(slash); + if (!prefix.endsWith(slash)) + prefix += slash; + } + } else if (reader.name() == m_strings.TAG_FILE) { + if (tokens.isEmpty() || tokens.top() != ResourceTag) { + reader.raiseError(QLatin1String("unexpected tag")); + } else { + tokens.push(FileTag); + + QXmlStreamAttributes attributes = reader.attributes(); + alias.clear(); + if (attributes.hasAttribute(m_strings.ATTRIBUTE_ALIAS)) + alias = attributes.value(m_strings.ATTRIBUTE_ALIAS).toString(); + + compressAlgo = m_compressionAlgo; + compressLevel = m_compressLevel; + compressThreshold = m_compressThreshold; + + QString errorString; + if (attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESSALGO)) + compressAlgo = parseCompressionAlgorithm(attributes.value(m_strings.ATTRIBUTE_COMPRESSALGO), &errorString); + if (errorString.isEmpty() && attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESS)) { + QString value = attributes.value(m_strings.ATTRIBUTE_COMPRESS).toString(); + compressLevel = parseCompressionLevel(compressAlgo, value, &errorString); + } + + // Special case for -no-compress + if (m_compressLevel == -2) + compressAlgo = CompressionAlgorithm::None; + + if (attributes.hasAttribute(m_strings.ATTRIBUTE_THRESHOLD)) + compressThreshold = attributes.value(m_strings.ATTRIBUTE_THRESHOLD).toString().toInt(); + + if (!errorString.isEmpty()) + reader.raiseError(errorString); + } + } else { + reader.raiseError(QString(QLatin1String("unexpected tag: %1")).arg(reader.name().toString())); + } + break; + + case QXmlStreamReader::EndElement: + if (reader.name() == m_strings.TAG_RCC) { + if (!tokens.isEmpty() && tokens.top() == RccTag) + tokens.pop(); + else + reader.raiseError(QLatin1String("unexpected closing tag")); + } else if (reader.name() == m_strings.TAG_RESOURCE) { + if (!tokens.isEmpty() && tokens.top() == ResourceTag) + tokens.pop(); + else + reader.raiseError(QLatin1String("unexpected closing tag")); + } else if (reader.name() == m_strings.TAG_FILE) { + if (!tokens.isEmpty() && tokens.top() == FileTag) + tokens.pop(); + else + reader.raiseError(QLatin1String("unexpected closing tag")); + } + break; + + case QXmlStreamReader::Characters: + if (reader.isWhitespace()) + break; + if (tokens.isEmpty() || tokens.top() != FileTag) { + reader.raiseError(QLatin1String("unexpected text")); + } else { + QString fileName = reader.text().toString(); + if (fileName.isEmpty()) { + const QString msg = QString::fromLatin1("RCC: Warning: Null node in XML of '%1'\n").arg(fname); + m_errorDevice->write(msg.toUtf8()); + } + + if (alias.isNull()) + alias = fileName; + + alias = QDir::cleanPath(alias); + while (alias.startsWith(QLatin1String("../"))) + alias.remove(0, 3); + alias = QDir::cleanPath(m_resourceRoot) + prefix + alias; + + QString absFileName = fileName; + if (QDir::isRelativePath(absFileName)) + absFileName.prepend(currentPath); + QFileInfo file(absFileName); + if (file.isDir()) { + QDir dir(file.filePath()); + if (!alias.endsWith(slash)) + alias += slash; + + QStringList filePaths; + QDirIterator it(dir, QDirIterator::FollowSymlinks|QDirIterator::Subdirectories); + while (it.hasNext()) { + it.next(); + if (it.fileName() == QLatin1String(".") + || it.fileName() == QLatin1String("..")) + continue; + filePaths.append(it.filePath()); + } + + // make rcc output deterministic + std::sort(filePaths.begin(), filePaths.end()); + + for (const QString &filePath : filePaths) { + QFileInfo child(filePath); + const bool arc = addFile( + alias + child.fileName(), + RCCFileInfo(child.fileName(), child, language, country, + child.isDir() ? RCCFileInfo::Directory + : RCCFileInfo::NoFlags, + compressAlgo, compressLevel, compressThreshold)); + if (!arc) + m_failedResources.push_back(child.fileName()); + } + } else if (listMode || file.isFile()) { + const bool arc = + addFile(alias, + RCCFileInfo(alias.section(slash, -1), + file, + language, + country, + RCCFileInfo::NoFlags, + compressAlgo, + compressLevel, + compressThreshold) + ); + if (!arc) + m_failedResources.push_back(absFileName); + } else if (file.exists()) { + m_failedResources.push_back(absFileName); + const QString msg = QString::fromLatin1("RCC: Error in '%1': Entry '%2' is neither a file nor a directory\n") + .arg(fname, fileName); + m_errorDevice->write(msg.toUtf8()); + return false; + } else { + m_failedResources.push_back(absFileName); + const QString msg = QString::fromLatin1("RCC: Error in '%1': Cannot find file '%2'\n") + .arg(fname, fileName); + m_errorDevice->write(msg.toUtf8()); + return false; + } + } + break; + + default: + break; + } + } + + if (reader.hasError()) { + int errorLine = reader.lineNumber(); + int errorColumn = reader.columnNumber(); + QString errorMessage = reader.errorString(); + QString msg = QString::fromLatin1("RCC Parse Error: '%1' Line: %2 Column: %3 [%4]\n").arg(fname).arg(errorLine).arg(errorColumn).arg(errorMessage); + m_errorDevice->write(msg.toUtf8()); + return false; + } + + if (m_root == 0) { + const QString msg = QString::fromLatin1("RCC: Warning: No resources in '%1'.\n").arg(fname); + m_errorDevice->write(msg.toUtf8()); + if (!listMode && m_format == Binary) { + // create dummy entry, otherwise loading with QResource will crash + m_root = new RCCFileInfo(QString(), QFileInfo(), + QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); + } + } + + return true; +} + +bool RCCResourceLibrary::addFile(const QString &alias, const RCCFileInfo &file) +{ + Q_ASSERT(m_errorDevice); + if (file.m_fileInfo.size() > 0xffffffff) { + const QString msg = QString::fromLatin1("File too big: %1\n").arg(file.m_fileInfo.absoluteFilePath()); + m_errorDevice->write(msg.toUtf8()); + return false; + } + if (!m_root) + m_root = new RCCFileInfo(QString(), QFileInfo(), QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); + + RCCFileInfo *parent = m_root; + const QStringList nodes = alias.split(QLatin1Char('/')); + for (int i = 1; i < nodes.size()-1; ++i) { + const QString node = nodes.at(i); + if (node.isEmpty()) + continue; + if (!parent->m_children.contains(node)) { + RCCFileInfo *s = new RCCFileInfo(node, QFileInfo(), QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); + s->m_parent = parent; + parent->m_children.insert(node, s); + parent = s; + } else { + parent = *parent->m_children.constFind(node); + } + } + + const QString filename = nodes.at(nodes.size()-1); + RCCFileInfo *s = new RCCFileInfo(file); + s->m_parent = parent; + typedef QHash::const_iterator ChildConstIterator; + const ChildConstIterator cbegin = parent->m_children.constFind(filename); + const ChildConstIterator cend = parent->m_children.constEnd(); + for (ChildConstIterator it = cbegin; it != cend; ++it) { + if (it.key() == filename && it.value()->m_language == s->m_language && + it.value()->m_country == s->m_country) { + for (const QString &name : qAsConst(m_fileNames)) { + qWarning("%s: Warning: potential duplicate alias detected: '%s'", + qPrintable(name), qPrintable(filename)); + } + break; + } + } + parent->m_children.insert(filename, s); + return true; +} + +void RCCResourceLibrary::reset() +{ + if (m_root) { + delete m_root; + m_root = 0; + } + m_errorDevice = 0; + m_failedResources.clear(); +} + + +bool RCCResourceLibrary::readFiles(bool listMode, QIODevice &errorDevice) +{ + reset(); + m_errorDevice = &errorDevice; + //read in data + if (m_verbose) { + const QString msg = QString::fromLatin1("Processing %1 files [listMode=%2]\n") + .arg(m_fileNames.size()).arg(static_cast(listMode)); + m_errorDevice->write(msg.toUtf8()); + } + for (int i = 0; i < m_fileNames.size(); ++i) { + QFile fileIn; + QString fname = m_fileNames.at(i); + QString pwd; + if (fname == QLatin1String("-")) { + fname = QLatin1String("(stdin)"); + pwd = QDir::currentPath(); + fileIn.setFileName(fname); + if (!fileIn.open(stdin, QIODevice::ReadOnly)) { + m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8()); + return false; + } + } else { + pwd = QFileInfo(fname).path(); + fileIn.setFileName(fname); + if (!fileIn.open(QIODevice::ReadOnly)) { + m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8()); + return false; + } + } + if (m_verbose) { + const QString msg = QString::fromLatin1("Interpreting %1\n").arg(fname); + m_errorDevice->write(msg.toUtf8()); + } + + if (!interpretResourceFile(&fileIn, fname, pwd, listMode)) + return false; + } + return true; +} + +QStringList RCCResourceLibrary::dataFiles() const +{ + QStringList ret; + QStack pending; + + if (!m_root) + return ret; + pending.push(m_root); + while (!pending.isEmpty()) { + RCCFileInfo *file = pending.pop(); + for (QHash::iterator it = file->m_children.begin(); + it != file->m_children.end(); ++it) { + RCCFileInfo *child = it.value(); + if (child->m_flags & RCCFileInfo::Directory) + pending.push(child); + else + ret.append(child->m_fileInfo.filePath()); + } + } + return ret; +} + +// Determine map of resource identifier (':/newPrefix/images/p1.png') to file via recursion +static void resourceDataFileMapRecursion(const RCCFileInfo *m_root, const QString &path, RCCResourceLibrary::ResourceDataFileMap &m) +{ + typedef QHash::const_iterator ChildConstIterator; + const QChar slash = QLatin1Char('/'); + const ChildConstIterator cend = m_root->m_children.constEnd(); + for (ChildConstIterator it = m_root->m_children.constBegin(); it != cend; ++it) { + const RCCFileInfo *child = it.value(); + const QString childName = path + slash + child->m_name; + if (child->m_flags & RCCFileInfo::Directory) { + resourceDataFileMapRecursion(child, childName, m); + } else { + m.insert(childName, child->m_fileInfo.filePath()); + } + } +} + +RCCResourceLibrary::ResourceDataFileMap RCCResourceLibrary::resourceDataFileMap() const +{ + ResourceDataFileMap rc; + if (m_root) + resourceDataFileMapRecursion(m_root, QString(QLatin1Char(':')), rc); + return rc; +} + +RCCResourceLibrary::CompressionAlgorithm RCCResourceLibrary::parseCompressionAlgorithm(QStringView value, QString *errorMsg) +{ + if (value == QLatin1String("best")) + return CompressionAlgorithm::Best; + if (value == QLatin1String("zlib")) { +#ifdef QT_NO_COMPRESS + *errorMsg = QLatin1String("zlib support not compiled in"); +#else + return CompressionAlgorithm::Zlib; +#endif + } else if (value == QLatin1String("zstd")) { +#if QT_CONFIG(zstd) + return CompressionAlgorithm::Zstd; +#else + *errorMsg = QLatin1String("Zstandard support not compiled in"); +#endif + } else if (value != QLatin1String("none")) { + *errorMsg = QString::fromLatin1("Unknown compression algorithm '%1'").arg(value); + } + + return CompressionAlgorithm::None; +} + +int RCCResourceLibrary::parseCompressionLevel(CompressionAlgorithm algo, const QString &level, QString *errorMsg) +{ + bool ok; + int c = level.toInt(&ok); + if (ok) { + switch (algo) { + case CompressionAlgorithm::None: + case CompressionAlgorithm::Best: + return 0; + case CompressionAlgorithm::Zlib: + if (c >= 1 && c <= 9) + return c; + break; + case CompressionAlgorithm::Zstd: +#if QT_CONFIG(zstd) + if (c >= 0 && c <= ZSTD_maxCLevel()) + return c; +#endif + break; + } + } + + *errorMsg = QString::fromLatin1("invalid compression level '%1'").arg(level); + return 0; +} + +bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &tempDevice, QIODevice &errorDevice) +{ + m_errorDevice = &errorDevice; + + if (m_format == Pass2) { + const char pattern[] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' }; + bool foundSignature = false; + + while (true) { + char c; + for (int i = 0; i < 8; ) { + if (!tempDevice.getChar(&c)) { + if (foundSignature) + return true; + m_errorDevice->write("No data signature found\n"); + return false; + } + if (c == pattern[i]) { + ++i; + } else { + for (int k = 0; k < i; ++k) + outDevice.putChar(pattern[k]); + outDevice.putChar(c); + i = 0; + } + } + + m_outDevice = &outDevice; + quint64 start = outDevice.pos(); + writeDataBlobs(); + quint64 len = outDevice.pos() - start; + + tempDevice.seek(tempDevice.pos() + len - 8); + foundSignature = true; + } + } + + //write out + if (m_verbose) + m_errorDevice->write("Outputting code\n"); + if (!writeHeader()) { + m_errorDevice->write("Could not write header\n"); + return false; + } + if (m_root) { + if (!writeDataBlobs()) { + m_errorDevice->write("Could not write data blobs.\n"); + return false; + } + if (!writeDataNames()) { + m_errorDevice->write("Could not write file names\n"); + return false; + } + if (!writeDataStructure()) { + m_errorDevice->write("Could not write data tree\n"); + return false; + } + } + if (!writeInitializer()) { + m_errorDevice->write("Could not write footer\n"); + return false; + } + outDevice.write(m_out.constData(), m_out.size()); + return true; +} + +void RCCResourceLibrary::writeDecimal(int value) +{ + Q_ASSERT(m_format != RCCResourceLibrary::Binary); + char buf[std::numeric_limits::digits10 + 2]; + int n = snprintf(buf, sizeof(buf), "%d", value); + write(buf, n); +} + +static const char hexDigits[] = "0123456789abcdef"; + +inline void RCCResourceLibrary::write2HexDigits(quint8 number) +{ + writeChar(hexDigits[number >> 4]); + writeChar(hexDigits[number & 0xf]); +} + +void RCCResourceLibrary::writeHex(quint8 tmp) +{ + switch (m_format) { + case RCCResourceLibrary::Python3_Code: + case RCCResourceLibrary::Python2_Code: + if (tmp >= 32 && tmp < 127 && tmp != '"' && tmp != '\\') { + writeChar(char(tmp)); + } else { + writeChar('\\'); + writeChar('x'); + write2HexDigits(tmp); + } + break; + default: + writeChar('0'); + writeChar('x'); + if (tmp < 16) + writeChar(hexDigits[tmp]); + else + write2HexDigits(tmp); + writeChar(','); + break; + } +} + +void RCCResourceLibrary::writeNumber2(quint16 number) +{ + if (m_format == RCCResourceLibrary::Binary) { + writeChar(number >> 8); + writeChar(number); + } else { + writeHex(number >> 8); + writeHex(number); + } +} + +void RCCResourceLibrary::writeNumber4(quint32 number) +{ + if (m_format == RCCResourceLibrary::Pass2) { + m_outDevice->putChar(char(number >> 24)); + m_outDevice->putChar(char(number >> 16)); + m_outDevice->putChar(char(number >> 8)); + m_outDevice->putChar(char(number)); + } else if (m_format == RCCResourceLibrary::Binary) { + writeChar(number >> 24); + writeChar(number >> 16); + writeChar(number >> 8); + writeChar(number); + } else { + writeHex(number >> 24); + writeHex(number >> 16); + writeHex(number >> 8); + writeHex(number); + } +} + +void RCCResourceLibrary::writeNumber8(quint64 number) +{ + if (m_format == RCCResourceLibrary::Pass2) { + m_outDevice->putChar(char(number >> 56)); + m_outDevice->putChar(char(number >> 48)); + m_outDevice->putChar(char(number >> 40)); + m_outDevice->putChar(char(number >> 32)); + m_outDevice->putChar(char(number >> 24)); + m_outDevice->putChar(char(number >> 16)); + m_outDevice->putChar(char(number >> 8)); + m_outDevice->putChar(char(number)); + } else if (m_format == RCCResourceLibrary::Binary) { + writeChar(number >> 56); + writeChar(number >> 48); + writeChar(number >> 40); + writeChar(number >> 32); + writeChar(number >> 24); + writeChar(number >> 16); + writeChar(number >> 8); + writeChar(number); + } else { + writeHex(number >> 56); + writeHex(number >> 48); + writeHex(number >> 40); + writeHex(number >> 32); + writeHex(number >> 24); + writeHex(number >> 16); + writeHex(number >> 8); + writeHex(number); + } +} + +bool RCCResourceLibrary::writeHeader() +{ + switch (m_format) { + case C_Code: + case Pass1: + writeString("/****************************************************************************\n"); + writeString("** Resource object code\n"); + writeString("**\n"); + writeString("** Created by: The Resource Compiler for Qt version "); + writeByteArray(QT_VERSION_STR); + writeString("\n**\n"); + writeString("** WARNING! All changes made in this file will be lost!\n"); + writeString( "*****************************************************************************/\n\n"); + break; + case Python3_Code: + case Python2_Code: + writeString("# Resource object code (Python "); + writeChar(m_format == Python3_Code ? '3' : '2'); + writeString(")\n"); + writeString("# Created by: object code\n"); + writeString("# Created by: The Resource Compiler for Qt version "); + writeByteArray(QT_VERSION_STR); + writeString("\n"); + writeString("# WARNING! All changes made in this file will be lost!\n\n"); + writeString("from PySide2 import QtCore\n\n"); + break; + case Binary: + writeString("qres"); + writeNumber4(0); + writeNumber4(0); + writeNumber4(0); + writeNumber4(0); + if (m_formatVersion >= 3) + writeNumber4(m_overallFlags); + break; + default: + break; + } + return true; +} + +bool RCCResourceLibrary::writeDataBlobs() +{ + Q_ASSERT(m_errorDevice); + switch (m_format) { + case C_Code: + writeString("static const unsigned char qt_resource_data[] = {\n"); + break; + case Python3_Code: + writeString("qt_resource_data = b\"\\\n"); + break; + case Python2_Code: + writeString("qt_resource_data = \"\\\n"); + break; + case Binary: + m_dataOffset = m_out.size(); + break; + default: + break; + } + + if (!m_root) + return false; + + QStack pending; + pending.push(m_root); + qint64 offset = 0; + QString errorMessage; + while (!pending.isEmpty()) { + RCCFileInfo *file = pending.pop(); + for (QHash::iterator it = file->m_children.begin(); + it != file->m_children.end(); ++it) { + RCCFileInfo *child = it.value(); + if (child->m_flags & RCCFileInfo::Directory) + pending.push(child); + else { + offset = child->writeDataBlob(*this, offset, &errorMessage); + if (offset == 0) { + m_errorDevice->write(errorMessage.toUtf8()); + return false; + } + } + } + } + switch (m_format) { + case C_Code: + writeString("\n};\n\n"); + break; + case Python3_Code: + case Python2_Code: + writeString("\"\n\n"); + break; + case Pass1: + if (offset < 8) + offset = 8; + writeString("\nstatic const unsigned char qt_resource_data["); + writeByteArray(QByteArray::number(offset)); + writeString("] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' };\n\n"); + break; + default: + break; + } + return true; +} + +bool RCCResourceLibrary::writeDataNames() +{ + switch (m_format) { + case C_Code: + case Pass1: + writeString("static const unsigned char qt_resource_name[] = {\n"); + break; + case Python3_Code: + writeString("qt_resource_name = b\"\\\n"); + break; + case Python2_Code: + writeString("qt_resource_name = \"\\\n"); + break; + case Binary: + m_namesOffset = m_out.size(); + break; + default: + break; + } + + QHash names; + QStack pending; + + if (!m_root) + return false; + + pending.push(m_root); + qint64 offset = 0; + while (!pending.isEmpty()) { + RCCFileInfo *file = pending.pop(); + for (QHash::iterator it = file->m_children.begin(); + it != file->m_children.end(); ++it) { + RCCFileInfo *child = it.value(); + if (child->m_flags & RCCFileInfo::Directory) + pending.push(child); + if (names.contains(child->m_name)) { + child->m_nameOffset = names.value(child->m_name); + } else { + names.insert(child->m_name, offset); + offset = child->writeDataName(*this, offset); + } + } + } + switch (m_format) { + case C_Code: + case Pass1: + writeString("\n};\n\n"); + break; + case Python3_Code: + case Python2_Code: + writeString("\"\n\n"); + break; + default: + break; + } + return true; +} + +struct qt_rcc_compare_hash +{ + typedef bool result_type; + result_type operator()(const RCCFileInfo *left, const RCCFileInfo *right) const + { + return qt_hash(left->m_name) < qt_hash(right->m_name); + } +}; + +bool RCCResourceLibrary::writeDataStructure() +{ + switch (m_format) { + case C_Code: + case Pass1: + writeString("static const unsigned char qt_resource_struct[] = {\n"); + break; + case Python3_Code: + writeString("qt_resource_struct = b\"\\\n"); + break; + case Python2_Code: + writeString("qt_resource_struct = \"\\\n"); + break; + case Binary: + m_treeOffset = m_out.size(); + break; + default: + break; + } + + QStack pending; + + if (!m_root) + return false; + + //calculate the child offsets (flat) + pending.push(m_root); + int offset = 1; + while (!pending.isEmpty()) { + RCCFileInfo *file = pending.pop(); + file->m_childOffset = offset; + + //sort by hash value for binary lookup + QList m_children = file->m_children.values(); + std::sort(m_children.begin(), m_children.end(), qt_rcc_compare_hash()); + + //write out the actual data now + for (int i = 0; i < m_children.size(); ++i) { + RCCFileInfo *child = m_children.at(i); + ++offset; + if (child->m_flags & RCCFileInfo::Directory) + pending.push(child); + } + } + + //write out the structure (ie iterate again!) + pending.push(m_root); + m_root->writeDataInfo(*this); + while (!pending.isEmpty()) { + RCCFileInfo *file = pending.pop(); + + //sort by hash value for binary lookup + QList m_children = file->m_children.values(); + std::sort(m_children.begin(), m_children.end(), qt_rcc_compare_hash()); + + //write out the actual data now + for (int i = 0; i < m_children.size(); ++i) { + RCCFileInfo *child = m_children.at(i); + child->writeDataInfo(*this); + if (child->m_flags & RCCFileInfo::Directory) + pending.push(child); + } + } + switch (m_format) { + case C_Code: + case Pass1: + writeString("\n};\n\n"); + break; + case Python3_Code: + case Python2_Code: + writeString("\"\n\n"); + break; + default: + break; + } + + return true; +} + +void RCCResourceLibrary::writeMangleNamespaceFunction(const QByteArray &name) +{ + if (m_useNameSpace) { + writeString("QT_RCC_MANGLE_NAMESPACE("); + writeByteArray(name); + writeChar(')'); + } else { + writeByteArray(name); + } +} + +void RCCResourceLibrary::writeAddNamespaceFunction(const QByteArray &name) +{ + if (m_useNameSpace) { + writeString("QT_RCC_PREPEND_NAMESPACE("); + writeByteArray(name); + writeChar(')'); + } else { + writeByteArray(name); + } +} + +bool RCCResourceLibrary::writeInitializer() +{ + if (m_format == C_Code || m_format == Pass1) { + //write("\nQT_BEGIN_NAMESPACE\n"); + QString initNameStr = m_initName; + if (!initNameStr.isEmpty()) { + initNameStr.prepend(QLatin1Char('_')); + initNameStr.replace(QRegExp(QLatin1String("[^a-zA-Z0-9_]")), QLatin1String("_")); + } + QByteArray initName = initNameStr.toLatin1(); + + //init + if (m_useNameSpace) { + writeString("#ifdef QT_NAMESPACE\n" + "# define QT_RCC_PREPEND_NAMESPACE(name) ::QT_NAMESPACE::name\n" + "# define QT_RCC_MANGLE_NAMESPACE0(x) x\n" + "# define QT_RCC_MANGLE_NAMESPACE1(a, b) a##_##b\n" + "# define QT_RCC_MANGLE_NAMESPACE2(a, b) QT_RCC_MANGLE_NAMESPACE1(a,b)\n" + "# define QT_RCC_MANGLE_NAMESPACE(name) QT_RCC_MANGLE_NAMESPACE2( \\\n" + " QT_RCC_MANGLE_NAMESPACE0(name), QT_RCC_MANGLE_NAMESPACE0(QT_NAMESPACE))\n" + "#else\n" + "# define QT_RCC_PREPEND_NAMESPACE(name) name\n" + "# define QT_RCC_MANGLE_NAMESPACE(name) name\n" + "#endif\n\n"); + + writeString("#ifdef QT_NAMESPACE\n" + "namespace QT_NAMESPACE {\n" + "#endif\n\n"); + } + + if (m_root) { + writeString("bool qRegisterResourceData" + "(int, const unsigned char *, " + "const unsigned char *, const unsigned char *);\n"); + writeString("bool qUnregisterResourceData" + "(int, const unsigned char *, " + "const unsigned char *, const unsigned char *);\n\n"); + + if (m_overallFlags & (RCCFileInfo::Compressed | RCCFileInfo::CompressedZstd)) { + // use variable relocations with ELF and Mach-O + writeString("#if defined(__ELF__) || defined(__APPLE__)\n"); + if (m_overallFlags & RCCFileInfo::Compressed) { + writeString("static inline unsigned char qResourceFeatureZlib()\n" + "{\n" + " extern const unsigned char qt_resourceFeatureZlib;\n" + " return qt_resourceFeatureZlib;\n" + "}\n"); + } + if (m_overallFlags & RCCFileInfo::CompressedZstd) { + writeString("static inline unsigned char qResourceFeatureZstd()\n" + "{\n" + " extern const unsigned char qt_resourceFeatureZstd;\n" + " return qt_resourceFeatureZstd;\n" + "}\n"); + } + writeString("#else\n"); + if (m_overallFlags & RCCFileInfo::Compressed) + writeString("unsigned char qResourceFeatureZlib();\n"); + if (m_overallFlags & RCCFileInfo::CompressedZstd) + writeString("unsigned char qResourceFeatureZstd();\n"); + writeString("#endif\n\n"); + } + } + + if (m_useNameSpace) + writeString("#ifdef QT_NAMESPACE\n}\n#endif\n\n"); + + QByteArray initResources = "qInitResources"; + initResources += initName; + + // Work around -Wmissing-declarations warnings. + writeString("int "); + writeMangleNamespaceFunction(initResources); + writeString("();\n"); + + writeString("int "); + writeMangleNamespaceFunction(initResources); + writeString("()\n{\n"); + + if (m_root) { + writeString(" int version = "); + writeDecimal(m_formatVersion); + writeString(";\n "); + writeAddNamespaceFunction("qRegisterResourceData"); + writeString("\n (version, qt_resource_struct, " + "qt_resource_name, qt_resource_data);\n"); + } + writeString(" return 1;\n"); + writeString("}\n\n"); + + //cleanup + QByteArray cleanResources = "qCleanupResources"; + cleanResources += initName; + + // Work around -Wmissing-declarations warnings. + writeString("int "); + writeMangleNamespaceFunction(cleanResources); + writeString("();\n"); + + writeString("int "); + writeMangleNamespaceFunction(cleanResources); + writeString("()\n{\n"); + if (m_root) { + writeString(" int version = "); + writeDecimal(m_formatVersion); + writeString(";\n "); + + // ODR-use certain symbols from QtCore if we require optional features + if (m_overallFlags & RCCFileInfo::Compressed) { + writeString("version += "); + writeAddNamespaceFunction("qResourceFeatureZlib()"); + writeString(";\n "); + } + if (m_overallFlags & RCCFileInfo::CompressedZstd) { + writeString("version += "); + writeAddNamespaceFunction("qResourceFeatureZstd()"); + writeString(";\n "); + } + + writeAddNamespaceFunction("qUnregisterResourceData"); + writeString("\n (version, qt_resource_struct, " + "qt_resource_name, qt_resource_data);\n"); + } + writeString(" return 1;\n"); + writeString("}\n\n"); + + + writeString("namespace {\n" + " struct initializer {\n"); + + if (m_useNameSpace) { + writeByteArray(" initializer() { QT_RCC_MANGLE_NAMESPACE(" + initResources + ")(); }\n" + " ~initializer() { QT_RCC_MANGLE_NAMESPACE(" + cleanResources + ")(); }\n"); + } else { + writeByteArray(" initializer() { " + initResources + "(); }\n" + " ~initializer() { " + cleanResources + "(); }\n"); + } + writeString(" } dummy;\n" + "}\n"); + + } else if (m_format == Binary) { + int i = 4; + char *p = m_out.data(); + p[i++] = 0; + p[i++] = 0; + p[i++] = 0; + p[i++] = m_formatVersion; + + p[i++] = (m_treeOffset >> 24) & 0xff; + p[i++] = (m_treeOffset >> 16) & 0xff; + p[i++] = (m_treeOffset >> 8) & 0xff; + p[i++] = (m_treeOffset >> 0) & 0xff; + + p[i++] = (m_dataOffset >> 24) & 0xff; + p[i++] = (m_dataOffset >> 16) & 0xff; + p[i++] = (m_dataOffset >> 8) & 0xff; + p[i++] = (m_dataOffset >> 0) & 0xff; + + p[i++] = (m_namesOffset >> 24) & 0xff; + p[i++] = (m_namesOffset >> 16) & 0xff; + p[i++] = (m_namesOffset >> 8) & 0xff; + p[i++] = (m_namesOffset >> 0) & 0xff; + + if (m_formatVersion >= 3) { + p[i++] = (m_overallFlags >> 24) & 0xff; + p[i++] = (m_overallFlags >> 16) & 0xff; + p[i++] = (m_overallFlags >> 8) & 0xff; + p[i++] = (m_overallFlags >> 0) & 0xff; + } + } else if (m_format == Python3_Code || m_format == Python2_Code) { + writeString("def qInitResources():\n"); + writeString(" QtCore.qRegisterResourceData(0x"); + write2HexDigits(m_formatVersion); + writeString(", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n"); + writeString("def qCleanupResources():\n"); + writeString(" QtCore.qUnregisterResourceData(0x"); + write2HexDigits(m_formatVersion); + writeString(", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n"); + writeString("qInitResources()\n"); + } + return true; +} + +QT_END_NAMESPACE diff --git a/src/rcc.pro b/src/rcc.pro index 96e1f4a..b7ca7bc 100644 --- a/src/rcc.pro +++ b/src/rcc.pro @@ -17,4 +17,8 @@ DEFINES += STATIC include(rcc.pri) SOURCES += main.cpp -RC_FILE = rcc.rc +QMAKE_TARGET_DESCRIPTION="Qt Resource Compiler and Decompiler (RccExtended)" +QMAKE_TARGET_COPYRIGHT="Copyright (C) 2022, The Qt Company Ltd." + +VERSION=5.15.2 +RC_ICONS=rcc.ico diff --git a/src/rcc.rc b/src/rcc.rc deleted file mode 100644 index 39b822f..0000000 --- a/src/rcc.rc +++ /dev/null @@ -1,32 +0,0 @@ -#include "winver.h" - -IDI_ICON1 ICON DISCARDABLE "rcc.ico" - -VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,15,0,0 - PRODUCTVERSION 1,0,0,0 - FILEFLAGS 0x0L - FILEFLAGSMASK 0x3fL - FILEOS 0x00040004L - FILETYPE 0x1L - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "000004b0" - BEGIN - VALUE "CompanyName", "zed" - VALUE "FileDescription", "Qt resource (de)compiler" - VALUE "FileVersion", "5.15.0.0" - VALUE "LegalCopyright", "Copyright (C) 2020, The Qt Company Ltd." - VALUE "InternalName", "rcc" - VALUE "OriginalFilename", "rcc.exe" - VALUE "ProductName", "Qt resource (de)compiler" - VALUE "ProductVersion", "1.0.0.0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x0, 1200 - END -END