|
- /**
- * @file runtime_project_bundle.cpp
- * @brief 实现用户运行程序工程数据封装服务
- * @version 1.0.0
- * @author suyu
- * @date 2026-08-26
- */
-
- #include "runtime_project_bundle.h"
-
- #include <QCryptographicHash>
- #include <QFile>
- #include <QFileInfo>
- #include <QObject>
-
- #include <limits>
-
- namespace {
-
- constexpr int kMagicSize = 16;
- constexpr int kFooterSize = kMagicSize + 4 + 8 + 32;
- constexpr quint32 kFormatVersion = 1;
- constexpr qint64 kCopyBufferSize = 64 * 1024;
- const QByteArray kMagic = QByteArrayLiteral("QTPROX_RUNTIME_B");
-
- static_assert(sizeof("QTPROX_RUNTIME_B") - 1 == kMagicSize,
- "runtime bundle magic must have a fixed length");
-
- void appendUint32LittleEndian(QByteArray *bytes, quint32 value)
- {
- for (int shift = 0; shift < 32; shift += 8)
- {
- bytes->append(static_cast<char>((value >> shift) & 0xff));
- }
- }
-
- void appendUint64LittleEndian(QByteArray *bytes, quint64 value)
- {
- for (int shift = 0; shift < 64; shift += 8)
- {
- bytes->append(static_cast<char>((value >> shift) & 0xff));
- }
- }
-
- quint32 readUint32LittleEndian(const char *bytes)
- {
- quint32 value = 0;
- for (int shift = 0; shift < 32; shift += 8)
- {
- value |= static_cast<quint32>(
- static_cast<unsigned char>(bytes[shift / 8]))
- << shift;
- }
- return value;
- }
-
- quint64 readUint64LittleEndian(const char *bytes)
- {
- quint64 value = 0;
- for (int shift = 0; shift < 64; shift += 8)
- {
- value |= static_cast<quint64>(
- static_cast<unsigned char>(bytes[shift / 8]))
- << shift;
- }
- return value;
- }
-
- QByteArray makeFooter(quint64 project_size, const QByteArray &hash)
- {
- QByteArray footer;
- footer.reserve(kFooterSize);
- footer.append(kMagic);
- appendUint32LittleEndian(&footer, kFormatVersion);
- appendUint64LittleEndian(&footer, project_size);
- footer.append(hash);
- return footer;
- }
-
- RuntimeProjectBundleLoadResult invalidBundle(const QString &message)
- {
- return {RuntimeProjectBundleStatus::Invalid, {}, message};
- }
-
- } // namespace
-
- RuntimeProjectBundleWriteResult RuntimeProjectBundleService::write(
- const QString &template_executable,
- const QString &project_file,
- const QString &destination_executable)
- {
- if (!QFileInfo::exists(template_executable))
- {
- return {false,
- QObject::tr("未找到用户运行程序模板:%1")
- .arg(template_executable)};
- }
- if (!QFileInfo::exists(project_file))
- {
- return {false,
- QObject::tr("未找到待封装的工程文件:%1").arg(project_file)};
- }
- if (QFileInfo::exists(destination_executable))
- {
- return {false,
- QObject::tr("导出 exe 已经存在:%1")
- .arg(destination_executable)};
- }
-
- const QString temporary_executable = destination_executable
- + QStringLiteral(".runtime-bundle-tmp");
- QFile::remove(temporary_executable);
- if (!QFile::copy(template_executable, temporary_executable))
- {
- return {false,
- QObject::tr("复制用户运行程序模板失败:%1")
- .arg(template_executable)};
- }
-
- QFile output(temporary_executable);
- QFile project(project_file);
- if (!output.open(QIODevice::ReadWrite | QIODevice::Append)
- || !project.open(QIODevice::ReadOnly))
- {
- output.close();
- project.close();
- QFile::remove(temporary_executable);
- return {false, QObject::tr("无法打开用户运行程序封装文件")};
- }
-
- QCryptographicHash hash(QCryptographicHash::Sha256);
- quint64 project_size = 0;
- bool succeeded = true;
- while (!project.atEnd())
- {
- const QByteArray chunk = project.read(kCopyBufferSize);
- if (chunk.isEmpty() && project.error() != QFileDevice::NoError)
- {
- succeeded = false;
- break;
- }
- if (chunk.isEmpty())
- {
- break;
- }
- if (output.write(chunk) != chunk.size())
- {
- succeeded = false;
- break;
- }
- hash.addData(chunk);
- project_size += static_cast<quint64>(chunk.size());
- }
-
- if (succeeded)
- {
- const QByteArray footer = makeFooter(project_size, hash.result());
- succeeded = output.write(footer) == footer.size();
- }
- output.flush();
- output.close();
- project.close();
- if (!succeeded || !QFile::rename(temporary_executable, destination_executable))
- {
- QFile::remove(temporary_executable);
- return {false, QObject::tr("写入用户运行程序工程数据失败")};
- }
- return {true, {}};
- }
-
- RuntimeProjectBundleLoadResult RuntimeProjectBundleService::load(
- const QString &executable_path)
- {
- QFile executable(executable_path);
- if (!executable.open(QIODevice::ReadOnly))
- {
- return invalidBundle(
- QObject::tr("无法读取用户运行程序:%1").arg(executable_path));
- }
-
- const qint64 executable_size = executable.size();
- if (executable_size < kFooterSize)
- {
- return {RuntimeProjectBundleStatus::NotFound, {}, {}};
- }
- if (!executable.seek(executable_size - kFooterSize))
- {
- return invalidBundle(QObject::tr("无法定位用户运行程序工程数据"));
- }
- const QByteArray footer = executable.read(kFooterSize);
- if (footer.size() != kFooterSize)
- {
- return invalidBundle(QObject::tr("用户运行程序工程数据不完整"));
- }
- if (footer.left(kMagicSize) != kMagic)
- {
- return {RuntimeProjectBundleStatus::NotFound, {}, {}};
- }
-
- const quint32 version = readUint32LittleEndian(footer.constData() + kMagicSize);
- const quint64 project_size = readUint64LittleEndian(
- footer.constData() + kMagicSize + 4);
- if (version != kFormatVersion)
- {
- return invalidBundle(QObject::tr("不支持的用户运行程序工程数据版本"));
- }
- if (project_size > static_cast<quint64>(
- executable_size - static_cast<qint64>(kFooterSize))
- || project_size > static_cast<quint64>(std::numeric_limits<qint64>::max()))
- {
- return invalidBundle(QObject::tr("用户运行程序工程数据长度无效"));
- }
-
- const qint64 project_offset = executable_size - kFooterSize
- - static_cast<qint64>(project_size);
- if (!executable.seek(project_offset))
- {
- return invalidBundle(QObject::tr("无法读取用户运行程序工程数据"));
- }
- const QByteArray project_data = executable.read(
- static_cast<qint64>(project_size));
- if (project_data.size() != static_cast<qint64>(project_size))
- {
- return invalidBundle(QObject::tr("用户运行程序工程数据不完整"));
- }
-
- const QByteArray expected_hash = footer.right(32);
- if (QCryptographicHash::hash(project_data, QCryptographicHash::Sha256)
- != expected_hash)
- {
- return invalidBundle(QObject::tr("用户运行程序工程数据校验失败"));
- }
- return {RuntimeProjectBundleStatus::Loaded, project_data, {}};
- }
|