#include #include #include #include #include #include #include #include #include #include "UpdateLogic.h" #include "../Common/ConfigHelper.h" #include "../Common/PolicyHelper.h" #include "../Common/LocalStateHelper.h" #include "../Common/TicketHelper.h" #include "../Common/DeviceIdentityHelper.h" #include #include #include 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 软件启动器"); progress.setCancelButton(nullptr); progress.setWindowModality(Qt::ApplicationModal); progress.setMinimumDuration(0); progress.setAutoClose(false); progress.show(); QApplication::processEvents(); const QString appDir = QApplication::applicationDirPath(); ConfigHelper& config = ConfigHelper::instance(); const auto isLicenseError = [](const QString& errorText) { const QString text = errorText.toLower(); return text.contains("license") || errorText.contains("授权") || errorText.contains("过期") || errorText.contains("设备数量已达到上限") || errorText.contains("已绑定其他授权"); }; 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 { QString promptReason = reason; while (true) { progress.close(); bool accepted = false; const QString appName = config.getValue("App", "app_name").trimmed(); const QString prompt = promptReason.trimmed().isEmpty() ? QString("请输入%1授权 License:").arg(appName.isEmpty() ? "软件" : appName) : QString("%1\n\n请重新输入%2授权 License:").arg(promptReason, appName.isEmpty() ? "软件" : appName); licenseKey = QInputDialog::getText( nullptr, "输入 License", prompt, QLineEdit::Normal, QString(), &accepted ).trimmed(); if (!accepted) { QMessageBox::information(nullptr, "需要 License", "首次启动需要输入管理员提供的 License。"); return QString(); } if (licenseKey.isEmpty()) { QMessageBox::warning(nullptr, "License 不能为空", "请粘贴管理员在后台创建的 License。"); promptReason.clear(); continue; } if (!config.setValue("License", "license_key", licenseKey)) { QMessageBox::critical(nullptr, "保存 License 失败", QString("无法写入配置文件:%1\n%2").arg(config.configPath(), config.lastError())); return QString(); } progress.show(); progress.setLabelText("正在验证 License..."); QApplication::processEvents(); return licenseKey; } }; while (true) { if (licenseKey.isEmpty()) { licenseKey = promptAndSaveLicense(QString()); 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)) { break; } const QString error = identity.errorString(); if (!isLicenseError(error)) { progress.close(); QMessageBox::critical(nullptr, "设备身份验证失败", error); return -1; } clearDeviceCredential(); licenseKey = promptAndSaveLicense(QString("当前 License 无法使用:%1").arg(error)); if (licenseKey.isEmpty()) return 0; } UpdateLogic logic; logic.checkUpdate(); const bool needUpdate = logic.getNeedUpdate(); const bool networkOk = logic.isNetworkOk(); const QString latestVer = logic.getLatestVersion(); const QJsonObject response = logic.getCheckResult(); const int targetVersionId = response.value("version_id").toInt(); const QString appId = logic.getAppId(); const QString channel = logic.getChannel(); 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)) { clearDeviceCredential(); const QString newLicense = promptAndSaveLicense(QString("当前授权被服务端拒绝:%1").arg(displayMessage)); if (!newLicense.isEmpty()) { progress.close(); QMessageBox::information(nullptr, "License 已保存", "请重新启动 Launcher 完成设备授权和更新检查。"); } return 0; } QMessageBox::critical(nullptr, "授权被拒绝", 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 QString value = config.getValue("Runtime", key).trimmed(); return value.isEmpty() ? fallback : value; }; const QString mainExecutable = configuredName("main_executable", "MainApp.exe"); 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)"); 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")) { progress.close(); if (!importOfflinePackage()) QMessageBox::information(nullptr, "离线更新", "未选择离线更新包。"); return 0; } const auto startMainApp = [&]() { QString ticketPath; QString ticketError; if (!TicketHelper::createTicket(logic.getAppId(), deviceId, currentVersion, 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); return started; }; progress.setLabelText("正在验证本地运行策略..."); QApplication::processEvents(); PolicyHelper policy(appDir); if (!policy.loadPolicy("config/version_policy.dat") || !policy.isValid()) { progress.close(); QMessageBox::critical(nullptr, "无法启动", QString("本地版本策略无效:%1").arg(policy.errorString())); return -1; } if (policy.isExpired()) { progress.close(); QMessageBox::critical(nullptr, "无法启动", "本地版本策略已过期,请连接网络或联系管理员。"); return -1; } if ((!policy.allowRun() || !policy.isVersionAllowed(currentVersion)) && !(networkOk && needUpdate)) { progress.close(); QMessageBox::critical(nullptr, "当前版本不可运行", policy.message().isEmpty() ? QString("当前版本 %1 已被管理员停用。").arg(currentVersion) : policy.message()); return -1; } LocalStateHelper state(appDir); if (!state.loadState()) { progress.close(); QMessageBox::critical(nullptr, "无法启动", QString("无法读取本地状态:%1").arg(state.errorString())); return -1; } if (state.isPolicySeqRolledBack(policy.policySeq())) { progress.close(); QMessageBox::critical(nullptr, "安全检查失败", "检测到版本策略序列回退,已阻止启动。"); return -1; } if (state.isSystemTimeRewound()) { progress.close(); QMessageBox::critical(nullptr, "安全检查失败", "检测到系统时间可能被回拨,已阻止启动。"); return -1; } if (networkOk && needUpdate) { progress.close(); const bool rollbackOperation = response.value("action").toString() == "rollback_allowed"; const QString dialogTitle = rollbackOperation ? "版本回退" : (policy.forceUpdate() ? "必须更新" : "发现新版本"); const QString prompt = policy.message().isEmpty() ? QString(rollbackOperation ? "管理员提供了版本 %1 作为回退目标,是否现在降级?" : "发现新版本 %1,是否现在更新?").arg(latestVer) : policy.message() + QString("\n目标版本:%1").arg(latestVer); bool accepted = true; if (policy.forceUpdate()) { 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)); return -1; } return 0; } const QStringList updaterArgs{appId, channel, latestVer, QString::number(targetVersionId)}; if (!QProcess::startDetached(updaterPath, updaterArgs)) { QMessageBox::critical(nullptr, "更新器启动失败", QString("无法启动更新器:%1").arg(updaterPath)); return -1; } return 0; } if (!networkOk) { progress.close(); if (QMessageBox::question(nullptr, "服务器不可用", "当前无法连接更新服务器。是否导入离线更新包?", QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { if (!importOfflinePackage()) QMessageBox::information(nullptr, "离线更新", "未选择离线更新包或无法启动更新器。"); return 0; } progress.show(); } if (!networkOk && !policy.isOfflineAllowed()) { progress.close(); QMessageBox::critical(nullptr, "网络不可用", "无法连接更新服务器,且当前策略不允许离线启动。"); return -1; } progress.setLabelText(networkOk ? "当前已是最新版本,正在启动..." : "当前处于离线模式,正在启动..."); QApplication::processEvents(); if (!startMainApp()) { progress.close(); QMessageBox::critical(nullptr, "启动失败", QString("无法启动主程序:%1").arg(mainAppPath)); return -1; } progress.close(); return 0; }