#include "mainwindow.h" #include "ui_mainwindow.h" // Подключаем сгенерированный заголовочный файл #include "settingsdialog.h" #include #include #include #include #include // Для диалога выбора #include // Для проверки файла #include // Для работы с JSON #include // Для работы с JSON #include // Для работы с JSON #include // Для работы с JSON MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) // Инициализируем указатель на UI { ui->setupUi(this); // Загружаем интерфейс из .ui файла // Начальная настройка виджетов ui->progressBar->setRange(0, 0); // Неопределенный прогресс-бар ui->progressBar->setVisible(false); // Инициализация логики settings = new QSettings("GaleonDev", "MinecraftLauncher", this); process = new QProcess(this); // Подключаем сигналы от процесса (это делается вручную) connect(process, &QProcess::readyReadStandardOutput, this, &MainWindow::readProcessOutput); connect(process, &QProcess::readyReadStandardError, this, &MainWindow::readProcessOutput); connect(process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onProcessFinished(int, QProcess::ExitStatus))); connect(process, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(onProcessError(QProcess::ProcessError))); } MainWindow::~MainWindow() { delete ui; // Освобождаем память от объекта UI } // Вспомогательная функция для проверки правил OS bool checkRules(const QJsonObject &item) { if (!item.contains("rules")) { return true; // Нет правил - разрешено для всех. } // Если правила есть, по умолчанию запрещаем, пока не найдем разрешающее правило. bool isAllowed = false; QString currentOs; #if defined(Q_OS_WIN) currentOs = "windows"; #elif defined(Q_OS_MAC) currentOs = "osx"; #elif defined(Q_OS_LINUX) currentOs = "linux"; #endif QJsonArray rules = item["rules"].toArray(); for (const QJsonValue &value : rules) { QJsonObject rule = value.toObject(); QString action = rule["action"].toString(); bool conditionMet = false; if (rule.contains("os")) { QJsonObject osRule = rule["os"].toObject(); if (osRule.contains("name") && osRule["name"].toString() == currentOs) { conditionMet = true; } } else { // Правило без указания ОС применяется ко всем системам. conditionMet = true; } if (conditionMet) { if (action == "allow") { isAllowed = true; } else if (action == "disallow") { isAllowed = false; } } } return isAllowed; } void MainWindow::on_launchButton_clicked() { // --- 1. Выбор версии (остается без изменений) --- QString minecraftPath = getMinecraftPath(); if (minecraftPath.isEmpty()) { QMessageBox::warning(this, "Ошибка", "Не удалось найти директорию .minecraft."); return; } QString versionsPath = QDir::toNativeSeparators(minecraftPath + "/versions"); QDir versionsDir(versionsPath); if (!versionsDir.exists()) { QMessageBox::warning(this, "Ошибка", "Папка 'versions' не найдена!"); return; } QStringList versionList = versionsDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); if (versionList.isEmpty()) { QMessageBox::warning(this, "Ошибка", "Не найдено ни одной установленной версии Minecraft."); return; } bool ok; QString selectedVersion = QInputDialog::getItem(this, "Выбор версии", "Выберите версию Minecraft для запуска:", versionList, 0, false, &ok); if (!ok || selectedVersion.isEmpty()) { return; // Пользователь отменил выбор } // --- 2. Чтение и парсинг JSON файла версии --- QString jsonPath = QDir::toNativeSeparators(versionsPath + "/" + selectedVersion + "/" + selectedVersion + ".json"); QFile jsonFile(jsonPath); if (!jsonFile.open(QIODevice::ReadOnly)) { QMessageBox::critical(this, "Ошибка", "Не удалось открыть .json файл для версии " + selectedVersion); return; } QByteArray jsonData = jsonFile.readAll(); jsonFile.close(); QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData); if (jsonDoc.isNull()) { QMessageBox::critical(this, "Ошибка", "Некорректный .json файл для версии " + selectedVersion); return; } QJsonObject rootObj = jsonDoc.object(); // --- 3. Формирование Classpath --- QStringList classpathEntries; QString librariesPath = QDir::toNativeSeparators(minecraftPath + "/libraries"); QJsonArray libraries = rootObj["libraries"].toArray(); for (const QJsonValue &value : libraries) { QJsonObject library = value.toObject(); if (!checkRules(library)) { continue; // Пропускаем библиотеку, если она не для этой ОС } QJsonObject downloads = library["downloads"].toObject(); if(downloads.isEmpty()){ // Для Fabric Loader и некоторых других QString name = library["name"].toString(); QStringList parts = name.split(':'); QString path = parts[0].replace('.', '/') + "/" + parts[1] + "/" + parts[2] + "/" + parts[1] + "-" + parts[2] + ".jar"; classpathEntries << QDir::toNativeSeparators(librariesPath + "/" + path); } else { QJsonObject artifact = downloads["artifact"].toObject(); QString path = artifact["path"].toString(); classpathEntries << QDir::toNativeSeparators(librariesPath + "/" + path); } } // Добавляем сам .jar файл игры в classpath classpathEntries << QDir::toNativeSeparators(versionsPath + "/" + selectedVersion + "/" + selectedVersion + ".jar"); // Определяем разделитель для classpath в зависимости от ОС QString pathSeparator; #if defined(Q_OS_WIN) pathSeparator = ";"; #else pathSeparator = ":"; #endif QString classpath = classpathEntries.join(pathSeparator); // --- 4. Сборка аргументов --- QStringList jvmArgs; QStringList gameArgs; // Аргументы для JVM QJsonObject argumentsObj = rootObj["arguments"].toObject(); QJsonArray jvmArgsArray = argumentsObj["jvm"].toArray(); for (const QJsonValue &value : jvmArgsArray) { if (value.isString()) { jvmArgs << value.toString(); } else if (value.isObject()) { if (checkRules(value.toObject())) { QJsonArray values = value.toObject()["values"].toArray(); for(const QJsonValue &v : values){ jvmArgs << v.toString(); } } } } // Аргументы для игры QJsonArray gameArgsArray = argumentsObj["game"].toArray(); for (const QJsonValue &value : gameArgsArray) { if (value.isString()) { gameArgs << value.toString(); } else if (value.isObject()) { if (checkRules(value.toObject())) { QJsonArray values = value.toObject()["values"].toArray(); for(const QJsonValue &v : values){ gameArgs << v.toString(); } } } } // --- 5. Подстановка значений в плейсхолдеры --- QString nativesPath = QDir::toNativeSeparators(versionsPath + "/" + selectedVersion + "/natives"); QDir(nativesPath).mkpath("."); // Создаем папку для нативных библиотек, если ее нет // Заменители для JVM аргументов for (QString &arg : jvmArgs) { arg.replace("${natives_directory}", nativesPath); arg.replace("${launcher_name}", "CustomLauncher"); arg.replace("${launcher_version}", "1.0"); arg.replace("${classpath}", classpath); } // Заменители для игровых аргументов (используем базовые значения) for (QString &arg : gameArgs) { arg.replace("${auth_player_name}", "Player"); arg.replace("${version_name}", selectedVersion); arg.replace("${game_directory}", minecraftPath); arg.replace("${assets_root}", QDir::toNativeSeparators(minecraftPath + "/assets")); arg.replace("${assets_index_name}", rootObj["assetIndex"].toObject()["id"].toString()); arg.replace("${auth_uuid}", "00000000-0000-0000-0000-000000000000"); arg.replace("${auth_access_token}", "0"); arg.replace("${clientid}", "N/A"); arg.replace("${auth_xuid}", "N/A"); arg.replace("${user_type}", "msa"); arg.replace("${version_type}", rootObj["type"].toString()); } // --- 6. Финальная сборка и запуск --- QString mainClass = rootObj["mainClass"].toString(); QString javaPath = "java"; QStringList finalArguments; finalArguments << jvmArgs << mainClass << gameArgs; ui->logOutput->appendPlainText("Запуск Minecraft версии: " + selectedVersion); ui->logOutput->appendPlainText("Главный класс: " + mainClass); // Для отладки можно вывести всю команду ui->logOutput->appendPlainText("Команда: " + javaPath + " " + finalArguments.join(" ")); process->start(javaPath, finalArguments); ui->progressBar->setVisible(true); } void MainWindow::on_modsFolderButton_clicked() { QString minecraftPath = getMinecraftPath(); if (minecraftPath.isEmpty()) { QMessageBox::warning(this, "Ошибка", "Не удалось найти директорию .minecraft."); return; } QString modsPath = minecraftPath + "/mods"; QDesktopServices::openUrl(QUrl::fromLocalFile(modsPath)); } void MainWindow::on_settingsButton_clicked() { SettingsDialog dialog(this); dialog.exec(); } void MainWindow::on_updateModsButton_clicked() { QString gitRepoUrl = settings->value("gitRepoUrl").toString(); if (gitRepoUrl.isEmpty()) { QMessageBox::information(this, "Настройки", "Пожалуйста, укажите URL Git-репозитория в настройках."); return; } QString minecraftPath = getMinecraftPath(); if (minecraftPath.isEmpty()) { QMessageBox::warning(this, "Ошибка", "Не удалось найти директорию .minecraft."); return; } QString modsPath = minecraftPath + "/mods"; QDir modsDir(modsPath); if (!modsDir.exists(".git")) { ui->logOutput->appendPlainText("Клонирование модов из " + gitRepoUrl); process->start("git", QStringList() << "clone" << gitRepoUrl << modsPath); } else { ui->logOutput->appendPlainText("Обновление модов..."); process->setWorkingDirectory(modsPath); process->start("git", QStringList() << "pull"); } ui->progressBar->setVisible(true); } void MainWindow::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { ui->progressBar->setVisible(false); if (exitStatus == QProcess::CrashExit) { ui->logOutput->appendPlainText("Процесс завершился с ошибкой."); } else { ui->logOutput->appendPlainText("Процесс успешно завершен с кодом " + QString::number(exitCode)); } } void MainWindow::onProcessError(QProcess::ProcessError error) { ui->progressBar->setVisible(false); ui->logOutput->appendPlainText("Ошибка запуска процесса: " + process->errorString()); } void MainWindow::readProcessOutput() { ui->logOutput->appendPlainText(process->readAllStandardOutput()); ui->logOutput->appendPlainText(process->readAllStandardError()); } QString MainWindow::getMinecraftPath() { QString path; #if defined(Q_OS_WIN) path = QDir::homePath() + "/AppData/Roaming/.minecraft"; #elif defined(Q_OS_MAC) path = QDir::homePath() + "/Library/Application Support/minecraft"; #else path = QDir::homePath() + "/.minecraft"; #endif return path; }