qt6-bb10/src/corelib/io/qresource.cpp

1792 lines
54 KiB
C++

// Copyright (C) 2019 The Qt Company Ltd.
// Copyright (C) 2020 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qresource.h"
#include "qresource_p.h"
#include "qresource_iterator_p.h"
#include "qset.h"
#include <private/qlocking_p.h>
#include "qdebug.h"
#include "qlocale.h"
#include "qglobal.h"
#include "qlist.h"
#include "qdatetime.h"
#include "qbytearray.h"
#include "qstringlist.h"
#include "qendian.h"
#include <qshareddata.h>
#include <qplatformdefs.h>
#include <qendian.h>
#include "private/qabstractfileengine_p.h"
#include "private/qduplicatetracker_p.h"
#include "private/qnumeric_p.h"
#include "private/qsimd_p.h"
#include "private/qtools_p.h"
#include "private/qsystemerror_p.h"
#ifndef QT_NO_COMPRESS
# include <zconf.h>
# include <zlib.h>
#endif
#if QT_CONFIG(zstd)
# include <zstd.h>
#endif
#if defined(Q_OS_UNIX) && !defined(Q_OS_INTEGRITY)
# define QT_USE_MMAP
# include <sys/mman.h>
# ifdef Q_OS_LINUX
// since 5.7, so define in case we're being compiled with older kernel headers
# define MREMAP_DONTUNMAP 4
# elif defined(Q_OS_DARWIN)
# include <mach/mach.h>
# include <mach/vm_map.h>
# endif
#endif
#ifdef Q_OS_WIN
# include <qt_windows.h>
#endif
//#define DEBUG_RESOURCE_MATCH
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
// Symbols used by code generated by RCC.
// They cause compilation errors if the RCC content couldn't
// be interpreted by this QtCore version.
#if defined(__ELF__) || defined(__APPLE__) // same as RCC generates
# define RCC_FEATURE_SYMBOL(feature) \
extern Q_CORE_EXPORT const quint8 qt_resourceFeature ## feature; \
const quint8 qt_resourceFeature ## feature = 0;
#else
# define RCC_FEATURE_SYMBOL(feature) \
Q_CORE_EXPORT quint8 qResourceFeature ## feature() { return 0; }
#endif
#ifndef QT_NO_COMPRESS
RCC_FEATURE_SYMBOL(Zlib)
#endif
#if QT_CONFIG(zstd)
RCC_FEATURE_SYMBOL(Zstd)
#endif
#undef RCC_FEATURE_SYMBOL
namespace {
class QStringSplitter
{
public:
explicit QStringSplitter(QStringView sv)
: m_data(sv.data()), m_len(sv.size())
{
}
inline bool hasNext()
{
while (m_pos < m_len && m_data[m_pos] == m_splitChar)
++m_pos;
return m_pos < m_len;
}
inline QStringView next()
{
const qsizetype start = m_pos;
while (m_pos < m_len && m_data[m_pos] != m_splitChar)
++m_pos;
return QStringView(m_data + start, m_pos - start);
}
const QChar *m_data;
qsizetype m_len;
qsizetype m_pos = 0;
QChar m_splitChar = u'/';
};
// resource glue
class QResourceRoot
{
public:
enum Flags {
// must match rcc.h
Compressed = 0x01,
Directory = 0x02,
CompressedZstd = 0x04
};
private:
const uchar *tree, *names, *payloads;
int version;
inline int findOffset(int node) const { return node * (14 + (version >= 0x02 ? 8 : 0)); } //sizeof each tree element
uint hash(int node) const;
QString name(int node) const;
short flags(int node) const;
public:
mutable QAtomicInt ref;
inline QResourceRoot(): tree(nullptr), names(nullptr), payloads(nullptr), version(0) {}
inline QResourceRoot(int version, const uchar *t, const uchar *n, const uchar *d) { setSource(version, t, n, d); }
virtual ~QResourceRoot() { }
int findNode(const QString &path, const QLocale &locale=QLocale()) const;
inline bool isContainer(int node) const { return flags(node) & Directory; }
QResource::Compression compressionAlgo(int node)
{
uint compressionFlags = flags(node) & (Compressed | CompressedZstd);
if (compressionFlags == Compressed)
return QResource::ZlibCompression;
if (compressionFlags == CompressedZstd)
return QResource::ZstdCompression;
return QResource::NoCompression;
}
const uchar *data(int node, qint64 *size) const;
qint64 lastModified(int node) const;
QStringList children(int node) const;
virtual QString mappingRoot() const { return QString(); }
bool mappingRootSubdir(const QString &path, QString *match = nullptr) const;
inline bool operator==(const QResourceRoot &other) const
{ return tree == other.tree && names == other.names && payloads == other.payloads && version == other.version; }
inline bool operator!=(const QResourceRoot &other) const
{ return !operator==(other); }
enum ResourceRootType { Resource_Builtin, Resource_File, Resource_Buffer };
virtual ResourceRootType type() const { return Resource_Builtin; }
protected:
inline void setSource(int v, const uchar *t, const uchar *n, const uchar *d) {
tree = t;
names = n;
payloads = d;
version = v;
}
};
static QString cleanPath(const QString &_path)
{
QString path = QDir::cleanPath(_path);
// QDir::cleanPath does not remove two trailing slashes under _Windows_
// due to support for UNC paths. Remove those manually.
if (path.startsWith("//"_L1))
path.remove(0, 1);
return path;
}
} // unnamed namespace
Q_DECLARE_TYPEINFO(QResourceRoot, Q_RELOCATABLE_TYPE);
typedef QList<QResourceRoot*> ResourceList;
namespace {
struct QResourceGlobalData
{
QRecursiveMutex resourceMutex;
ResourceList resourceList;
};
}
Q_GLOBAL_STATIC(QResourceGlobalData, resourceGlobalData)
static inline QRecursiveMutex &resourceMutex()
{ return resourceGlobalData->resourceMutex; }
static inline ResourceList *resourceList()
{ return &resourceGlobalData->resourceList; }
/*!
\class QResource
\inmodule QtCore
\brief The QResource class provides an interface for reading directly from resources.
\ingroup io
\reentrant
\since 4.2
QResource is an object that represents a set of data (and possibly
children) relating to a single resource entity. QResource gives direct
access to the bytes in their raw format. In this way direct access
allows reading data without buffer copying or indirection. Indirection
is often useful when interacting with the resource entity as if it is a
file, this can be achieved with QFile. The data and children behind a
QResource are normally compiled into an application/library, but it is
also possible to load a resource at runtime. When loaded at run time
the resource file will be loaded as one big set of data and then given
out in pieces via references into the resource tree.
A QResource can either be loaded with an absolute path, either treated
as a file system rooted with a \c{/} character, or in resource notation
rooted with a \c{:} character. A relative resource can also be opened
which will be found in the list of paths returned by QDir::searchPaths().
A QResource that is representing a file will have data backing it, this
data can possibly be compressed, in which case qUncompress() must be
used to access the real data; this happens implicitly when accessed
through a QFile. A QResource that is representing a directory will have
only children and no data.
\section1 Dynamic Resource Loading
A resource can be left out of an application's binary and loaded when
it is needed at run-time by using the registerResource() function. The
resource file passed into registerResource() must be a binary resource
as created by rcc. Further information about binary resources can be
found in \l{The Qt Resource System} documentation.
This can often be useful when loading a large set of application icons
that may change based on a setting, or that can be edited by a user and
later recreated. The resource is immediately loaded into memory, either
as a result of a single file read operation, or as a memory mapped file.
This approach can prove to be a significant performance gain as only a
single file will be loaded, and pieces of data will be given out via the
path requested in setFileName().
The unregisterResource() function removes a reference to a particular
file. If there are QResource objects that currently reference resources related
to the unregistered file, they will continue to be valid but the resource
file itself will be removed from the resource roots, and thus no further
QResource can be created pointing into this resource data. The resource
itself will be unmapped from memory when the last QResource that points
to it is destroyed.
\section2 Corruption and Security
The QResource class performs some checks on the file passed to determine
whether it is supported by the current version of Qt. Those tests are only
to check the file header does not request features (such as Zstandard
decompression) that have not been compiled in or that the file is not of a
future version of Qt. They do not confirm the validity of the entire file.
QResource should not be used on files whose provenance cannot be trusted.
Applications should be designed to attempt to load only resource files
whose provenance is at least as trustworthy as that of the application
itself or its plugins.
\sa {The Qt Resource System}, QFile, QDir, QFileInfo
*/
/*!
\enum QResource::Compression
\since 5.13
This enum is used by compressionAlgorithm() to indicate which algorithm the
RCC tool used to compress the payload.
\value NoCompression Contents are not compressed
\value ZlibCompression Contents are compressed using \l{https://zlib.net}{zlib} and can
be decompressed using the qUncompress() function.
\value ZstdCompression Contents are compressed using \l{Zstandard Site}{zstd}. To
decompress, use the \c{ZSTD_decompress} function from the zstd
library.
\sa compressionAlgorithm()
*/
class QResourcePrivate {
public:
inline QResourcePrivate(QResource *_q) : q_ptr(_q) { clear(); }
inline ~QResourcePrivate() { clear(); }
void ensureInitialized() const;
void ensureChildren() const;
qint64 uncompressedSize() const Q_DECL_PURE_FUNCTION;
qsizetype decompress(char *buffer, qsizetype bufferSize) const;
bool load(const QString &file);
void clear();
static bool mayRemapData(const QResource &resource);
QLocale locale;
QString fileName, absoluteFilePath;
QList<QResourceRoot *> related;
qint64 size;
qint64 lastModified;
const uchar *data;
mutable QStringList children;
quint8 compressionAlgo;
bool container;
/* 2 or 6 padding bytes */
QResource *q_ptr;
Q_DECLARE_PUBLIC(QResource)
};
void QResourcePrivate::clear()
{
absoluteFilePath.clear();
compressionAlgo = QResource::NoCompression;
data = nullptr;
size = 0;
children.clear();
lastModified = 0;
container = 0;
for (int i = 0; i < related.size(); ++i) {
QResourceRoot *root = related.at(i);
if (!root->ref.deref())
delete root;
}
related.clear();
}
bool QResourcePrivate::load(const QString &file)
{
related.clear();
const auto locker = qt_scoped_lock(resourceMutex());
const ResourceList *list = resourceList();
QString cleaned = cleanPath(file);
for (int i = 0; i < list->size(); ++i) {
QResourceRoot *res = list->at(i);
const int node = res->findNode(cleaned, locale);
if (node != -1) {
if (related.isEmpty()) {
container = res->isContainer(node);
if (!container) {
data = res->data(node, &size);
compressionAlgo = res->compressionAlgo(node);
} else {
data = nullptr;
size = 0;
compressionAlgo = QResource::NoCompression;
}
lastModified = res->lastModified(node);
} else if (res->isContainer(node) != container) {
qWarning("QResourceInfo: Resource [%s] has both data and children!",
file.toLatin1().constData());
}
res->ref.ref();
related.append(res);
} else if (res->mappingRootSubdir(file)) {
container = true;
data = nullptr;
size = 0;
compressionAlgo = QResource::NoCompression;
lastModified = 0;
res->ref.ref();
related.append(res);
}
}
return !related.isEmpty();
}
void QResourcePrivate::ensureInitialized() const
{
if (!related.isEmpty())
return;
QResourcePrivate *that = const_cast<QResourcePrivate *>(this);
if (fileName == ":"_L1)
that->fileName += u'/';
that->absoluteFilePath = fileName;
if (!that->absoluteFilePath.startsWith(u':'))
that->absoluteFilePath.prepend(u':');
QStringView path(fileName);
if (path.startsWith(u':'))
path = path.mid(1);
if (path.startsWith(u'/')) {
that->load(path.toString());
} else {
// Should we search QDir::searchPath() before falling back to root ?
const QString searchPath(u'/' + path);
if (that->load(searchPath))
that->absoluteFilePath = u':' + searchPath;
}
}
void QResourcePrivate::ensureChildren() const
{
ensureInitialized();
if (!children.isEmpty() || !container || related.isEmpty())
return;
QString path = absoluteFilePath, k;
if (path.startsWith(u':'))
path = path.mid(1);
QDuplicateTracker<QString> kids(related.size());
QString cleaned = cleanPath(path);
for (int i = 0; i < related.size(); ++i) {
QResourceRoot *res = related.at(i);
if (res->mappingRootSubdir(path, &k) && !k.isEmpty()) {
if (!kids.hasSeen(k))
children += k;
} else {
const int node = res->findNode(cleaned);
if (node != -1) {
QStringList related_children = res->children(node);
for (int kid = 0; kid < related_children.size(); ++kid) {
k = related_children.at(kid);
if (!kids.hasSeen(k))
children += k;
}
}
}
}
}
qint64 QResourcePrivate::uncompressedSize() const
{
switch (compressionAlgo) {
case QResource::NoCompression:
return size;
case QResource::ZlibCompression:
#ifndef QT_NO_COMPRESS
if (size_t(size) >= sizeof(quint32))
return qFromBigEndian<quint32>(data);
#else
Q_ASSERT(!"QResource: Qt built without support for Zlib compression");
Q_UNREACHABLE();
#endif
break;
case QResource::ZstdCompression: {
#if QT_CONFIG(zstd)
size_t n = ZSTD_getFrameContentSize(data, size);
return ZSTD_isError(n) ? -1 : qint64(n);
#else
// This should not happen because we've refused to load such resource
Q_ASSERT(!"QResource: Qt built without support for Zstd compression");
Q_UNREACHABLE();
#endif
}
}
return -1;
}
qsizetype QResourcePrivate::decompress(char *buffer, qsizetype bufferSize) const
{
Q_ASSERT(data);
#if defined(QT_NO_COMPRESS) && !QT_CONFIG(zstd)
Q_UNUSED(buffer);
Q_UNUSED(bufferSize);
#endif
switch (compressionAlgo) {
case QResource::NoCompression:
Q_UNREACHABLE();
break;
case QResource::ZlibCompression: {
#ifndef QT_NO_COMPRESS
uLong len = uLong(bufferSize);
int res = ::uncompress(reinterpret_cast<Bytef *>(buffer), &len, data + sizeof(quint32),
uLong(size - sizeof(quint32)));
if (res != Z_OK) {
qWarning("QResource: error decompressing zlib content (%d)", res);
return -1;
}
return len;
#else
Q_UNREACHABLE();
#endif
}
case QResource::ZstdCompression: {
#if QT_CONFIG(zstd)
size_t usize = ZSTD_decompress(buffer, bufferSize, data, size);
if (ZSTD_isError(usize)) {
qWarning("QResource: error decompressing zstd content: %s", ZSTD_getErrorName(usize));
return -1;
}
return usize;
#else
Q_UNREACHABLE();
#endif
}
}
return -1;
}
/*!
Constructs a QResource pointing to \a file. \a locale is used to
load a specific localization of a resource data.
\sa QFileInfo, QDir::searchPaths(), setFileName(), setLocale()
*/
QResource::QResource(const QString &file, const QLocale &locale) : d_ptr(new QResourcePrivate(this))
{
Q_D(QResource);
d->fileName = file;
d->locale = locale;
}
/*!
Releases the resources of the QResource object.
*/
QResource::~QResource()
{
}
/*!
Sets a QResource to only load the localization of resource to for \a
locale. If a resource for the specific locale is not found then the
C locale is used.
\sa setFileName()
*/
void QResource::setLocale(const QLocale &locale)
{
Q_D(QResource);
d->clear();
d->locale = locale;
}
/*!
Returns the locale used to locate the data for the QResource.
*/
QLocale QResource::locale() const
{
Q_D(const QResource);
return d->locale;
}
/*!
Sets a QResource to point to \a file. \a file can either be absolute,
in which case it is opened directly, if relative then the file will be
tried to be found in QDir::searchPaths().
\sa absoluteFilePath()
*/
void QResource::setFileName(const QString &file)
{
Q_D(QResource);
d->clear();
d->fileName = file;
}
/*!
Returns the full path to the file that this QResource represents as it
was passed.
\sa absoluteFilePath()
*/
QString QResource::fileName() const
{
Q_D(const QResource);
d->ensureInitialized();
return d->fileName;
}
/*!
Returns the real path that this QResource represents, if the resource
was found via the QDir::searchPaths() it will be indicated in the path.
\sa fileName()
*/
QString QResource::absoluteFilePath() const
{
Q_D(const QResource);
d->ensureInitialized();
return d->absoluteFilePath;
}
/*!
Returns \c true if the resource really exists in the resource hierarchy,
false otherwise.
*/
bool QResource::isValid() const
{
Q_D(const QResource);
d->ensureInitialized();
return !d->related.isEmpty();
}
/*!
\fn bool QResource::isFile() const
Returns \c true if the resource represents a file and thus has data
backing it, false if it represents a directory.
\sa isDir()
*/
/*!
\since 5.13
Returns the compression type that this resource is compressed with, if any.
If it is not compressed, this function returns QResource::NoCompression.
If this function returns QResource::ZlibCompression, you may decompress the
data using the qUncompress() function. Up until Qt 5.13, this was the only
possible compression algorithm.
If this function returns QResource::ZstdCompression, you need to use the
Zstandard library functions (\c{<zstd.h>} header). Qt does not provide a
wrapper.
See \l{http://facebook.github.io/zstd/zstd_manual.html}{Zstandard manual}.
\sa data(), isFile()
*/
QResource::Compression QResource::compressionAlgorithm() const
{
Q_D(const QResource);
d->ensureInitialized();
return Compression(d->compressionAlgo);
}
/*!
Returns the size of the stored data backing the resource.
If the resource is compressed, this function returns the size of the
compressed data. See uncompressedSize() for the uncompressed size.
\sa data(), uncompressedSize(), isFile()
*/
qint64 QResource::size() const
{
Q_D(const QResource);
d->ensureInitialized();
return d->size;
}
/*!
\since 5.15
Returns the size of the data in this resource. If the data was not
compressed, this function returns the same as size(). If it was, then this
function extracts the size of the original uncompressed data from the
stored stream.
\sa size(), uncompressedData(), isFile()
*/
qint64 QResource::uncompressedSize() const
{
Q_D(const QResource);
d->ensureInitialized();
return d->uncompressedSize();
}
/*!
Returns direct access to a segment of read-only data, that this resource
represents. If the resource is compressed, the data returned is also
compressed. The caller must then decompress the data or use
uncompressedData(). If the resource is a directory, \c nullptr is returned.
\sa uncompressedData(), size(), isFile()
*/
const uchar *QResource::data() const
{
Q_D(const QResource);
d->ensureInitialized();
return d->data;
}
/*!
\since 5.15
Returns the resource data, decompressing it first, if the data was stored
compressed. If the resource is a directory or an error occurs while
decompressing, a null QByteArray is returned.
\note If the data was compressed, this function will decompress every time
it is called. The result is not cached between calls.
\sa uncompressedSize(), size(), compressionAlgorithm(), isFile()
*/
QByteArray QResource::uncompressedData() const
{
Q_D(const QResource);
qint64 n = uncompressedSize();
if (n < 0)
return QByteArray();
if (n > std::numeric_limits<QByteArray::size_type>::max()) {
qWarning("QResource: compressed content does not fit into a QByteArray; use QFile instead");
return QByteArray();
}
if (d->compressionAlgo == NoCompression)
return QByteArray::fromRawData(reinterpret_cast<const char *>(d->data), n);
// decompress
QByteArray result(n, Qt::Uninitialized);
n = d->decompress(result.data(), n);
if (n < 0)
result.clear();
else
result.truncate(n);
return result;
}
/*!
\since 5.8
Returns the date and time when the file was last modified before
packaging into a resource.
*/
QDateTime QResource::lastModified() const
{
Q_D(const QResource);
d->ensureInitialized();
return d->lastModified ? QDateTime::fromMSecsSinceEpoch(d->lastModified) : QDateTime();
}
/*!
Returns \c true if the resource represents a directory and thus may have
children() in it, false if it represents a file.
\sa isFile()
*/
bool QResource::isDir() const
{
Q_D(const QResource);
d->ensureInitialized();
return d->container;
}
/*!
Returns a list of all resources in this directory, if the resource
represents a file the list will be empty.
\sa isDir()
*/
QStringList QResource::children() const
{
Q_D(const QResource);
d->ensureChildren();
return d->children;
}
inline uint QResourceRoot::hash(int node) const
{
if (!node) // root
return 0;
const int offset = findOffset(node);
qint32 name_offset = qFromBigEndian<qint32>(tree + offset);
name_offset += 2; // jump past name length
return qFromBigEndian<quint32>(names + name_offset);
}
inline QString QResourceRoot::name(int node) const
{
if (!node) // root
return QString();
const int offset = findOffset(node);
QString ret;
qint32 name_offset = qFromBigEndian<qint32>(tree + offset);
quint16 name_length = qFromBigEndian<qint16>(names + name_offset);
name_offset += 2;
name_offset += 4; // jump past hash
ret.resize(name_length);
QChar *strData = ret.data();
qFromBigEndian<char16_t>(names + name_offset, name_length, strData);
return ret;
}
int QResourceRoot::findNode(const QString &_path, const QLocale &locale) const
{
QString path = _path;
{
QString root = mappingRoot();
if (!root.isEmpty()) {
if (root == path) {
path = u'/';
} else {
if (!root.endsWith(u'/'))
root += u'/';
if (path.size() >= root.size() && path.startsWith(root))
path = path.mid(root.size() - 1);
if (path.isEmpty())
path = u'/';
}
}
}
#ifdef DEBUG_RESOURCE_MATCH
qDebug() << "!!!!" << "START" << path << locale.territory() << locale.language();
#endif
if (path == "/"_L1)
return 0;
// the root node is always first
qint32 child_count = qFromBigEndian<qint32>(tree + 6);
qint32 child = qFromBigEndian<qint32>(tree + 10);
// now iterate up the tree
int node = -1;
QStringSplitter splitter(path);
while (child_count && splitter.hasNext()) {
QStringView segment = splitter.next();
#ifdef DEBUG_RESOURCE_MATCH
qDebug() << " CHILDREN" << segment;
for (int j = 0; j < child_count; ++j) {
qDebug() << " " << child + j << " :: " << name(child + j);
}
#endif
const uint h = qt_hash(segment);
// do the binary search for the hash
int l = 0, r = child_count - 1;
int sub_node = (l + r + 1) / 2;
while (r != l) {
const uint sub_node_hash = hash(child + sub_node);
if (h == sub_node_hash)
break;
else if (h < sub_node_hash)
r = sub_node - 1;
else
l = sub_node;
sub_node = (l + r + 1) / 2;
}
sub_node += child;
// now do the "harder" compares
bool found = false;
if (hash(sub_node) == h) {
while (sub_node > child && hash(sub_node - 1) == h) // backup for collisions
--sub_node;
for (; sub_node < child + child_count && hash(sub_node) == h;
++sub_node) { // here we go...
if (name(sub_node) == segment) {
found = true;
int offset = findOffset(sub_node);
#ifdef DEBUG_RESOURCE_MATCH
qDebug() << " TRY" << sub_node << name(sub_node) << offset;
#endif
offset += 4; // jump past name
const qint16 flags = qFromBigEndian<qint16>(tree + offset);
offset += 2;
if (!splitter.hasNext()) {
if (!(flags & Directory)) {
const qint16 territory = qFromBigEndian<qint16>(tree + offset);
offset += 2;
const qint16 language = qFromBigEndian<qint16>(tree + offset);
offset += 2;
#ifdef DEBUG_RESOURCE_MATCH
qDebug() << " " << "LOCALE" << country << language;
#endif
if (territory == locale.territory() && language == locale.language()) {
#ifdef DEBUG_RESOURCE_MATCH
qDebug() << "!!!!" << "FINISHED" << __LINE__ << sub_node;
#endif
return sub_node;
} else if ((territory == QLocale::AnyTerritory
&& language == locale.language())
|| (territory == QLocale::AnyTerritory
&& language == QLocale::C
&& node == -1)) {
node = sub_node;
}
continue;
} else {
#ifdef DEBUG_RESOURCE_MATCH
qDebug() << "!!!!" << "FINISHED" << __LINE__ << sub_node;
#endif
return sub_node;
}
}
if (!(flags & Directory))
return -1;
child_count = qFromBigEndian<qint32>(tree + offset);
offset += 4;
child = qFromBigEndian<qint32>(tree + offset);
break;
}
}
}
if (!found)
break;
}
#ifdef DEBUG_RESOURCE_MATCH
qDebug() << "!!!!" << "FINISHED" << __LINE__ << node;
#endif
return node;
}
short QResourceRoot::flags(int node) const
{
if (node == -1)
return 0;
const int offset = findOffset(node) + 4; // jump past name
return qFromBigEndian<qint16>(tree + offset);
}
const uchar *QResourceRoot::data(int node, qint64 *size) const
{
if (node == -1) {
*size = 0;
return nullptr;
}
int offset = findOffset(node) + 4; // jump past name
const qint16 flags = qFromBigEndian<qint16>(tree + offset);
offset += 2;
offset += 4; // jump past locale
if (!(flags & Directory)) {
const qint32 data_offset = qFromBigEndian<qint32>(tree + offset);
const quint32 data_length = qFromBigEndian<quint32>(payloads + data_offset);
const uchar *ret = payloads + data_offset + 4;
*size = data_length;
return ret;
}
*size = 0;
return nullptr;
}
qint64 QResourceRoot::lastModified(int node) const
{
if (node == -1 || version < 0x02)
return 0;
const int offset = findOffset(node) + 14;
return qFromBigEndian<qint64>(tree + offset);
}
QStringList QResourceRoot::children(int node) const
{
if (node == -1)
return QStringList();
int offset = findOffset(node) + 4; // jump past name
const qint16 flags = qFromBigEndian<qint16>(tree + offset);
offset += 2;
QStringList ret;
if (flags & Directory) {
const qint32 child_count = qFromBigEndian<qint32>(tree + offset);
offset += 4;
const qint32 child_off = qFromBigEndian<qint32>(tree + offset);
ret.reserve(child_count);
for (int i = child_off; i < child_off + child_count; ++i)
ret << name(i);
}
return ret;
}
bool QResourceRoot::mappingRootSubdir(const QString &path, QString *match) const
{
const QString root = mappingRoot();
if (root.isEmpty())
return false;
QStringSplitter rootIt(root);
QStringSplitter pathIt(path);
while (rootIt.hasNext()) {
if (pathIt.hasNext()) {
if (rootIt.next() != pathIt.next()) // mismatch
return false;
} else {
// end of path, but not of root:
if (match)
*match = rootIt.next().toString();
return true;
}
}
// end of root
return !pathIt.hasNext();
}
Q_CORE_EXPORT bool qRegisterResourceData(int version, const unsigned char *tree,
const unsigned char *name, const unsigned char *data)
{
if (resourceGlobalData.isDestroyed())
return false;
const auto locker = qt_scoped_lock(resourceMutex());
ResourceList *list = resourceList();
if (version >= 0x01 && version <= 0x3) {
bool found = false;
QResourceRoot res(version, tree, name, data);
for (int i = 0; i < list->size(); ++i) {
if (*list->at(i) == res) {
found = true;
break;
}
}
if (!found) {
QResourceRoot *root = new QResourceRoot(version, tree, name, data);
root->ref.ref();
list->append(root);
}
return true;
}
return false;
}
Q_CORE_EXPORT bool qUnregisterResourceData(int version, const unsigned char *tree,
const unsigned char *name, const unsigned char *data)
{
if (resourceGlobalData.isDestroyed())
return false;
const auto locker = qt_scoped_lock(resourceMutex());
if (version >= 0x01 && version <= 0x3) {
QResourceRoot res(version, tree, name, data);
ResourceList *list = resourceList();
for (int i = 0; i < list->size();) {
if (*list->at(i) == res) {
QResourceRoot *root = list->takeAt(i);
if (!root->ref.deref())
delete root;
} else {
++i;
}
}
return true;
}
return false;
}
namespace {
// run time resource creation
class QDynamicBufferResourceRoot : public QResourceRoot
{
QString root;
const uchar *buffer;
public:
inline QDynamicBufferResourceRoot(const QString &_root) : root(_root), buffer(nullptr) { }
inline ~QDynamicBufferResourceRoot() { }
inline const uchar *mappingBuffer() const { return buffer; }
QString mappingRoot() const override { return root; }
ResourceRootType type() const override { return Resource_Buffer; }
// size == -1 means "unknown"
bool registerSelf(const uchar *b, qsizetype size)
{
// 5 int "pointers"
if (size >= 0 && size < 20)
return false;
// setup the data now
int offset = 0;
// magic number
if (b[offset + 0] != 'q' || b[offset + 1] != 'r' || b[offset + 2] != 'e'
|| b[offset + 3] != 's') {
return false;
}
offset += 4;
const int version = qFromBigEndian<qint32>(b + offset);
offset += 4;
const int tree_offset = qFromBigEndian<qint32>(b + offset);
offset += 4;
const int data_offset = qFromBigEndian<qint32>(b + offset);
offset += 4;
const int name_offset = qFromBigEndian<qint32>(b + offset);
offset += 4;
quint32 file_flags = 0;
if (version >= 3) {
file_flags = qFromBigEndian<qint32>(b + offset);
offset += 4;
}
// Some sanity checking for sizes. This is _not_ a security measure.
if (size >= 0 && (tree_offset >= size || data_offset >= size || name_offset >= size))
return false;
// And some sanity checking for features
quint32 acceptableFlags = 0;
#ifndef QT_NO_COMPRESS
acceptableFlags |= Compressed;
#endif
if (QT_CONFIG(zstd))
acceptableFlags |= CompressedZstd;
if (file_flags & ~acceptableFlags)
return false;
if (version >= 0x01 && version <= 0x03) {
buffer = b;
setSource(version, b + tree_offset, b + name_offset, b + data_offset);
return true;
}
return false;
}
};
class QDynamicFileResourceRoot : public QDynamicBufferResourceRoot
{
public:
static uchar *map_sys(QFile &file, qint64 base, qsizetype size);
static void unmap_sys(void *base, qsizetype size);
private:
QString fileName;
// for mmap'ed files, this is what needs to be unmapped.
uchar *unmapPointer;
qsizetype unmapLength;
public:
QDynamicFileResourceRoot(const QString &_root)
: QDynamicBufferResourceRoot(_root), unmapPointer(nullptr), unmapLength(0)
{ }
~QDynamicFileResourceRoot() {
if (wasMemoryMapped())
unmap_sys(unmapPointer, unmapLength);
else
delete[] mappingBuffer();
}
QString mappingFile() const { return fileName; }
ResourceRootType type() const override { return Resource_File; }
bool wasMemoryMapped() const { return unmapPointer; }
bool registerSelf(const QString &f);
};
} // unnamed namespace
#ifndef MAP_FILE
# define MAP_FILE 0
#endif
#ifndef MAP_FAILED
# define MAP_FAILED reinterpret_cast<void *>(-1)
#endif
void QDynamicFileResourceRoot::unmap_sys(void *base, qsizetype size)
{
#if defined(QT_USE_MMAP)
munmap(base, size);
#elif defined(Q_OS_WIN)
Q_UNUSED(size)
UnmapViewOfFile(reinterpret_cast<void *>(base));
#endif
}
// Note: caller must ensure \a offset and \a size are acceptable to the OS.
uchar *QDynamicFileResourceRoot::map_sys(QFile &file, qint64 offset, qsizetype size)
{
Q_ASSERT(file.isOpen());
void *ptr = nullptr;
if (size < 0)
size = qMin(file.size() - offset, (std::numeric_limits<qsizetype>::max)());
// We don't use QFile::map() here because we want to dispose of the QFile object
#if defined(QT_USE_MMAP)
int fd = file.handle();
int protection = PROT_READ; // read-only memory
int flags = MAP_FILE | MAP_PRIVATE; // swap-backed map from file
ptr = QT_MMAP(nullptr, size, protection, flags, fd, offset);
if (ptr == MAP_FAILED)
ptr = nullptr;
#elif defined(Q_OS_WIN)
int fd = file.handle();
HANDLE fileHandle = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
if (fileHandle != INVALID_HANDLE_VALUE) {
HANDLE mapHandle = CreateFileMapping(fileHandle, 0, PAGE_WRITECOPY, 0, 0, 0);
if (mapHandle) {
ptr = MapViewOfFile(mapHandle, FILE_MAP_COPY, DWORD(offset >> 32), DWORD(offset), size);
CloseHandle(mapHandle);
}
}
#endif // QT_USE_MMAP
return static_cast<uchar *>(ptr);
}
bool QDynamicFileResourceRoot::registerSelf(const QString &f)
{
QFile file(f);
if (!file.open(QIODevice::ReadOnly))
return false;
qint64 data_len = file.size();
if (data_len > std::numeric_limits<qsizetype>::max())
return false;
uchar *data = map_sys(file, 0, data_len);
bool fromMM = !!data;
if (!fromMM) {
bool ok = false;
data = new uchar[data_len];
ok = (data_len == file.read(reinterpret_cast<char *>(data), data_len));
if (!ok) {
delete[] data;
data = nullptr;
data_len = 0;
return false;
}
}
if (data && QDynamicBufferResourceRoot::registerSelf(data, data_len)) {
if (fromMM) {
unmapPointer = data;
unmapLength = data_len;
}
fileName = f;
return true;
}
return false;
}
static QString qt_resource_fixResourceRoot(QString r)
{
if (!r.isEmpty()) {
if (r.startsWith(u':'))
r = r.mid(1);
if (!r.isEmpty())
r = QDir::cleanPath(r);
}
return r;
}
/*!
\fn bool QResource::registerResource(const QString &rccFileName, const QString &mapRoot)
Registers the resource with the given \a rccFileName at the location in the
resource tree specified by \a mapRoot, and returns \c true if the file is
successfully opened; otherwise returns \c false.
\sa unregisterResource()
*/
bool QResource::registerResource(const QString &rccFilename, const QString &resourceRoot)
{
QString r = qt_resource_fixResourceRoot(resourceRoot);
if (!r.isEmpty() && r[0] != u'/') {
qWarning("QDir::registerResource: Registering a resource [%ls] must be rooted in an "
"absolute path (start with /) [%ls]",
qUtf16Printable(rccFilename), qUtf16Printable(resourceRoot));
return false;
}
QDynamicFileResourceRoot *root = new QDynamicFileResourceRoot(r);
if (root->registerSelf(rccFilename)) {
root->ref.ref();
const auto locker = qt_scoped_lock(resourceMutex());
resourceList()->append(root);
return true;
}
delete root;
return false;
}
/*!
\fn bool QResource::unregisterResource(const QString &rccFileName, const QString &mapRoot)
Unregisters the resource with the given \a rccFileName at the location in
the resource tree specified by \a mapRoot, and returns \c true if the
resource is successfully unloaded and no references exist for the
resource; otherwise returns \c false.
\sa registerResource()
*/
bool QResource::unregisterResource(const QString &rccFilename, const QString &resourceRoot)
{
QString r = qt_resource_fixResourceRoot(resourceRoot);
const auto locker = qt_scoped_lock(resourceMutex());
ResourceList *list = resourceList();
for (int i = 0; i < list->size(); ++i) {
QResourceRoot *res = list->at(i);
if (res->type() == QResourceRoot::Resource_File) {
QDynamicFileResourceRoot *root = reinterpret_cast<QDynamicFileResourceRoot *>(res);
if (root->mappingFile() == rccFilename && root->mappingRoot() == r) {
list->removeAt(i);
if (!root->ref.deref()) {
delete root;
return true;
}
return false;
}
}
}
return false;
}
/*!
\fn bool QResource::registerResource(const uchar *rccData, const QString &mapRoot)
\since 4.3
Registers the resource with the given \a rccData at the location in the
resource tree specified by \a mapRoot, and returns \c true if the file is
successfully opened; otherwise returns \c false.
\warning The data must remain valid throughout the life of any QFile
that may reference the resource data.
\sa unregisterResource()
*/
bool QResource::registerResource(const uchar *rccData, const QString &resourceRoot)
{
QString r = qt_resource_fixResourceRoot(resourceRoot);
if (!r.isEmpty() && r[0] != u'/') {
qWarning("QDir::registerResource: Registering a resource [%p] must be rooted in an "
"absolute path (start with /) [%ls]",
rccData, qUtf16Printable(resourceRoot));
return false;
}
QDynamicBufferResourceRoot *root = new QDynamicBufferResourceRoot(r);
if (root->registerSelf(rccData, -1)) {
root->ref.ref();
const auto locker = qt_scoped_lock(resourceMutex());
resourceList()->append(root);
return true;
}
delete root;
return false;
}
/*!
\fn bool QResource::unregisterResource(const uchar *rccData, const QString &mapRoot)
\since 4.3
Unregisters the resource with the given \a rccData at the location in the
resource tree specified by \a mapRoot, and returns \c true if the resource is
successfully unloaded and no references exist into the resource; otherwise returns \c false.
\sa registerResource()
*/
bool QResource::unregisterResource(const uchar *rccData, const QString &resourceRoot)
{
QString r = qt_resource_fixResourceRoot(resourceRoot);
const auto locker = qt_scoped_lock(resourceMutex());
ResourceList *list = resourceList();
for (int i = 0; i < list->size(); ++i) {
QResourceRoot *res = list->at(i);
if (res->type() == QResourceRoot::Resource_Buffer) {
QDynamicBufferResourceRoot *root = reinterpret_cast<QDynamicBufferResourceRoot *>(res);
if (root->mappingBuffer() == rccData && root->mappingRoot() == r) {
list->removeAt(i);
if (!root->ref.deref()) {
delete root;
return true;
}
return false;
}
}
}
return false;
}
#if !defined(QT_BOOTSTRAPPED)
// resource engine
class QResourceFileEnginePrivate : public QAbstractFileEnginePrivate
{
protected:
Q_DECLARE_PUBLIC(QResourceFileEngine)
private:
uchar *map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags);
bool unmap(uchar *ptr);
void uncompress() const;
void mapUncompressed();
bool mapUncompressed_sys();
void unmapUncompressed_sys();
qint64 offset = 0;
QResource resource;
mutable QByteArray uncompressed;
bool mustUnmap = false;
// minimum size for which we'll try to re-open ourselves in mapUncompressed()
static constexpr qsizetype RemapCompressedThreshold = 16384;
protected:
~QResourceFileEnginePrivate()
{
if (mustUnmap)
unmapUncompressed_sys();
}
};
bool QResourceFileEngine::caseSensitive() const
{
return true;
}
QResourceFileEngine::QResourceFileEngine(const QString &file) :
QAbstractFileEngine(*new QResourceFileEnginePrivate)
{
Q_D(QResourceFileEngine);
d->resource.setFileName(file);
}
QResourceFileEngine::~QResourceFileEngine()
{
}
void QResourceFileEngine::setFileName(const QString &file)
{
Q_D(QResourceFileEngine);
d->resource.setFileName(file);
}
bool QResourceFileEngine::open(QIODevice::OpenMode flags,
std::optional<QFile::Permissions> permissions)
{
Q_UNUSED(permissions);
Q_D(QResourceFileEngine);
if (d->resource.fileName().isEmpty()) {
qWarning("QResourceFileEngine::open: Missing file name");
return false;
}
if (flags & QIODevice::WriteOnly)
return false;
if (d->resource.compressionAlgorithm() != QResource::NoCompression) {
d->uncompress();
if (d->uncompressed.isNull()) {
d->errorString = QSystemError::stdString(EIO);
return false;
}
}
if (!d->resource.isValid()) {
d->errorString = QSystemError::stdString(ENOENT);
return false;
}
return true;
}
bool QResourceFileEngine::close()
{
Q_D(QResourceFileEngine);
d->offset = 0;
return true;
}
bool QResourceFileEngine::flush()
{
return true;
}
qint64 QResourceFileEngine::read(char *data, qint64 len)
{
Q_D(QResourceFileEngine);
if (len > size() - d->offset)
len = size() - d->offset;
if (len <= 0)
return 0;
if (!d->uncompressed.isNull())
memcpy(data, d->uncompressed.constData() + d->offset, len);
else
memcpy(data, d->resource.data() + d->offset, len);
d->offset += len;
return len;
}
qint64 QResourceFileEngine::size() const
{
Q_D(const QResourceFileEngine);
return d->resource.isValid() ? d->resource.uncompressedSize() : 0;
}
qint64 QResourceFileEngine::pos() const
{
Q_D(const QResourceFileEngine);
return d->offset;
}
bool QResourceFileEngine::atEnd() const
{
Q_D(const QResourceFileEngine);
if (!d->resource.isValid())
return true;
return d->offset == size();
}
bool QResourceFileEngine::seek(qint64 pos)
{
Q_D(QResourceFileEngine);
if (!d->resource.isValid())
return false;
if (d->offset > size())
return false;
d->offset = pos;
return true;
}
QAbstractFileEngine::FileFlags QResourceFileEngine::fileFlags(QAbstractFileEngine::FileFlags type) const
{
Q_D(const QResourceFileEngine);
QAbstractFileEngine::FileFlags ret;
if (!d->resource.isValid())
return ret;
if (type & PermsMask)
ret |= QAbstractFileEngine::FileFlags(ReadOwnerPerm | ReadUserPerm | ReadGroupPerm
| ReadOtherPerm);
if (type & TypesMask) {
if (d->resource.isDir())
ret |= DirectoryType;
else
ret |= FileType;
}
if (type & FlagsMask) {
ret |= ExistsFlag;
if (d->resource.absoluteFilePath() == ":/"_L1)
ret |= RootFlag;
}
return ret;
}
QString QResourceFileEngine::fileName(FileName file) const
{
Q_D(const QResourceFileEngine);
if (file == BaseName) {
const qsizetype slash = d->resource.fileName().lastIndexOf(u'/');
if (slash == -1)
return d->resource.fileName();
return d->resource.fileName().mid(slash + 1);
} else if (file == PathName || file == AbsolutePathName) {
const QString path = (file == AbsolutePathName) ? d->resource.absoluteFilePath()
: d->resource.fileName();
const qsizetype slash = path.lastIndexOf(u'/');
if (slash == -1)
return ":"_L1;
else if (slash <= 1)
return ":/"_L1;
return path.left(slash);
} else if (file == CanonicalName || file == CanonicalPathName) {
const QString absoluteFilePath = d->resource.absoluteFilePath();
if (file == CanonicalPathName) {
const qsizetype slash = absoluteFilePath.lastIndexOf(u'/');
if (slash != -1)
return absoluteFilePath.left(slash);
}
return absoluteFilePath;
}
return d->resource.fileName();
}
uint QResourceFileEngine::ownerId(FileOwner) const
{
static const uint nobodyID = static_cast<uint>(-2);
return nobodyID;
}
QDateTime QResourceFileEngine::fileTime(QFile::FileTime time) const
{
Q_D(const QResourceFileEngine);
if (time == QFile::FileModificationTime)
return d->resource.lastModified();
return QDateTime();
}
/*!
\internal
*/
QAbstractFileEngine::IteratorUniquePtr
QResourceFileEngine::beginEntryList(const QString &path, QDir::Filters filters,
const QStringList &filterNames)
{
return std::make_unique<QResourceFileEngineIterator>(path, filters, filterNames);
}
QAbstractFileEngine::IteratorUniquePtr
QResourceFileEngine::beginEntryList(const QString &path, QDirListing::IteratorFlags filters,
const QStringList &filterNames)
{
return std::make_unique<QResourceFileEngineIterator>(path, filters, filterNames);
}
bool QResourceFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output)
{
Q_D(QResourceFileEngine);
if (extension == MapExtension) {
const auto *options = static_cast<const MapExtensionOption *>(option);
auto *returnValue = static_cast<MapExtensionReturn *>(output);
returnValue->address = d->map(options->offset, options->size, options->flags);
return (returnValue->address != nullptr);
}
if (extension == UnMapExtension) {
const auto *options = static_cast<const UnMapExtensionOption *>(option);
return d->unmap(options->address);
}
return false;
}
bool QResourceFileEngine::supportsExtension(Extension extension) const
{
return (extension == UnMapExtension || extension == MapExtension);
}
uchar *QResourceFileEnginePrivate::map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags)
{
Q_Q(QResourceFileEngine);
Q_ASSERT_X(resource.compressionAlgorithm() == QResource::NoCompression
|| !uncompressed.isNull(), "QFile::map()",
"open() should have uncompressed compressed resources");
qint64 max = resource.uncompressedSize();
qint64 end;
if (offset < 0 || size <= 0 || !resource.isValid() ||
qAddOverflow(offset, size, &end) || end > max) {
q->setError(QFile::UnspecifiedError, QString());
return nullptr;
}
const uchar *address = reinterpret_cast<const uchar *>(uncompressed.constBegin());
if (!uncompressed.isNull())
return const_cast<uchar *>(address) + offset;
// resource was not compressed
address = resource.data();
if (flags & QFile::MapPrivateOption) {
// We need to provide read-write memory
mapUncompressed();
address = reinterpret_cast<const uchar *>(uncompressed.constData());
}
return const_cast<uchar *>(address) + offset;
}
bool QResourceFileEnginePrivate::unmap(uchar *ptr)
{
Q_UNUSED(ptr);
return true;
}
void QResourceFileEnginePrivate::uncompress() const
{
if (resource.compressionAlgorithm() == QResource::NoCompression
|| !uncompressed.isEmpty() || resource.size() == 0)
return; // nothing to do
uncompressed = resource.uncompressedData();
}
void QResourceFileEnginePrivate::mapUncompressed()
{
Q_ASSERT(resource.compressionAlgorithm() == QResource::NoCompression);
if (!uncompressed.isNull())
return; // nothing to do
if (resource.uncompressedSize() >= RemapCompressedThreshold) {
if (mapUncompressed_sys())
return;
}
uncompressed = resource.uncompressedData();
uncompressed.detach();
}
#if defined(MREMAP_MAYMOVE) && defined(MREMAP_DONTUNMAP)
inline bool QResourcePrivate::mayRemapData(const QResource &resource)
{
auto d = resource.d_func();
// assumptions from load():
// - d->related is not empty
// - the first item in d->related is the one with our data
// by current construction, it's also the only item
const QResourceRoot *root = d->related.at(0);
switch (root->type()) {
case QResourceRoot::Resource_Builtin:
return true; // always acceptable, memory is read-only
case QResourceRoot::Resource_Buffer:
return false; // never acceptable, memory is heap
case QResourceRoot::Resource_File:
break;
}
auto df = static_cast<const QDynamicFileResourceRoot *>(root);
return df->wasMemoryMapped();
}
#endif
// Returns the page boundaries of where \a location is located in memory.
static auto mappingBoundaries(const void *location, qsizetype size)
{
#ifdef Q_OS_WIN
auto getpagesize = [] {
SYSTEM_INFO sysinfo;
::GetSystemInfo(&sysinfo);
return sysinfo.dwAllocationGranularity;
};
#endif
struct R {
void *begin;
qsizetype size;
qptrdiff offset;
} r;
const quintptr pageMask = getpagesize() - 1;
quintptr data = quintptr(location);
quintptr begin = data & ~pageMask;
quintptr end = (data + size + pageMask) & ~pageMask;
r.begin = reinterpret_cast<void *>(begin);
r.size = end - begin;
r.offset = data & pageMask;
return r;
}
bool QResourceFileEnginePrivate::mapUncompressed_sys()
{
auto r = mappingBoundaries(resource.data(), resource.uncompressedSize());
void *ptr = nullptr;
#if defined(MREMAP_MAYMOVE) && defined(MREMAP_DONTUNMAP)
// Use MREMAP_MAYMOVE to tell the kernel to give us a new address and use
// MREMAP_DONTUNMAP (supported since kernel 5.7) to request that it create
// a new mapping of the same pages, instead of moving. We can only do that
// for pages that are read-only, otherwise the kernel replaces the source
// with pages full of nulls.
if (!QResourcePrivate::mayRemapData(resource))
return false;
ptr = mremap(r.begin, r.size, r.size, MREMAP_MAYMOVE | MREMAP_DONTUNMAP);
if (ptr == MAP_FAILED)
return false;
// Allow writing, which the documentation says we allow. This is safe
// because MREMAP_DONTUNMAP only works for private mappings.
if (mprotect(ptr, r.size, PROT_READ | PROT_WRITE) != 0) {
munmap(ptr, r.size);
return false;
}
#elif defined(Q_OS_DARWIN)
mach_port_t self = mach_task_self();
vm_address_t addr = 0;
vm_address_t mask = 0;
bool anywhere = true;
bool copy = true;
vm_prot_t cur_prot = VM_PROT_READ | VM_PROT_WRITE;
vm_prot_t max_prot = VM_PROT_ALL;
kern_return_t res = vm_remap(self, &addr, r.size, mask, anywhere,
self, vm_address_t(r.begin), copy, &cur_prot,
&max_prot, VM_INHERIT_DEFAULT);
if (res != KERN_SUCCESS)
return false;
ptr = reinterpret_cast<void *>(addr);
if ((max_prot & VM_PROT_WRITE) == 0 || mprotect(ptr, r.size, PROT_READ | PROT_WRITE) != 0) {
munmap(ptr, r.size);
return false;
}
#endif
if (!ptr)
return false;
const char *newdata = static_cast<char *>(ptr) + r.offset;
uncompressed = QByteArray::fromRawData(newdata, resource.uncompressedSize());
mustUnmap = true;
return true;
}
void QResourceFileEnginePrivate::unmapUncompressed_sys()
{
auto r = mappingBoundaries(uncompressed.constBegin(), uncompressed.size());
QDynamicFileResourceRoot::unmap_sys(r.begin, r.size);
}
#endif // !defined(QT_BOOTSTRAPPED)
QT_END_NAMESPACE