#include "mainwindow.h" #include "ui_mainwindow.h" // Подключаем сгенерированный заголовочный файл #include "settingsdialog.h" #include "profiledialog.h" // Подключаем новый диалог #include #include #include #include #include // Для диалога выбора #include // Для проверки файла #include // Для работы с JSON #include // Для работы с JSON #include // Для работы с JSON #include // Для работы с JSON #include // Для генерации ID профиля #include // Для поиска папки с данными 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))); // Загружаем профили при старте profilesPath = "/launcher_profiles.json"; loadProfiles(); } 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::loadProfiles() { QFile profilesFile(profilesPath); if (profilesFile.exists() && profilesFile.open(QIODevice::ReadOnly)) { QJsonDocument doc = QJsonDocument::fromJson(profilesFile.readAll()); profilesData = doc.object(); profilesFile.close(); } else { // Создаем базовую структуру, если файла нет profilesData = QJsonObject({{"profiles", QJsonObject()}}); } QJsonObject profiles = profilesData["profiles"].toObject(); ui->profileComboBox->clear(); if (profiles.isEmpty()) { // Если профилей нет, принудительно открываем диалог создания QMessageBox::information(this, "Настройка", "Не найдено ни одного профиля. Давайте создадим новый."); createNewProfile(); return; // createNewProfile вызовет loadProfiles() повторно } // Заполняем ComboBox for (const QString &key : profiles.keys()) { QJsonObject profile = profiles[key].toObject(); ui->profileComboBox->addItem(profile["name"].toString(), key); // Имя и UUID } } void MainWindow::saveProfiles() { QFile profilesFile(profilesPath); if (profilesFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { QJsonDocument doc(profilesData); profilesFile.write(doc.toJson(QJsonDocument::Indented)); profilesFile.close(); } else { QMessageBox::critical(this, "Ошибка", "Не удалось сохранить файл профилей: " + profilesFile.errorString()); } } void MainWindow::on_editProfileButton_clicked() { // 1. Проверяем, выбран ли профиль QString currentProfileId = ui->profileComboBox->currentData().toString(); if (currentProfileId.isEmpty()) { QMessageBox::warning(this, "Нет профиля", "Пожалуйста, выберите профиль для редактирования."); return; } // 2. Получаем данные текущего профиля QJsonObject profiles = profilesData["profiles"].toObject(); QJsonObject currentProfile = profiles[currentProfileId].toObject(); // 3. Создаем диалог и заполняем его данными ProfileDialog dialog(this); dialog.setWindowTitle("Редактирование профиля"); dialog.setProfileName(currentProfile["name"].toString()); dialog.setJavaPath(currentProfile["javaDir"].toString()); // 4. Показываем диалог и обрабатываем результат if (dialog.exec() == QDialog::Accepted) { if (dialog.profileName().isEmpty() || dialog.javaPath().isEmpty()) { QMessageBox::warning(this, "Ошибка", "Имя профиля и путь к Java не могут быть пустыми."); return; } // 5. Обновляем данные в нашем JSON объекте currentProfile["name"] = dialog.profileName(); currentProfile["javaDir"] = dialog.javaPath(); // 6. Записываем обновленный профиль обратно profiles[currentProfileId] = currentProfile; profilesData["profiles"] = profiles; // 7. Сохраняем и перезагружаем список saveProfiles(); // Сохраняем ID, чтобы восстановить выбор после перезагрузки QString previouslySelectedId = currentProfileId; loadProfiles(); int indexToSelect = ui->profileComboBox->findData(previouslySelectedId); if (indexToSelect != -1) { ui->profileComboBox->setCurrentIndex(indexToSelect); } } } void MainWindow::createNewProfile() { ProfileDialog dialog(this); if (dialog.exec() == QDialog::Accepted) { if (dialog.profileName().isEmpty() || dialog.javaPath().isEmpty()) { QMessageBox::warning(this, "Ошибка", "Имя профиля и путь к Java не могут быть пустыми."); return; } QString uuid = QUuid::createUuid().toString(QUuid::WithoutBraces); QJsonObject newProfile; newProfile["name"] = dialog.profileName(); newProfile["javaDir"] = dialog.javaPath(); newProfile["lastVersionId"] = ""; newProfile["created"] = QDateTime::currentDateTime().toString(Qt::ISODate); newProfile["lastUsed"] = QDateTime::currentDateTime().toString(Qt::ISODate); newProfile["icon"] = "Bedrock"; newProfile["type"] = "custom"; QJsonObject profiles = profilesData["profiles"].toObject(); profiles[uuid] = newProfile; profilesData["profiles"] = profiles; saveProfiles(); loadProfiles(); // Перезагружаем список профилей } } void MainWindow::on_addProfileButton_clicked() { createNewProfile(); } void MainWindow::on_profileComboBox_currentIndexChanged(int index) { // Пока ничего не делаем, но слот полезен для будущего Q_UNUSED(index); } void MainWindow::on_launchButton_clicked() { QString currentProfileId = ui->profileComboBox->currentData().toString(); if (currentProfileId.isEmpty()) { QMessageBox::critical(this, "Ошибка", "Не выбран профиль для запуска!"); return; } QJsonObject profile = profilesData["profiles"].toObject()[currentProfileId].toObject(); QString javaPath = profile["javaDir"].toString(); if (!QFileInfo::exists(javaPath)) { QMessageBox::critical(this, "Ошибка", "Не найден исполняемый файл Java, указанный в профиле:\n" + javaPath); return; } // --- 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; }