feat(hardening): 内嵌产品配置 + 系统级状态 + 全量 i18n + 依赖治理 + 资料清理
update-client-hardening 四工作流合入(构建/静态检查已过): - 配置:不可变产品策略经 UpdateClientResources.cmake 归一化校验后 内嵌 qrc(URL/相对路径/主程序必须落 install_root/版本/UUID 格式, CMake override 写入前复验);可变机器状态迁系统级原生存储; 运行目录不再落可编辑 app_config.json。 - i18n:生产源全英文(no-Han 检查 41/41),中文进 translations/ zh_CN 目录;翻译加载收敛 Common/TranslationHelper 唯一入口; Bootstrap 补资源文件。 - 依赖:Qt/OpenSSL/架构/Perl 机器路径改 imported targets + UpdateClientDependencies.cmake,3rdparty 由父工程契约供给。 - 清理:旧 README/SDK README/3 个重复 PowerShell 打包脚本/PDF 提取 文本删除,canonical README + 5 份结构化英文文档替代;Launcher requireAdministrator manifest;统一 MSVC /utf-8 删运行时编码设置。
This commit is contained in:
+33
-45
@@ -1,46 +1,34 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(LauncherDemo LANGUAGES C CXX)
|
||||
# C++标准
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
# Qt 自动处理ui/moc/rcc
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
# 依赖Qt模块:网络、基础核心、窗口
|
||||
set(CMAKE_PREFIX_PATH "C:\\Qt\\5.15.2\\msvc2019_64" ${CMAKE_PREFIX_PATH})
|
||||
find_package(Qt5 REQUIRED COMPONENTS Core Network Gui Widgets)
|
||||
# 所有源码文件
|
||||
set(SRC_LIST
|
||||
main.cpp
|
||||
UpdateLogic.h
|
||||
UpdateLogic.cpp
|
||||
)
|
||||
# 生成可执行程序
|
||||
add_executable(Launcher ${SRC_LIST})
|
||||
if(WIN32)
|
||||
set_target_properties(Launcher PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||
endif()
|
||||
|
||||
# 关键:添加OpenSSL头文件目录
|
||||
target_include_directories(Launcher PRIVATE ${OPENSSL_INC})
|
||||
|
||||
# 链接Common,自动带上OpenSSL库
|
||||
target_link_libraries(Launcher Common Qt5::Core Qt5::Network Qt5::Gui Qt5::Widgets)
|
||||
# 输出编译产物到 out 文件夹(干净不乱)
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/out/lib)
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/out/bin)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/out/bin)
|
||||
# 强制VS打开CMake文件夹时默认选中该可执行目标
|
||||
set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT Launcher)
|
||||
# 编译完成自动执行windeployqt,复制Qt dll到exe目录
|
||||
if(WIN32)
|
||||
get_target_property(QT_WINDEPLOYQT_EXE Qt5::qmake IMPORTED_LOCATION)
|
||||
get_filename_component(QT_BIN_PATH "${QT_WINDEPLOYQT_EXE}" DIRECTORY)
|
||||
set(WINDEPLOYQT "${QT_BIN_PATH}/windeployqt.exe")
|
||||
add_custom_command(TARGET Launcher POST_BUILD
|
||||
COMMAND ${WINDEPLOYQT} $<IF:$<CONFIG:Debug>,--debug,--release> $<TARGET_FILE:Launcher>
|
||||
COMMENT "自动部署Qt依赖dll"
|
||||
)
|
||||
set(_launcher_sources
|
||||
main.cpp
|
||||
UpdateLogic.h
|
||||
UpdateLogic.cpp
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
set(_launcher_manifest "${CMAKE_CURRENT_SOURCE_DIR}/launcher.manifest")
|
||||
if(NOT EXISTS "${_launcher_manifest}")
|
||||
message(FATAL_ERROR "Launcher UAC manifest is missing: ${_launcher_manifest}")
|
||||
endif()
|
||||
list(APPEND _launcher_sources "${_launcher_manifest}")
|
||||
endif()
|
||||
|
||||
add_executable(Launcher ${_launcher_sources})
|
||||
target_compile_features(Launcher PRIVATE cxx_std_17)
|
||||
target_link_libraries(Launcher
|
||||
PRIVATE
|
||||
Common
|
||||
Qt5::Core
|
||||
Qt5::Network
|
||||
Qt5::Gui
|
||||
Qt5::Widgets
|
||||
)
|
||||
update_client_link_embedded_resources(Launcher)
|
||||
update_client_deploy_openssl_runtime(Launcher)
|
||||
update_client_enable_qt_deployment(Launcher)
|
||||
|
||||
if(WIN32)
|
||||
set_target_properties(Launcher PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||
if(MSVC)
|
||||
target_link_options(Launcher PRIVATE /MANIFESTUAC:NO)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
||||
+224
-88
@@ -1,4 +1,3 @@
|
||||
#include <windows.h>
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QMessageBox>
|
||||
@@ -6,26 +5,28 @@
|
||||
#include <QProgressDialog>
|
||||
#include <QInputDialog>
|
||||
#include <QLineEdit>
|
||||
#include <QTextCodec>
|
||||
#include "UpdateLogic.h"
|
||||
#include "../Common/ConfigHelper.h"
|
||||
#include "../Common/PolicyHelper.h"
|
||||
#include "../Common/LocalStateHelper.h"
|
||||
#include "../Common/TicketHelper.h"
|
||||
#include "../Common/DeviceIdentityHelper.h"
|
||||
#include "../Common/IntegrityHelper.h"
|
||||
#include "../Common/TranslationHelper.h"
|
||||
#include <QFile>
|
||||
#include <QFileDialog>
|
||||
#include <QDir>
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
SetConsoleOutputCP(65001);
|
||||
QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
|
||||
QApplication app(argc, argv);
|
||||
QApplication::setApplicationName("Marsco Launcher");
|
||||
|
||||
QProgressDialog progress("正在检查软件更新...", QString(), 0, 0);
|
||||
progress.setWindowTitle("Marsco 软件启动器");
|
||||
TranslationHelper translation;
|
||||
translation.installForSystemLocale();
|
||||
|
||||
QProgressDialog progress(QCoreApplication::translate("Launcher", "Checking for software updates..."), QString(), 0, 0);
|
||||
progress.setWindowTitle(QCoreApplication::translate("Launcher", "Marsco Launcher"));
|
||||
progress.setCancelButton(nullptr);
|
||||
progress.setWindowModality(Qt::ApplicationModal);
|
||||
progress.setMinimumDuration(0);
|
||||
@@ -34,80 +35,117 @@ int main(int argc, char* argv[])
|
||||
QApplication::processEvents();
|
||||
|
||||
const QString appDir = QApplication::applicationDirPath();
|
||||
ConfigHelper& config = ConfigHelper::instance();
|
||||
const auto isLicenseError = [](const QString& errorText) {
|
||||
ConfigHelper &config = ConfigHelper::instance();
|
||||
const auto isLicenseError = [&translation](const QString &errorText)
|
||||
{
|
||||
const QString text = errorText.toLower();
|
||||
return text.contains("license")
|
||||
|| errorText.contains("授权")
|
||||
|| errorText.contains("过期")
|
||||
|| errorText.contains("设备数量已达到上限")
|
||||
|| errorText.contains("已绑定其他授权");
|
||||
QStringList markers{QStringLiteral("license")};
|
||||
const char *const markerSources[]{
|
||||
QT_TRANSLATE_NOOP("LauncherErrorMarker", "authorization"),
|
||||
QT_TRANSLATE_NOOP("LauncherErrorMarker", "expired"),
|
||||
QT_TRANSLATE_NOOP("LauncherErrorMarker", "device limit reached"),
|
||||
QT_TRANSLATE_NOOP("LauncherErrorMarker", "bound to another license")};
|
||||
for (const char *source : markerSources)
|
||||
{
|
||||
markers.append(QCoreApplication::translate("LauncherErrorMarker", source));
|
||||
markers.append(translation.translate("LauncherErrorMarker", source));
|
||||
}
|
||||
for (const QString &marker : markers)
|
||||
{
|
||||
if (!marker.isEmpty() && text.contains(marker.toLower()))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const auto clearDeviceCredential = [&]() {
|
||||
const auto clearDeviceCredential = [&]()
|
||||
{
|
||||
QFile::remove(QDir(appDir).filePath("config/client_identity.dat"));
|
||||
config.setValue("Update", "device_id", QString());
|
||||
};
|
||||
QString licenseKey = config.getValue("License", "license_key").trimmed();
|
||||
auto promptAndSaveLicense = [&](const QString& reason) -> QString {
|
||||
auto promptAndSaveLicense = [&](const QString &reason) -> QString
|
||||
{
|
||||
QString promptReason = reason;
|
||||
while (true) {
|
||||
while (true)
|
||||
{
|
||||
progress.close();
|
||||
bool accepted = false;
|
||||
const QString appName = config.getValue("App", "app_name").trimmed();
|
||||
const QString displayName = appName.isEmpty()
|
||||
? QCoreApplication::translate("Launcher", "application")
|
||||
: appName;
|
||||
const QString prompt = promptReason.trimmed().isEmpty()
|
||||
? QString("请输入%1授权 License:").arg(appName.isEmpty() ? "软件" : appName)
|
||||
: QString("%1\n\n请重新输入%2授权 License:").arg(promptReason, appName.isEmpty() ? "软件" : appName);
|
||||
? QCoreApplication::translate("Launcher", "Enter the license for %1:").arg(displayName)
|
||||
: QCoreApplication::translate("Launcher", "%1\n\nPlease re-enter the license for %2:").arg(promptReason, displayName);
|
||||
licenseKey = QInputDialog::getText(
|
||||
nullptr,
|
||||
"输入 License",
|
||||
prompt,
|
||||
QLineEdit::Normal,
|
||||
QString(),
|
||||
&accepted
|
||||
).trimmed();
|
||||
if (!accepted) {
|
||||
QMessageBox::information(nullptr, "需要 License", "首次启动需要输入管理员提供的 License。");
|
||||
nullptr,
|
||||
QCoreApplication::translate("Launcher", "Enter License"),
|
||||
prompt,
|
||||
QLineEdit::Normal,
|
||||
QString(),
|
||||
&accepted)
|
||||
.trimmed();
|
||||
if (!accepted)
|
||||
{
|
||||
QMessageBox::information(
|
||||
nullptr,
|
||||
QCoreApplication::translate("Launcher", "License Required"),
|
||||
QCoreApplication::translate("Launcher", "A license provided by an administrator is required for the first launch."));
|
||||
return QString();
|
||||
}
|
||||
if (licenseKey.isEmpty()) {
|
||||
QMessageBox::warning(nullptr, "License 不能为空", "请粘贴管理员在后台创建的 License。");
|
||||
if (licenseKey.isEmpty())
|
||||
{
|
||||
QMessageBox::warning(
|
||||
nullptr,
|
||||
QCoreApplication::translate("Launcher", "License Cannot Be Empty"),
|
||||
QCoreApplication::translate("Launcher", "Paste the license created by an administrator."));
|
||||
promptReason.clear();
|
||||
continue;
|
||||
}
|
||||
if (!config.setValue("License", "license_key", licenseKey)) {
|
||||
QMessageBox::critical(nullptr, "保存 License 失败",
|
||||
QString("无法写入配置文件:%1\n%2").arg(config.configPath(), config.lastError()));
|
||||
if (!config.setValue("License", "license_key", licenseKey))
|
||||
{
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
QCoreApplication::translate("Launcher", "Failed to Save License"),
|
||||
QCoreApplication::translate("Launcher", "Cannot save system state at %1:\n%2")
|
||||
.arg(config.configPath(), config.lastError()));
|
||||
return QString();
|
||||
}
|
||||
progress.show();
|
||||
progress.setLabelText("正在验证 License...");
|
||||
progress.setLabelText(QCoreApplication::translate("Launcher", "Verifying license..."));
|
||||
QApplication::processEvents();
|
||||
return licenseKey;
|
||||
}
|
||||
};
|
||||
|
||||
while (true) {
|
||||
if (licenseKey.isEmpty()) {
|
||||
while (true)
|
||||
{
|
||||
if (licenseKey.isEmpty())
|
||||
{
|
||||
licenseKey = promptAndSaveLicense(QString());
|
||||
if (licenseKey.isEmpty()) return 0;
|
||||
if (licenseKey.isEmpty())
|
||||
return 0;
|
||||
}
|
||||
DeviceIdentityHelper identity(appDir);
|
||||
if (identity.ensureIssued(config.getValue("Server", "api_base_url"),
|
||||
config.getValue("Server", "client_token"),
|
||||
config.getValue("App", "app_id"),
|
||||
config.getValue("App", "channel"),
|
||||
licenseKey)) {
|
||||
licenseKey))
|
||||
{
|
||||
break;
|
||||
}
|
||||
const QString error = identity.errorString();
|
||||
if (!isLicenseError(error)) {
|
||||
if (!isLicenseError(error))
|
||||
{
|
||||
progress.close();
|
||||
QMessageBox::critical(nullptr, "设备身份验证失败", error);
|
||||
QMessageBox::critical(nullptr, QCoreApplication::translate("Launcher", "Device Authentication Failed"), error);
|
||||
return -1;
|
||||
}
|
||||
clearDeviceCredential();
|
||||
licenseKey = promptAndSaveLicense(QString("当前 License 无法使用:%1").arg(error));
|
||||
if (licenseKey.isEmpty()) return 0;
|
||||
licenseKey = promptAndSaveLicense(QCoreApplication::translate("Launcher", "The current license cannot be used: %1").arg(error));
|
||||
if (licenseKey.isEmpty())
|
||||
return 0;
|
||||
}
|
||||
|
||||
UpdateLogic logic;
|
||||
@@ -121,27 +159,37 @@ int main(int argc, char* argv[])
|
||||
const QString appId = logic.getAppId();
|
||||
const QString channel = logic.getChannel();
|
||||
|
||||
if (logic.lastStatusCode() == 401 || logic.lastStatusCode() == 403) {
|
||||
if (logic.lastStatusCode() == 401 || logic.lastStatusCode() == 403)
|
||||
{
|
||||
progress.close();
|
||||
const QJsonValue detail = response.value("detail");
|
||||
const QString message = detail.isObject() ? detail.toObject().value("msg").toString() : detail.toString();
|
||||
const QString displayMessage = message.isEmpty() ? "设备或 License 授权无效。" : message;
|
||||
if (isLicenseError(displayMessage)) {
|
||||
const QString displayMessage = message.isEmpty()
|
||||
? QCoreApplication::translate("Launcher", "The device or license authorization is invalid.")
|
||||
: message;
|
||||
if (isLicenseError(displayMessage))
|
||||
{
|
||||
clearDeviceCredential();
|
||||
const QString newLicense = promptAndSaveLicense(QString("当前授权被服务端拒绝:%1").arg(displayMessage));
|
||||
if (!newLicense.isEmpty()) {
|
||||
const QString newLicense = promptAndSaveLicense(
|
||||
QCoreApplication::translate("Launcher", "The server rejected the current license: %1").arg(displayMessage));
|
||||
if (!newLicense.isEmpty())
|
||||
{
|
||||
progress.close();
|
||||
QMessageBox::information(nullptr, "License 已保存", "请重新启动 Launcher 完成设备授权和更新检查。");
|
||||
QMessageBox::information(
|
||||
nullptr,
|
||||
QCoreApplication::translate("Launcher", "License Saved"),
|
||||
QCoreApplication::translate("Launcher", "Restart Launcher to complete device authorization and check for updates."));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
QMessageBox::critical(nullptr, "授权被拒绝", displayMessage);
|
||||
QMessageBox::critical(nullptr, QCoreApplication::translate("Launcher", "Authorization Denied"), displayMessage);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const QString launchToken = config.getValue("App", "launch_token");
|
||||
const QString currentVersion = config.getValue("App", "current_version");
|
||||
const auto configuredName = [&](const QString& key, const QString& fallback) {
|
||||
const auto configuredName = [&](const QString &key, const QString &fallback)
|
||||
{
|
||||
const QString value = config.getValue("Runtime", key).trimmed();
|
||||
return value.isEmpty() ? fallback : value;
|
||||
};
|
||||
@@ -149,53 +197,99 @@ int main(int argc, char* argv[])
|
||||
const QString updaterExecutable = configuredName("updater_executable", "Updater.exe");
|
||||
const QString mainAppPath = QDir(appDir).filePath(mainExecutable);
|
||||
const QString updaterPath = QDir(appDir).filePath(updaterExecutable);
|
||||
const auto importOfflinePackage = [&]() {
|
||||
const QString package = QFileDialog::getOpenFileName(nullptr, "选择离线更新包", QString(), "Marsco 离线更新包 (*.upd)");
|
||||
IntegrityHelper integrity(config.installRoot());
|
||||
if (!integrity.verifyInstalledVersion(appId, channel, currentVersion))
|
||||
{
|
||||
progress.close();
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
QCoreApplication::translate("Launcher", "Integrity Check Failed"),
|
||||
QCoreApplication::translate(
|
||||
"Launcher",
|
||||
"Application files failed signed manifest verification:\n%1")
|
||||
.arg(integrity.errorString()));
|
||||
return -1;
|
||||
}
|
||||
const auto importOfflinePackage = [&]()
|
||||
{
|
||||
const QString package = QFileDialog::getOpenFileName(
|
||||
nullptr,
|
||||
QCoreApplication::translate("Launcher", "Select Offline Update Package"),
|
||||
QString(),
|
||||
QCoreApplication::translate("Launcher", "Marsco Offline Update Package (*.upd)"));
|
||||
return package.isEmpty() ? false : QProcess::startDetached(updaterPath, QStringList{QString("--offline-package=%1").arg(package)});
|
||||
};
|
||||
const QString deviceId = config.getValue("Update", "device_id");
|
||||
if (QCoreApplication::arguments().contains("--import-offline")) {
|
||||
if (QCoreApplication::arguments().contains("--import-offline"))
|
||||
{
|
||||
progress.close();
|
||||
if (!importOfflinePackage()) QMessageBox::information(nullptr, "离线更新", "未选择离线更新包。");
|
||||
if (!importOfflinePackage())
|
||||
QMessageBox::information(
|
||||
nullptr,
|
||||
QCoreApplication::translate("Launcher", "Offline Update"),
|
||||
QCoreApplication::translate("Launcher", "No offline update package was selected."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto startMainApp = [&]() {
|
||||
const auto startMainApp = [&]()
|
||||
{
|
||||
QString ticketPath;
|
||||
QString ticketError;
|
||||
if (!TicketHelper::createTicket(logic.getAppId(), deviceId, currentVersion,
|
||||
launchToken, &ticketPath, &ticketError)) {
|
||||
launchToken, &ticketPath, &ticketError))
|
||||
{
|
||||
qDebug() << "Cannot create launch ticket:" << ticketError;
|
||||
return false;
|
||||
}
|
||||
const bool started = QProcess::startDetached(mainAppPath,
|
||||
QStringList{QString("--ticket-file=%1").arg(ticketPath)});
|
||||
if (!started) QFile::remove(ticketPath);
|
||||
QStringList{QString("--ticket-file=%1").arg(ticketPath)});
|
||||
if (!started)
|
||||
QFile::remove(ticketPath);
|
||||
return started;
|
||||
};
|
||||
|
||||
progress.setLabelText("正在验证本地运行策略...");
|
||||
progress.setLabelText(QCoreApplication::translate("Launcher", "Validating the local run policy..."));
|
||||
QApplication::processEvents();
|
||||
|
||||
PolicyHelper policy(appDir);
|
||||
if (!policy.loadPolicy("config/version_policy.dat") || !policy.isValid())
|
||||
{
|
||||
progress.close();
|
||||
QMessageBox::critical(nullptr, "无法启动", QString("本地版本策略无效:%1").arg(policy.errorString()));
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
QCoreApplication::translate("Launcher", "Unable to Start"),
|
||||
QCoreApplication::translate("Launcher", "The local version policy is invalid: %1").arg(policy.errorString()));
|
||||
return -1;
|
||||
}
|
||||
if (!policy.isApplicable(appId, channel, currentVersion))
|
||||
{
|
||||
progress.close();
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
QCoreApplication::translate("Launcher", "Unable to Start"),
|
||||
QCoreApplication::translate("Launcher", "The local version policy is invalid: %1").arg(policy.errorString()));
|
||||
return -1;
|
||||
}
|
||||
if (policy.isExpired())
|
||||
{
|
||||
progress.close();
|
||||
QMessageBox::critical(nullptr, "无法启动", "本地版本策略已过期,请连接网络或联系管理员。");
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
QCoreApplication::translate("Launcher", "Unable to Start"),
|
||||
QCoreApplication::translate("Launcher", "The local version policy has expired. Connect to the network or contact an administrator."));
|
||||
return -1;
|
||||
}
|
||||
if ((!policy.allowRun() || !policy.isVersionAllowed(currentVersion)) && !(networkOk && needUpdate))
|
||||
const bool currentVersionAllowed =
|
||||
policy.allowRun() && policy.isVersionAllowed(currentVersion);
|
||||
if (!currentVersionAllowed && !(networkOk && needUpdate))
|
||||
{
|
||||
progress.close();
|
||||
QMessageBox::critical(nullptr, "当前版本不可运行",
|
||||
policy.message().isEmpty() ? QString("当前版本 %1 已被管理员停用。").arg(currentVersion)
|
||||
: policy.message());
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
QCoreApplication::translate("Launcher", "Current Version Cannot Run"),
|
||||
policy.message().isEmpty()
|
||||
? QCoreApplication::translate("Launcher", "Version %1 has been disabled by an administrator.").arg(currentVersion)
|
||||
: policy.message());
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -203,19 +297,28 @@ int main(int argc, char* argv[])
|
||||
if (!state.loadState())
|
||||
{
|
||||
progress.close();
|
||||
QMessageBox::critical(nullptr, "无法启动", QString("无法读取本地状态:%1").arg(state.errorString()));
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
QCoreApplication::translate("Launcher", "Unable to Start"),
|
||||
QCoreApplication::translate("Launcher", "Cannot read the local state: %1").arg(state.errorString()));
|
||||
return -1;
|
||||
}
|
||||
if (state.isPolicySeqRolledBack(policy.policySeq()))
|
||||
{
|
||||
progress.close();
|
||||
QMessageBox::critical(nullptr, "安全检查失败", "检测到版本策略序列回退,已阻止启动。");
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
QCoreApplication::translate("Launcher", "Security Check Failed"),
|
||||
QCoreApplication::translate("Launcher", "A version policy sequence rollback was detected, so startup was blocked."));
|
||||
return -1;
|
||||
}
|
||||
if (state.isSystemTimeRewound())
|
||||
{
|
||||
progress.close();
|
||||
QMessageBox::critical(nullptr, "安全检查失败", "检测到系统时间可能被回拨,已阻止启动。");
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
QCoreApplication::translate("Launcher", "Security Check Failed"),
|
||||
QCoreApplication::translate("Launcher", "A possible system clock rollback was detected, so startup was blocked."));
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -223,22 +326,36 @@ int main(int argc, char* argv[])
|
||||
{
|
||||
progress.close();
|
||||
const bool rollbackOperation = response.value("action").toString() == "rollback_allowed";
|
||||
const QString dialogTitle = rollbackOperation ? "版本回退"
|
||||
: (policy.forceUpdate() ? "必须更新" : "发现新版本");
|
||||
const bool updateRequired = policy.forceUpdate() || !currentVersionAllowed;
|
||||
const QString dialogTitle = rollbackOperation
|
||||
? QCoreApplication::translate("Launcher", "Version Rollback")
|
||||
: (updateRequired
|
||||
? QCoreApplication::translate("Launcher", "Update Required")
|
||||
: QCoreApplication::translate("Launcher", "New Version Available"));
|
||||
const QString prompt = policy.message().isEmpty()
|
||||
? QString(rollbackOperation ? "管理员提供了版本 %1 作为回退目标,是否现在降级?"
|
||||
: "发现新版本 %1,是否现在更新?").arg(latestVer)
|
||||
: policy.message() + QString("\n目标版本:%1").arg(latestVer);
|
||||
? (rollbackOperation
|
||||
? QCoreApplication::translate("Launcher", "An administrator selected version %1 as the rollback target. Downgrade now?")
|
||||
: QCoreApplication::translate("Launcher", "Version %1 is available. Update now?"))
|
||||
.arg(latestVer)
|
||||
: policy.message() + QCoreApplication::translate("Launcher", "\nTarget version: %1").arg(latestVer);
|
||||
bool accepted = true;
|
||||
if (policy.forceUpdate()) {
|
||||
if (updateRequired)
|
||||
{
|
||||
QMessageBox::information(nullptr, dialogTitle, prompt);
|
||||
} else {
|
||||
accepted = QMessageBox::question(nullptr, dialogTitle, prompt,
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes;
|
||||
}
|
||||
if (!accepted) {
|
||||
if (!startMainApp()) {
|
||||
QMessageBox::critical(nullptr, "启动失败", QString("无法启动主程序:%1").arg(mainAppPath));
|
||||
else
|
||||
{
|
||||
accepted = QMessageBox::question(nullptr, dialogTitle, prompt,
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes;
|
||||
}
|
||||
if (!accepted)
|
||||
{
|
||||
if (!startMainApp())
|
||||
{
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
QCoreApplication::translate("Launcher", "Startup Failed"),
|
||||
QCoreApplication::translate("Launcher", "Cannot start the main application: %1").arg(mainAppPath));
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
@@ -246,17 +363,28 @@ int main(int argc, char* argv[])
|
||||
const QStringList updaterArgs{appId, channel, latestVer, QString::number(targetVersionId)};
|
||||
if (!QProcess::startDetached(updaterPath, updaterArgs))
|
||||
{
|
||||
QMessageBox::critical(nullptr, "更新器启动失败", QString("无法启动更新器:%1").arg(updaterPath));
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
QCoreApplication::translate("Launcher", "Updater Startup Failed"),
|
||||
QCoreApplication::translate("Launcher", "Cannot start the updater: %1").arg(updaterPath));
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!networkOk) {
|
||||
if (!networkOk)
|
||||
{
|
||||
progress.close();
|
||||
if (QMessageBox::question(nullptr, "服务器不可用", "当前无法连接更新服务器。是否导入离线更新包?",
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) {
|
||||
if (!importOfflinePackage()) QMessageBox::information(nullptr, "离线更新", "未选择离线更新包或无法启动更新器。");
|
||||
if (QMessageBox::question(nullptr,
|
||||
QCoreApplication::translate("Launcher", "Server Unavailable"),
|
||||
QCoreApplication::translate("Launcher", "The update server is currently unreachable. Import an offline update package?"),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes)
|
||||
{
|
||||
if (!importOfflinePackage())
|
||||
QMessageBox::information(
|
||||
nullptr,
|
||||
QCoreApplication::translate("Launcher", "Offline Update"),
|
||||
QCoreApplication::translate("Launcher", "No offline update package was selected, or the updater could not be started."));
|
||||
return 0;
|
||||
}
|
||||
progress.show();
|
||||
@@ -265,16 +393,24 @@ int main(int argc, char* argv[])
|
||||
if (!networkOk && !policy.isOfflineAllowed())
|
||||
{
|
||||
progress.close();
|
||||
QMessageBox::critical(nullptr, "网络不可用", "无法连接更新服务器,且当前策略不允许离线启动。");
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
QCoreApplication::translate("Launcher", "Network Unavailable"),
|
||||
QCoreApplication::translate("Launcher", "The update server is unreachable and the current policy does not allow offline startup."));
|
||||
return -1;
|
||||
}
|
||||
|
||||
progress.setLabelText(networkOk ? "当前已是最新版本,正在启动..." : "当前处于离线模式,正在启动...");
|
||||
progress.setLabelText(
|
||||
networkOk ? QCoreApplication::translate("Launcher", "The current version is up to date. Starting...")
|
||||
: QCoreApplication::translate("Launcher", "Offline mode is active. Starting..."));
|
||||
QApplication::processEvents();
|
||||
if (!startMainApp())
|
||||
{
|
||||
progress.close();
|
||||
QMessageBox::critical(nullptr, "启动失败", QString("无法启动主程序:%1").arg(mainAppPath));
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
QCoreApplication::translate("Launcher", "Startup Failed"),
|
||||
QCoreApplication::translate("Launcher", "Cannot start the main application: %1").arg(mainAppPath));
|
||||
return -1;
|
||||
}
|
||||
progress.close();
|
||||
|
||||
Reference in New Issue
Block a user