/** * @file runtime_project_bundle.cpp * @brief 实现用户运行程序工程数据封装服务 * @version 1.0.0 * @author suyu * @date 2026-08-26 */ #include "runtime_project_bundle.h" #include #include #include #include #include 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((value >> shift) & 0xff)); } } void appendUint64LittleEndian(QByteArray *bytes, quint64 value) { for (int shift = 0; shift < 64; shift += 8) { bytes->append(static_cast((value >> shift) & 0xff)); } } quint32 readUint32LittleEndian(const char *bytes) { quint32 value = 0; for (int shift = 0; shift < 32; shift += 8) { value |= static_cast( static_cast(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( static_cast(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(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( executable_size - static_cast(kFooterSize)) || project_size > static_cast(std::numeric_limits::max())) { return invalidBundle(QObject::tr("用户运行程序工程数据长度无效")); } const qint64 project_offset = executable_size - kFooterSize - static_cast(project_size); if (!executable.seek(project_offset)) { return invalidBundle(QObject::tr("无法读取用户运行程序工程数据")); } const QByteArray project_data = executable.read( static_cast(project_size)); if (project_data.size() != static_cast(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, {}}; }