#include "mainwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include const QString UPDATE_F_VERSION_FILE = ""; const QString DATA_DIR = ""; void ensureDataDirExists() { QString dataDir = ""; QDir dDrive(""); if (!dDrive.exists()) { dataDir = ""; } QDir dir(dataDir); if (!dir.exists()) { dir.mkpath("."); } const wchar_t* path = reinterpret_cast(dataDir.utf16()); DWORD attributes = GetFileAttributesW(path); if (attributes != INVALID_FILE_ATTRIBUTES) { SetFileAttributesW(path, attributes | FILE_ATTRIBUTE_HIDDEN); } } AuthWindow::AuthWindow(const QString &deviceId, const QString &savedKami, QWidget *parent) : QDialog(parent) { setWindowTitle("卡密验证"); setFixedSize(400, 250); setWindowModality(Qt::ApplicationModal); QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(20, 20, 20, 20); layout->setSpacing(15); QLabel *deviceLabel = new QLabel("设备ID: " + deviceId); deviceLabel->setStyleSheet("font-size: 10pt;"); layout->addWidget(deviceLabel); QLabel *kamiLabel = new QLabel("卡密:"); layout->addWidget(kamiLabel); kamiEntry = new QLineEdit(); kamiEntry->setPlaceholderText("请输入您的卡密"); if (!savedKami.isEmpty()) { kamiEntry->setText(savedKami); } layout->addWidget(kamiEntry); rememberCheck = new QCheckBox("记住卡密"); rememberCheck->setChecked(true); layout->addWidget(rememberCheck); QHBoxLayout *btnLayout = new QHBoxLayout(); QPushButton *okBtn = new QPushButton("验证"); connect(okBtn, &QPushButton::clicked, this, &QDialog::accept); btnLayout->addWidget(okBtn); QPushButton *cancelBtn = new QPushButton("取消"); connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject); btnLayout->addWidget(cancelBtn); layout->addLayout(btnLayout); } QString AuthWindow::getKami() const { return kamiEntry->text().trimmed(); } bool AuthWindow::getRemember() const { return rememberCheck->isChecked(); } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , m_isFirstUpdateInProgress(false) // 先初始化这个 , gameProcess(nullptr) // 然后初始化这个 { // 检测是否是首次启动 QDir dataDir(DATA_DIR); isFirstLaunch = !dataDir.exists(); ensureDataDirExists(); settings = new QSettings("GameStudio", "maimaiLauncher", this); loadSettings(); SERVER_URL = ""; VERSION_FILE = ""; UPDATE_ZIP = ""; ANNOUNCEMENT_FILE = ""; DEVICE_CODE_FILE = DATA_DIR + ""; CARD_FILE = DATA_DIR + ""; deviceId = getDeviceId(); savedKami = loadSavedKami(); setupUI(); checkAdminRights(); // 加载本地版本信息 loadLocalVersion(); checkPackageExists(); disableButtons(); if (!savedKami.isEmpty()) { authStatus->setText("使用保存的卡密验证中..."); QTimer::singleShot(100, this, [this]() { performNetworkAuthentication(savedKami, true); checkAndDeleteFiles(); // 添加删除检查 }); } else { authStatus->setText("等待卡密验证"); QTimer::singleShot(100, this, &MainWindow::showAuthWindow); } fetchAnnouncement(); quitTimer = new QTimer(this); quitTimer->setSingleShot(true); connect(quitTimer, &QTimer::timeout, this, &MainWindow::quitApplication); } MainWindow::~MainWindow() { saveSettings(); delete settings; } void MainWindow::loadLocalVersion() { QString versionFilePath = UPDATE_PATH + "/" + VERSION_FILE; QFile file(versionFilePath); if (file.exists() && file.open(QIODevice::ReadOnly)) { QByteArray data = file.readAll(); file.close(); QJsonDocument doc = QJsonDocument::fromJson(data); if (!doc.isNull() && doc.isObject()) { localVersion = doc.object(); QString ver = localVersion["version"].toString(); versionLabel->setText("版本: v" + ver); qDebug() << "加载本地版本: v" << ver; } else { versionLabel->setText("版本: 文件损坏"); qDebug() << "版本文件损坏"; } } else { // 如果版本文件不存在,创建初始版本 localVersion = QJsonObject(); localVersion["version"] = "0.0.0"; saveLocalVersion(); versionLabel->setText("版本: 未安装"); qDebug() << "创建初始版本文件"; } } void MainWindow::setupUI() { setWindowTitle("maimai启动器 v" + LAUNCHER_VERSION); setFixedSize(800, 600); QWidget *centralWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget); QWidget *pathWidget = new QWidget(); QHBoxLayout *pathLayout = new QHBoxLayout(pathWidget); pathLayout->setContentsMargins(10, 5, 10, 5); QLabel *pathTitle = new QLabel("Package路径:"); pathLabel = new QLabel(UPDATE_PATH); pathLabel->setStyleSheet("background-color: #f0f0f0; border: 1px solid #ccc; padding: 3px;"); pathLabel->setMinimumWidth(300); pathSelectBtn = new QPushButton("选择路径"); pathSelectBtn->setFixedSize(80, 25); connect(pathSelectBtn, &QPushButton::clicked, this, &MainWindow::selectPackagePath); pathLayout->addWidget(pathTitle); pathLayout->addWidget(pathLabel, 1); pathLayout->addWidget(pathSelectBtn); mainLayout->addWidget(pathWidget); QWidget *contentWidget = new QWidget(); QHBoxLayout *contentLayout = new QHBoxLayout(contentWidget); QWidget *leftWidget = new QWidget(); QVBoxLayout *leftLayout = new QVBoxLayout(leftWidget); leftLayout->setContentsMargins(10, 10, 10, 10); QLabel *titleLabel = new QLabel("maimai启动器"); titleLabel->setStyleSheet("font-size: 16pt; font-weight: bold;"); leftLayout->addWidget(titleLabel, 0, Qt::AlignCenter); QLabel *adminLabel = new QLabel("(已获得管理员权限)"); adminLabel->setStyleSheet("color: green; font-size: 8pt;"); leftLayout->addWidget(adminLabel, 0, Qt::AlignCenter); authStatus = new QLabel("验证状态: 正在初始化..."); authStatus->setStyleSheet("color: blue; font-weight: bold;"); leftLayout->addWidget(authStatus, 0, Qt::AlignCenter); vipInfo = new QLabel("VIP状态: 未验证"); vipInfo->setStyleSheet("color: purple;"); leftLayout->addWidget(vipInfo, 0, Qt::AlignCenter); versionLabel = new QLabel("版本: 加载中..."); leftLayout->addWidget(versionLabel, 0, Qt::AlignCenter); progressBar = new QProgressBar(); progressBar->setFixedHeight(20); leftLayout->addWidget(progressBar); statusLabel = new QLabel("等待验证..."); leftLayout->addWidget(statusLabel, 0, Qt::AlignCenter); QWidget *buttonWidget = new QWidget(); QVBoxLayout *buttonLayout = new QVBoxLayout(buttonWidget); QHBoxLayout *row1 = new QHBoxLayout(); startBtn = new QPushButton("启动游戏"); startBtn->setFixedSize(120, 35); startBtn->setEnabled(false); connect(startBtn, &QPushButton::clicked, this, &MainWindow::startGame); row1->addWidget(startBtn); oddBtn = new QPushButton("启动ODD"); oddBtn->setFixedSize(120, 35); oddBtn->setEnabled(false); connect(oddBtn, &QPushButton::clicked, this, &MainWindow::startOdd); row1->addWidget(oddBtn); buttonLayout->addLayout(row1); QHBoxLayout *row2 = new QHBoxLayout(); updateBtn = new QPushButton("更新"); updateBtn->setFixedSize(120, 35); updateBtn->setEnabled(false); connect(updateBtn, &QPushButton::clicked, this, &MainWindow::forceUpdate); row2->addWidget(updateBtn); hostsBtn = new QPushButton("修改hosts"); hostsBtn->setFixedSize(120, 35); hostsBtn->setEnabled(false); connect(hostsBtn, &QPushButton::clicked, this, &MainWindow::modifyHosts); row2->addWidget(hostsBtn); buttonLayout->addLayout(row2); // 修复:将"更新完整包"按钮添加到row3 QHBoxLayout *row3 = new QHBoxLayout(); buyBtn = new QPushButton("购买卡密"); buyBtn->setFixedSize(120, 35); connect(buyBtn, &QPushButton::clicked, this, &MainWindow::openBuyPage); row3->addWidget(buyBtn); fullUpdateBtn = new QPushButton("更新完整包"); fullUpdateBtn->setFixedSize(120, 35); connect(fullUpdateBtn, &QPushButton::clicked, this, &MainWindow::forceFullUpdate); row3->addWidget(fullUpdateBtn); buttonLayout->addLayout(row3); QHBoxLayout *row4 = new QHBoxLayout(); wikiBtn = new QPushButton("wiki文档"); wikiBtn->setFixedSize(120, 35); connect(wikiBtn, &QPushButton::clicked, this, &MainWindow::openWikiPage); row4->addWidget(wikiBtn); // 添加Bug报告按钮 bugReportBtn = new QPushButton("反馈Bug"); bugReportBtn->setFixedSize(120, 35); connect(bugReportBtn, &QPushButton::clicked, this, &MainWindow::reportBug); row4->addWidget(bugReportBtn); buttonLayout->addLayout(row4); leftLayout->addWidget(buttonWidget); QGroupBox *rightGroup = new QGroupBox("最新公告"); rightGroup->setStyleSheet("QGroupBox { font-weight: bold; }"); QVBoxLayout *rightLayout = new QVBoxLayout(rightGroup); announcementText = new QTextEdit(); announcementText->setReadOnly(true); announcementText->setText("正在加载公告..."); announcementText->setStyleSheet("font-size: 10pt;"); rightLayout->addWidget(announcementText); contentLayout->addWidget(leftWidget, 2); contentLayout->addWidget(rightGroup, 1); mainLayout->addWidget(contentWidget, 1); QLabel *footerLabel = new QLabel("闲鱼:小xin喵"); footerLabel->setStyleSheet("color: gray; font-size: 8pt;"); mainLayout->addWidget(footerLabel, 0, Qt::AlignRight | Qt::AlignBottom); setCentralWidget(centralWidget); networkManager = new QNetworkAccessManager(this); } void MainWindow::forceFullUpdate() { if (!isAuthenticated) { QMessageBox::warning(this, "未验证", "请先完成卡密验证"); return; } // 确认用户操作 if (QMessageBox::question(this, "更新完整包", "确定要下载并安装完整游戏包吗?\n这将覆盖所有本地文件。", QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; } // 禁用相关按钮 fullUpdateBtn->setEnabled(false); startBtn->setEnabled(false); statusLabel->setText("开始下载完整游戏包..."); // 调用首次更新函数(该函数已实现完整包下载) fetchFirstUpdateVersion(); } void MainWindow::updateAnnouncement(const QJsonObject &announcement) { QString title = announcement["title"].toString("公告"); QString date = announcement["date"].toString(QDate::currentDate().toString("yyyy-MM-dd")); QString content = announcement["content"].toString("暂无公告内容。"); announcementText->clear(); announcementText->append(QString("
%1
").arg(title)); announcementText->append(QString("
发布日期: %1
").arg(date)); announcementText->append("
"); announcementText->append(QString("
%1
").arg(content)); } void MainWindow::reportBug() { // 创建邮件主题和正文 QString subject = QString("maimai启动器Bug报告 (v%1)").arg(LAUNCHER_VERSION); QString body = QString("设备ID: %1\n\n请描述您遇到的问题:\n").arg(deviceId); // 创建mailto链接 QString mailto = QString("mailto:2932869213@qq.com?subject=%1&body=%2") .arg(QString(QUrl::toPercentEncoding(subject))) .arg(QString(QUrl::toPercentEncoding(body))); // 打开默认邮件客户端 if (!QDesktopServices::openUrl(QUrl(mailto))) { QMessageBox::warning(this, "错误", "无法打开邮件客户端。请确保已安装邮件程序。"); } } void MainWindow::activateButtons() { if (isAuthenticated) { startBtn->setEnabled(true); oddBtn->setEnabled(true); updateBtn->setEnabled(true); hostsBtn->setEnabled(true); fullUpdateBtn->setEnabled(true); } buyBtn->setEnabled(true); pathSelectBtn->setEnabled(true); wikiBtn->setEnabled(true); } void MainWindow::disableButtons() { startBtn->setEnabled(false); oddBtn->setEnabled(false); updateBtn->setEnabled(false); hostsBtn->setEnabled(false); fullUpdateBtn->setEnabled(false); wikiBtn->setEnabled(false); } void MainWindow::openWikiPage() { QDesktopServices::openUrl(QUrl("")); } void MainWindow::checkAdminRights() { BOOL isAdmin = FALSE; SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; PSID AdministratorsGroup; if (AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &AdministratorsGroup)) { if (!CheckTokenMembership(NULL, AdministratorsGroup, &isAdmin)) { isAdmin = FALSE; } FreeSid(AdministratorsGroup); } if (!isAdmin) { QMessageBox::information(this, "权限提升", "启动器需要管理员权限运行,请允许UAC提示。"); wchar_t path[MAX_PATH]; GetModuleFileNameW(NULL, path, MAX_PATH); ShellExecuteW(NULL, L"runas", path, NULL, NULL, SW_SHOWNORMAL); QApplication::quit(); } } int MainWindow::compareVersions(const QString &v1, const QString &v2) { QStringList parts1 = v1.split('.'); QStringList parts2 = v2.split('.'); int maxParts = qMax(parts1.size(), parts2.size()); for (int i = 0; i < maxParts; i++) { int num1 = (i < parts1.size()) ? parts1[i].toInt() : 0; int num2 = (i < parts2.size()) ? parts2[i].toInt() : 0; if (num1 < num2) return -1; if (num1 > num2) return 1; } return 0; } // 修改后的解压函数,支持密码 bool MainWindow::extractZip(const QString &zipPath, const QString &extractDir, const QString &password) { QFile zipFile(zipPath); if (!zipFile.exists()) { qDebug() << "ZIP文件不存在:" << zipPath; return false; } QDir dir(extractDir); if (!dir.exists()) { if (!dir.mkpath(".")) { qDebug() << "无法创建目录:" << extractDir; return false; } } QString program; QStringList arguments; // 使用7z进行解压(支持密码) QString sevenZipPath = QCoreApplication::applicationDirPath() + "/7z/7z.exe"; if (QFile::exists(sevenZipPath)) { program = sevenZipPath; arguments << "x" << "-y" << "-o" + extractDir; if (!password.isEmpty()) { arguments << "-p" + password; } arguments << zipPath; } QProcess process; process.start(program, arguments); if (!process.waitForStarted()) { qDebug() << "无法启动解压进程"; return false; } if (!process.waitForFinished(300000)) { qDebug() << "解压进程超时"; return false; } if (process.exitCode() != 0) { qDebug() << "解压失败,错误码:" << process.exitCode(); qDebug() << "错误输出:" << process.readAllStandardError(); return false; } qDebug() << "成功解压文件到" << extractDir; return true; } void MainWindow::startGame() { if (!isAuthenticated) { QMessageBox::warning(this, "未验证", "请先完成卡密验证"); return; } QString batPath = UPDATE_PATH + ""; if (!QFile::exists(batPath)) { QMessageBox::critical(this, "错误", "找不到启动脚本: " + batPath); return; } disableButtons(); statusLabel->setText("正在启动游戏..."); // 使用QProcess启动bat文件 QProcess *gameProcess = new QProcess(this); gameProcess->setWorkingDirectory(UPDATE_PATH); // 连接游戏结束信号 connect(gameProcess, QOverload::of(&QProcess::finished), this, &MainWindow::onGameFinished); // 启动bat文件 gameProcess->start("cmd.exe", QStringList() << "/c" << batPath); if (!gameProcess->waitForStarted()) { statusLabel->setText("无法启动游戏"); activateButtons(); return; } statusLabel->setText("游戏运行中..."); } void MainWindow::startGameProcess() { // 确保 gameProcess 被正确创建 if (gameProcess) { gameProcess->kill(); gameProcess->deleteLater(); gameProcess = nullptr; } gameProcess = new QProcess(this); gameProcess->setWorkingDirectory(UPDATE_PATH); // 连接游戏结束信号 connect(gameProcess, QOverload::of(&QProcess::finished), this, &MainWindow::onGameFinished); // 启动注入程序 - 使用新的 QProcess 实例 QProcess *injectProcess = new QProcess(this); injectProcess->setWorkingDirectory(UPDATE_PATH); QStringList injectArgs; injectArgs << "-d" << "-k" << "mai2hook.dll" << "amdaemon.exe" << "-f" << "-c" << "config_common.json" << "config_server.json" << "config_client.json"; // 增加超时时间到15秒(15000毫秒) injectProcess->start("inject", injectArgs); // 增加等待时间到15秒 if (!injectProcess->waitForFinished(15000)) { statusLabel->setText("注入程序超时"); injectProcess->deleteLater(); activateButtons(); return; } injectProcess->deleteLater(); // 启动游戏主程序 QStringList gameArgs; gameArgs << "-screen-fullscreen" << "1" << "-screen-width" << "1080" << "-screen-height" << "1920" << "-silent-crashes"; gameProcess->start("Sinmai.exe", gameArgs); if (!gameProcess->waitForStarted()) { statusLabel->setText("无法启动游戏"); activateButtons(); return; } statusLabel->setText("游戏运行中..."); } void MainWindow::onGameFinished(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitCode); Q_UNUSED(exitStatus); statusLabel->setText("游戏进程已结束"); activateButtons(); // 删除QProcess对象 QProcess *senderProcess = qobject_cast(sender()); if (senderProcess) { senderProcess->deleteLater(); } } void MainWindow::startOdd() { if (!isAuthenticated) { QMessageBox::warning(this, "未验证", "请先完成卡密验证"); return; } QString batPath = UPDATE_PATH + ""; if (!QFile::exists(batPath)) { QMessageBox::critical(this, "错误", "找不到ODD启动脚本: " + batPath); return; } // 启动bat文件 QProcess::startDetached("cmd.exe", QStringList() << "/c" << batPath, UPDATE_PATH); statusLabel->setText("正在启动ODD驱动程序..."); } void MainWindow::modifyHosts() { if (!isAuthenticated) { QMessageBox::warning(this, "未验证", "请先完成卡密验证"); return; } QString batPath = UPDATE_PATH + ""; if (!QFile::exists(batPath)) { QMessageBox::critical(this, "错误", "找不到hosts修改脚本: " + batPath); return; } // 启动bat文件 QProcess::startDetached("cmd.exe", QStringList() << "/c" << batPath, UPDATE_PATH); statusLabel->setText("正在修改hosts文件..."); } void MainWindow::forceUpdate() { if (!isAuthenticated) { QMessageBox::warning(this, "未验证", "请先完成卡密验证"); return; } statusLabel->setText("开始强制更新..."); fetchVersionForForceUpdate(); } void MainWindow::fetchVersionForForceUpdate() { QUrl url(SERVER_URL + VERSION_FILE); QNetworkRequest request(url); request.setRawHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"); QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone); request.setSslConfiguration(sslConfig); QNetworkReply *reply = networkManager->get(request); connect(reply, &QNetworkReply::finished, this, [=]() { if (reply->error() != QNetworkReply::NoError) { statusLabel->setText("连接服务器失败"); return; } QByteArray data = reply->readAll(); QJsonDocument doc = QJsonDocument::fromJson(data); if (doc.isNull()) { statusLabel->setText("版本信息解析错误"); return; } updateGame(doc.object()); reply->deleteLater(); }); } void MainWindow::openBuyPage() { QDesktopServices::openUrl(QUrl("https://m.tb.cn/h.hYesG5B?tk=qva9Vs7587S")); } void MainWindow::fetchAnnouncement() { QUrl url(SERVER_URL + "g/" + ANNOUNCEMENT_FILE); QNetworkRequest request(url); request.setRawHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"); QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone); request.setSslConfiguration(sslConfig); QNetworkReply *reply = networkManager->get(request); connect(reply, &QNetworkReply::finished, this, &MainWindow::onAnnouncementFetched); } void MainWindow::onAnnouncementFetched() { QNetworkReply *reply = qobject_cast(sender()); QJsonObject announcement; if (reply->error() == QNetworkReply::NoError) { QByteArray data = reply->readAll(); QJsonDocument doc = QJsonDocument::fromJson(data); if (!doc.isNull()) { announcement = doc.object(); } } if (announcement.isEmpty()) { announcement["title"] = "公告"; announcement["content"] = "无法连接到服务器获取最新公告。\n请检查网络连接或稍后再试。"; announcement["date"] = QDate::currentDate().toString("yyyy-MM-dd"); } updateAnnouncement(announcement); reply->deleteLater(); } void MainWindow::checkForUpdates() { if (!isAuthenticated) { statusLabel->setText("请先完成卡密验证"); return; } // 如果正在进行首次更新,则跳过常规更新检查 if (m_isFirstUpdateInProgress) { qDebug() << "跳过常规更新检查(首次更新进行中)"; return; } statusLabel->setText("正在检查更新..."); QUrl url(SERVER_URL + VERSION_FILE); QNetworkRequest request(url); request.setRawHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"); QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone); request.setSslConfiguration(sslConfig); QNetworkReply *reply = networkManager->get(request); connect(reply, &QNetworkReply::finished, this, &MainWindow::onVersionChecked); } void MainWindow::onVersionChecked() { QNetworkReply *reply = qobject_cast(sender()); if (reply->error() != QNetworkReply::NoError) { statusLabel->setText("连接服务器失败"); qDebug() << "连接服务器失败:" << reply->errorString(); return; } QByteArray data = reply->readAll(); QJsonDocument doc = QJsonDocument::fromJson(data); if (doc.isNull()) { statusLabel->setText("版本信息解析错误"); qDebug() << "版本信息解析错误"; return; } QJsonObject remoteVersion = doc.object(); QString remoteVer = remoteVersion["version"].toString(); QString localVer = localVersion["version"].toString(); qDebug() << "本地版本:" << localVer << "远程版本:" << remoteVer; int comparison = compareVersions(remoteVer, localVer); if (comparison <= 0) { statusLabel->setText("游戏已是最新版本"); versionLabel->setText("版本: v" + localVer); qDebug() << "游戏已是最新版本"; } else { statusLabel->setText("发现新版本 v" + remoteVer); versionLabel->setText("版本: v" + localVer + " → v" + remoteVer); qDebug() << "需要更新: 本地 v" << localVer << "-> 远程 v" << remoteVer; updateGame(remoteVersion); // 执行增量更新 } reply->deleteLater(); } void MainWindow::updateGame(const QJsonObject &remoteVersion) { if (remoteVersion.isEmpty()) { statusLabel->setText("无效的版本信息"); return; } QString remoteVer = remoteVersion["version"].toString(); QString localVer = localVersion["version"].toString(); // 检查下载URL是否存在 if (!remoteVersion.contains("url") || remoteVersion["url"].toString().isEmpty()) { statusLabel->setText("更新URL无效"); return; } QString updateUrl = remoteVersion["url"].toString(); disableButtons(); statusLabel->setText("正在下载增量更新..."); QUrl url(updateUrl); // 使用从JSON中获取的URL QNetworkRequest request(url); request.setRawHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"); QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone); request.setSslConfiguration(sslConfig); QNetworkReply *reply = networkManager->get(request); connect(reply, &QNetworkReply::downloadProgress, this, [=](qint64 bytesReceived, qint64 bytesTotal) { if (bytesTotal > 0) { int percent = static_cast((bytesReceived * 100) / bytesTotal); progressBar->setValue(percent); statusLabel->setText(QString("下载增量更新: %1%").arg(percent)); } }); connect(reply, &QNetworkReply::finished, this, [=]() { onUpdateDownloaded(reply, remoteVersion); }); } void MainWindow::onUpdateDownloaded(QNetworkReply *reply, const QJsonObject &version) { if (reply->error() != QNetworkReply::NoError) { statusLabel->setText("下载失败: " + reply->errorString()); qDebug() << "下载失败:" << reply->errorString(); activateButtons(); return; } QByteArray data = reply->readAll(); QFile file(UPDATE_ZIP); if (file.open(QIODevice::WriteOnly)) { file.write(data); file.close(); } else { qDebug() << "无法保存更新文件"; } statusLabel->setText("正在解压文件..."); progressBar->setValue(0); // 从版本信息中获取密码 QString password = version["password"].toString(); QFutureWatcher *watcher = new QFutureWatcher(this); connect(watcher, &QFutureWatcher::finished, this, [=]() { if (watcher->result()) { // 更新版本信息并保存 QJsonObject newLocalVersion; newLocalVersion["version"] = version["version"].toString(); if (version.contains("changelog")) { newLocalVersion["changelog"] = version["changelog"]; } if (version.contains("timestamp")) { newLocalVersion["timestamp"] = version["timestamp"]; } localVersion = newLocalVersion; saveLocalVersion(); // 重新加载本地版本以确保一致性 loadLocalVersion(); // 更新界面显示 versionLabel->setText("版本: v" + localVersion["version"].toString()); statusLabel->setText("更新完成!"); progressBar->setValue(100); QFile::remove(UPDATE_ZIP); QMessageBox::information(this, "更新完成", "游戏已成功更新到最新版本!"); qDebug() << "更新完成: v" << localVersion["version"].toString(); } else { statusLabel->setText("解压失败"); QMessageBox::critical(this, "更新失败", "解压更新包失败"); qDebug() << "解压失败"; } activateButtons(); watcher->deleteLater(); }); QFuture future = QtConcurrent::run([=]() { return extractZip(UPDATE_ZIP, UPDATE_PATH, password); }); watcher->setFuture(future); reply->deleteLater(); } void MainWindow::saveLocalVersion() { // 创建精简的版本对象 QJsonObject saveVersion; saveVersion["version"] = localVersion["version"].toString(); // 只保存必要的字段 if (localVersion.contains("changelog")) { saveVersion["changelog"] = localVersion["changelog"]; } if (localVersion.contains("timestamp")) { saveVersion["timestamp"] = localVersion["timestamp"]; } QFile file(UPDATE_PATH + "/" + VERSION_FILE); if (file.open(QIODevice::WriteOnly)) { QJsonDocument doc(saveVersion); file.write(doc.toJson()); file.close(); } } void MainWindow::selectPackagePath() { QString dir = QFileDialog::getExistingDirectory( this, tr("选择Package目录"), UPDATE_PATH, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks ); if (!dir.isEmpty()) { UPDATE_PATH = dir; pathLabel->setText(UPDATE_PATH); // 更新相关文件路径 BAT_FILE = UPDATE_PATH + "/2-Start.bat"; ODD_BAT_FILE = UPDATE_PATH + "/1-管理员运行odd.bat"; HOSTS_BAT = UPDATE_PATH + "/hosts.bat"; // 重新加载本地版本 loadLocalVersion(); checkPackageExists(); saveSettings(); } } void MainWindow::checkPackageExists() { QDir packageDir(UPDATE_PATH); bool exists = packageDir.exists(); startBtn->setEnabled(false); oddBtn->setEnabled(false); hostsBtn->setEnabled(false); wikiBtn->setEnabled(true); fullUpdateBtn->setEnabled(isAuthenticated); updateBtn->setEnabled(isAuthenticated); buyBtn->setEnabled(true); pathSelectBtn->setEnabled(true); if (!exists) { statusLabel->setText("警告: Package目录不存在!"); } else if (isAuthenticated) { startBtn->setEnabled(true); oddBtn->setEnabled(true); hostsBtn->setEnabled(true); } } void MainWindow::saveSettings() { settings->setValue("packagePath", UPDATE_PATH); settings->sync(); } void MainWindow::loadSettings() { if (settings->contains("packagePath")) { UPDATE_PATH = settings->value("packagePath").toString(); } else { UPDATE_PATH = "Package"; } BAT_FILE = UPDATE_PATH + "/2-Start.bat"; ODD_BAT_FILE = UPDATE_PATH + "/1-管理员运行odd.bat"; HOSTS_BAT = UPDATE_PATH + "/hosts.bat"; } // 修改 getDeviceId 函数 QString MainWindow::getDeviceId() { ensureDataDirExists(); // 确定数据目录路径 QString dataDir = "D:/maimaiLauncherData"; QDir dDrive("D:/"); if (!dDrive.exists()) { dataDir = "C:/maimaiLauncherData"; } DEVICE_CODE_FILE = dataDir + "/device_code.dat"; QFile file(DEVICE_CODE_FILE); if (file.exists() && file.open(QIODevice::ReadOnly)) { QString id = QString::fromUtf8(file.readAll()).trimmed(); file.close(); if (!id.isEmpty()) return id; } QString deviceInfo = ""; HKEY hKey; if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &hKey) == ERROR_SUCCESS) { wchar_t cpuName[256]; DWORD size = sizeof(cpuName); DWORD type; if (RegQueryValueEx(hKey, L"ProcessorNameString", NULL, &type, (LPBYTE)cpuName, &size) == ERROR_SUCCESS) { deviceInfo += QString::fromWCharArray(cpuName); } RegCloseKey(hKey); } if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"HARDWARE\\DEVICEMAP\\Scsi\\Scsi Port 0\\Scsi Bus 0\\Target Id 0\\Logical Unit Id 0", 0, KEY_READ, &hKey) == ERROR_SUCCESS) { wchar_t diskId[256]; DWORD size = sizeof(diskId); DWORD type; if (RegQueryValueEx(hKey, L"SerialNumber", NULL, &type, (LPBYTE)diskId, &size) == ERROR_SUCCESS) { deviceInfo += QString::fromWCharArray(diskId); } RegCloseKey(hKey); } QCryptographicHash hash(QCryptographicHash::Sha256); hash.addData(deviceInfo.toUtf8()); QString deviceId = hash.result().toHex().left(32); if (file.open(QIODevice::WriteOnly)) { file.write(deviceId.toUtf8()); file.close(); const wchar_t* path = reinterpret_cast(DEVICE_CODE_FILE.utf16()); DWORD attributes = GetFileAttributesW(path); if (attributes != INVALID_FILE_ATTRIBUTES) { SetFileAttributesW(path, attributes | FILE_ATTRIBUTE_HIDDEN); } } return deviceId; } QString MainWindow::loadSavedKami() { ensureDataDirExists(); // 确定数据目录路径 QString dataDir = "D:/maimaiLauncherData"; QDir dDrive("D:/"); if (!dDrive.exists()) { dataDir = "C:/maimaiLauncherData"; } CARD_FILE = dataDir + "/card.dat"; QFile file(CARD_FILE); if (file.exists() && file.open(QIODevice::ReadOnly)) { QString kami = QString::fromUtf8(file.readAll()).trimmed(); file.close(); return kami; } return ""; } bool MainWindow::saveKami(const QString &kami) { ensureDataDirExists(); // 确定数据目录路径 QString dataDir = "D:/maimaiLauncherData"; QDir dDrive("D:/"); if (!dDrive.exists()) { dataDir = "C:/maimaiLauncherData"; } CARD_FILE = dataDir + "/card.dat"; QFile file(CARD_FILE); if (file.open(QIODevice::WriteOnly)) { file.write(kami.toUtf8()); file.close(); const wchar_t* path = reinterpret_cast(CARD_FILE.utf16()); DWORD attributes = GetFileAttributesW(path); if (attributes != INVALID_FILE_ATTRIBUTES) { SetFileAttributesW(path, attributes | FILE_ATTRIBUTE_HIDDEN); } return true; } return false; } bool MainWindow::clearSavedKami() { // 确定数据目录路径 QString dataDir = "D:/maimaiLauncherData"; QDir dDrive("D:/"); if (!dDrive.exists()) { dataDir = "C:/maimaiLauncherData"; } CARD_FILE = dataDir + "/card.dat"; QFile file(CARD_FILE); return file.exists() ? file.remove() : true; } void MainWindow::showAuthWindow() { if (authWindow) { authWindow->deleteLater(); } authWindow = new AuthWindow(deviceId, savedKami, this); if (authWindow->exec() == QDialog::Accepted) { QString kami = authWindow->getKami(); bool remember = authWindow->getRemember(); if (!kami.isEmpty()) { authStatus->setText("验证中..."); performNetworkAuthentication(kami, remember); } } else { authStatus->setText("验证已取消"); QMessageBox::critical(this, "验证取消", "您必须完成验证才能使用启动器。\n程序将在5秒后关闭..."); quitTimer->start(5000); } } void MainWindow::performNetworkAuthentication(const QString &kami, bool remember) { QUrl url(AUTH_API); QUrlQuery query; query.addQueryItem("api", "kmlogon"); query.addQueryItem("app", APP_ID); query.addQueryItem("kami", kami); query.addQueryItem("markcode", deviceId); url.setQuery(query); QNetworkRequest request(url); request.setRawHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"); QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone); request.setSslConfiguration(sslConfig); QNetworkReply *reply = networkManager->get(request); connect(reply, &QNetworkReply::finished, this, [=]() { QString errorMsg; QString vipExpiry; bool success = false; if (reply->error() == QNetworkReply::NoError) { QByteArray data = reply->readAll(); QJsonDocument doc = QJsonDocument::fromJson(data); if (!doc.isNull() && doc.isObject()) { QJsonObject json = doc.object(); int code = json["code"].toInt(-1); if (json.contains("code")) { if (code == 200) { if (json.contains("msg") && json["msg"].isObject()) { QJsonObject msg = json["msg"].toObject(); if (msg.contains("vip")) { vipExpiry = msg["vip"].toString(); success = true; errorMsg = "验证成功"; } else { errorMsg = "服务器响应缺少vip字段"; } } else { errorMsg = "服务器响应格式错误"; } } else { QMap errorMap = { {101, "应用不存在 (101)"}, {102, "应用已关闭 (102)"}, {171, "接口维护中 (171)"}, {172, "接口未添加或不存在 (172)"}, {104, "签名为空 (104)"}, {105, "数据过期 (105)"}, {106, "签名有误 (106)"}, {148, "卡密为空 (148)"}, {149, "卡密不存在 (149)"}, {150, "已使用 (150)"}, {151, "卡密禁用 (151)"}, {169, "IP不一致 (169)"} }; errorMsg = errorMap.value(code, "未知错误 (代码: " + QString::number(code) + ")"); } } else { errorMsg = "服务器响应缺少code字段"; } } else { errorMsg = "响应解析错误: " + data; } } else { errorMsg = "网络错误: " + reply->errorString() + " (代码: " + QString::number(reply->error()) + ")"; } reply->deleteLater(); onAuthenticationFinished(kami, remember, success, errorMsg, vipExpiry); }); } void MainWindow::onAuthenticationFinished(const QString &kami, bool remember, bool success, const QString &message, const QString &vipExpiry) { authStatus->setText(message); if (success) { isAuthenticated = true; QDateTime expireTime = QDateTime::fromSecsSinceEpoch(vipExpiry.toLongLong()); QString expireStr = expireTime.toString("yyyy-MM-dd HH:mm:ss"); vipInfo->setText("VIP到期: " + expireStr); if (remember) { if (saveKami(kami)) { savedKami = kami; } else { authStatus->setText(authStatus->text() + " (保存卡密失败)"); } } else { clearSavedKami(); savedKami = ""; } // 如果是首次启动 if (isFirstLaunch) { // 提示选择Package目录 QMessageBox::information(this, "首次启动", "请选择游戏Package目录"); selectPackagePath(); // 提示首次更新 - 使用update_f.json if (QMessageBox::question(this, "首次启动", "检测到第一次启动,是否立即进行首次更新?", QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { statusLabel->setText("开始首次更新..."); fetchFirstUpdateVersion(); // 调用首次更新函数 } else { // 用户选择不进行首次更新,直接检查常规更新 checkForUpdates(); // 新增:非首次启动时检查更新 } isFirstLaunch = false; // 标记已处理首次启动 } else { // 非首次启动,直接检查常规更新 checkForUpdates(); // 新增:非首次启动时检查更新 } activateButtons(); fullUpdateBtn->setEnabled(true); checkPackageExists(); checkLauncherVersion(); // 检查启动器版本 checkAndDeleteFiles(); } else { isAuthenticated = false; vipInfo->setText("VIP状态: 验证失败"); clearSavedKami(); savedKami = ""; disableButtons(); QMessageBox::critical(this, "验证失败", "验证失败: " + message + "\n程序将在5秒后关闭..."); quitTimer->start(5000); } } void MainWindow::checkAndDeleteFiles() { QUrl url(SERVER_URL + "delete.json"); QNetworkRequest request(url); request.setRawHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"); QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone); request.setSslConfiguration(sslConfig); QNetworkReply *reply = networkManager->get(request); connect(reply, &QNetworkReply::finished, this, [=]() { if (reply->error() != QNetworkReply::NoError) { qDebug() << "无法获取删除列表:" << reply->errorString(); return; } QByteArray data = reply->readAll(); QJsonDocument doc = QJsonDocument::fromJson(data); if (doc.isNull() || !doc.isArray()) { qDebug() << "删除列表格式错误"; return; } QJsonArray filesToDelete = doc.array(); processDeleteList(filesToDelete); reply->deleteLater(); }); } void MainWindow::processDeleteList(const QJsonArray &filesToDelete) { int deletedCount = 0; int failedCount = 0; for (const QJsonValue &value : filesToDelete) { QString relativePath = value.toString(); if (relativePath.isEmpty()) continue; QString fullPath = UPDATE_PATH + "/" + relativePath; QFile file(fullPath); if (file.exists()) { // 如果是只读文件,先取消只读属性 const wchar_t* wPath = reinterpret_cast(fullPath.utf16()); DWORD attrs = GetFileAttributesW(wPath); if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_READONLY)) { SetFileAttributesW(wPath, attrs & ~FILE_ATTRIBUTE_READONLY); } if (file.remove()) { qDebug() << "已删除文件:" << fullPath; deletedCount++; } else { qDebug() << "删除失败:" << fullPath << file.errorString(); failedCount++; } } } if (deletedCount > 0 || failedCount > 0) { qDebug() << "删除操作完成: 成功删除" << deletedCount << "个文件," << failedCount << "个文件删除失败"; } } void MainWindow::fetchFirstUpdateVersion() { m_isFirstUpdateInProgress = true; // 标记首次更新开始 QUrl url(SERVER_URL + UPDATE_F_VERSION_FILE); QNetworkRequest request(url); request.setRawHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"); QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone); request.setSslConfiguration(sslConfig); QNetworkReply *reply = networkManager->get(request); connect(reply, &QNetworkReply::finished, this, [=]() { if (reply->error() != QNetworkReply::NoError) { statusLabel->setText("首次更新: 连接服务器失败"); m_isFirstUpdateInProgress = false; return; } QByteArray data = reply->readAll(); QJsonDocument doc = QJsonDocument::fromJson(data); if (doc.isNull()) { statusLabel->setText("首次更新: 版本信息解析错误"); m_isFirstUpdateInProgress = false; return; } QJsonObject remoteVersion = doc.object(); QString remoteVer = remoteVersion["version"].toString(); statusLabel->setText("首次更新: 下载完整包 " + remoteVer); // 使用新的文件名 QString FULL_UPDATE_ZIP = "update_f.zip"; // 获取完整包URL QString updateUrl = remoteVersion["url"].toString(); if (updateUrl.isEmpty()) { statusLabel->setText("首次更新: URL无效"); m_isFirstUpdateInProgress = false; return; } // 从版本信息中获取密码 QString password = remoteVersion["password"].toString(); // 下载完整包 QUrl fullUrl(updateUrl); QNetworkRequest fullRequest(fullUrl); fullRequest.setRawHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"); fullRequest.setSslConfiguration(sslConfig); QNetworkReply *downloadReply = networkManager->get(fullRequest); connect(downloadReply, &QNetworkReply::downloadProgress, this, [=](qint64 bytesReceived, qint64 bytesTotal) { if (bytesTotal > 0) { int percent = static_cast((bytesReceived * 100) / bytesTotal); progressBar->setValue(percent); statusLabel->setText(QString("下载完整包: %1%").arg(percent)); } }); connect(downloadReply, &QNetworkReply::finished, this, [=]() { if (downloadReply->error() != QNetworkReply::NoError) { statusLabel->setText("完整包下载失败: " + downloadReply->errorString()); m_isFirstUpdateInProgress = false; return; } // 保存完整包 QByteArray fullData = downloadReply->readAll(); QFile fullFile(FULL_UPDATE_ZIP); if (fullFile.open(QIODevice::WriteOnly)) { fullFile.write(fullData); fullFile.close(); } statusLabel->setText("正在解压完整包..."); progressBar->setValue(0); QFutureWatcher *watcher = new QFutureWatcher(this); connect(watcher, &QFutureWatcher::finished, this, [=]() { if (watcher->result()) { // 更新版本信息并保存 QJsonObject newLocalVersion; newLocalVersion["version"] = remoteVersion["version"].toString(); if (remoteVersion.contains("changelog")) { newLocalVersion["changelog"] = remoteVersion["changelog"]; } if (remoteVersion.contains("timestamp")) { newLocalVersion["timestamp"] = remoteVersion["timestamp"]; } localVersion = newLocalVersion; saveLocalVersion(); // 更新界面显示 versionLabel->setText("版本: v" + remoteVer); statusLabel->setText("首次更新完成!"); progressBar->setValue(100); QFile::remove(FULL_UPDATE_ZIP); QMessageBox::information(this, "首次更新完成", "游戏已成功安装完整包!"); // 标记首次更新完成 m_isFirstUpdateInProgress = false; // 立即执行一次增量更新检查 statusLabel->setText("检查增量更新..."); checkForUpdates(); } else { statusLabel->setText("解压完整包失败"); m_isFirstUpdateInProgress = false; } watcher->deleteLater(); }); QFuture future = QtConcurrent::run([=]() { return extractZip(FULL_UPDATE_ZIP, UPDATE_PATH, password); }); watcher->setFuture(future); downloadReply->deleteLater(); }); reply->deleteLater(); }); } // 检查启动器版本 void MainWindow::checkLauncherVersion() { QUrl url(SERVER_URL + ""); QNetworkRequest request(url); request.setRawHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"); QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone); request.setSslConfiguration(sslConfig); QNetworkReply *reply = networkManager->get(request); connect(reply, &QNetworkReply::finished, this, [=]() { if (reply->error() != QNetworkReply::NoError) { // 无法连接服务器,弹窗提示并闪退 QMessageBox::critical(nullptr, "网络错误", "无法连接服务器,启动器即将关闭"); QTimer::singleShot(0, this, &MainWindow::quitApplication); return; } QByteArray data = reply->readAll(); QJsonDocument doc = QJsonDocument::fromJson(data); if (doc.isNull() || !doc.isObject()) { // 数据解析错误,不退出 qDebug() << "启动器版本信息解析错误"; return; } QJsonObject remoteData = doc.object(); QString remoteVersion = remoteData["version"].toString(); QString downloadUrl = remoteData["url"].toString(); // 保留但不再使用 if (compareVersions(remoteVersion, LAUNCHER_VERSION) > 0) { // 当前版本过旧 QMessageBox msgBox; msgBox.setWindowTitle("启动器版本过旧"); msgBox.setText(QString("发现新版本启动器 v%1,当前版本 v%2。请下载最新版本启动器。\n程序将在5秒后关闭。").arg(remoteVersion).arg(LAUNCHER_VERSION)); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.exec(); // 5秒后退出 QTimer::singleShot(5000, this, &MainWindow::quitApplication); } reply->deleteLater(); }); } void MainWindow::quitApplication() { QApplication::quit(); }