From 3be6cca9299f00b892e7840af5d6380a7cb8df9d Mon Sep 17 00:00:00 2001 From: galeon Date: Sat, 25 Oct 2025 15:49:45 +0300 Subject: [PATCH] =?UTF-8?q?=D0=92=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20=D1=81?= =?UTF-8?q?=20=D1=83=D0=BF=D1=80=D0=BE=D1=89=D1=91=D0=BD=D0=BD=D1=8B=D0=BC?= =?UTF-8?q?=20GUI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 43 ++++++++++++ main.cpp | 10 +++ mainwindow.cpp | 170 +++++++++++++++++++++++++++++++++++++++++++++ mainwindow.h | 41 +++++++++++ mainwindow.ui | 68 ++++++++++++++++++ settingsdialog.cpp | 27 +++++++ settingsdialog.h | 27 +++++++ settingsdialog.ui | 74 ++++++++++++++++++++ 8 files changed, 460 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 main.cpp create mode 100644 mainwindow.cpp create mode 100644 mainwindow.h create mode 100644 mainwindow.ui create mode 100644 settingsdialog.cpp create mode 100644 settingsdialog.h create mode 100644 settingsdialog.ui diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3939dff --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,43 @@ +cmake_minimum_required(VERSION 3.19) +project(minecraft-launcher LANGUAGES CXX) + +find_package(Qt6 6.5 REQUIRED COMPONENTS Core Widgets) + +qt_standard_project_setup() + +qt_add_executable(minecraft-launcher + WIN32 MACOSX_BUNDLE + + + + + main.cpp + mainwindow.h mainwindow.cpp + settingsdialog.h settingsdialog.cpp + settingsdialog.ui + mainwindow.ui + + + +) + +target_link_libraries(minecraft-launcher + PRIVATE + Qt::Core + Qt::Widgets +) + +include(GNUInstallDirs) + +install(TARGETS minecraft-launcher + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET minecraft-launcher + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR +) +install(SCRIPT ${deploy_script}) diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..115a22b --- /dev/null +++ b/main.cpp @@ -0,0 +1,10 @@ +#include "mainwindow.h" +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..81b31b6 --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,170 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" // Подключаем сгенерированный заголовочный файл +#include "settingsdialog.h" + +#include +#include +#include +#include +#include // Для диалога выбора +#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))); +} + +MainWindow::~MainWindow() +{ + delete ui; // Освобождаем память от объекта UI +} + +void MainWindow::on_launchButton_clicked() +{ + QString minecraftPath = getMinecraftPath(); + if (minecraftPath.isEmpty()) { + QMessageBox::warning(this, "Ошибка", "Не удалось найти директорию .minecraft."); + return; + } + + // 1. Находим папку с версиями + QString versionsPath = minecraftPath + "/versions"; + QDir versionsDir(versionsPath); + if (!versionsDir.exists()) { + QMessageBox::warning(this, "Ошибка", "Папка 'versions' не найдена!"); + return; + } + + // 2. Получаем список всех папок внутри 'versions' (это и есть наши версии) + QStringList versionList = versionsDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + if (versionList.isEmpty()) { + QMessageBox::warning(this, "Ошибка", "Не найдено ни одной установленной версии Minecraft."); + return; + } + + // 3. Предлагаем пользователю выбрать версию + bool ok; + QString selectedVersion = QInputDialog::getItem(this, "Выбор версии", + "Выберите версию Minecraft для запуска:", + versionList, 0, false, &ok); + + // 4. Если пользователь сделал выбор (нажал "OK" и версия не пустая) + if (ok && !selectedVersion.isEmpty()) { + // Формируем путь к .jar файлу. Например: /.minecraft/versions/1.19.2/1.19.2.jar + QString jarPath = versionsPath + "/" + selectedVersion + "/" + selectedVersion + ".jar"; + + // Проверяем, что .jar файл действительно существует + QFileInfo jarFile(jarPath); + if (!jarFile.exists() || !jarFile.isFile()) { + QMessageBox::critical(this, "Ошибка запуска", + "Не удалось найти запускаемый .jar файл для версии " + selectedVersion + ".\n" + "Проверьте целостность файлов игры.\n" + "Ожидаемый путь: " + jarPath); + return; + } + + // 5. Запускаем игру + QString javaPath = "java"; // Java должна быть в системной переменной PATH + QStringList arguments; + arguments << "-jar" << jarPath << "net.fabricmc.loader.impl.launch.knot.KnotClient"; + + ui->logOutput->appendPlainText("Запуск Minecraft версии: " + selectedVersion); + process->start(javaPath, arguments); + 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; +} diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..0bce635 --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,41 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include + +// Предварительное объявление класса, сгенерированного из mainwindow.ui +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private slots: + // Слоты для кнопок (автоматически подключаются по имени: on_<имя_объекта>_<сигнал>) + void on_launchButton_clicked(); + void on_modsFolderButton_clicked(); + void on_updateModsButton_clicked(); + void on_settingsButton_clicked(); + + // Слоты для QProcess (подключаются вручную) + void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); + void onProcessError(QProcess::ProcessError error); + void readProcessOutput(); + +private: + QString getMinecraftPath(); + + Ui::MainWindow *ui; // Указатель на объект интерфейса + QProcess *process; + QSettings *settings; +}; + +#endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..9cce9c2 --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,68 @@ + + + MainWindow + + + + 0 + 0 + 600 + 400 + + + + Minecraft Лаунчер + + + + + + + Запустить Minecraft + + + + + + + Открыть папку с модами + + + + + + + Обновить моды из Git + + + + + + + Настройки + + + + + + + 0 + + + false + + + + + + + true + + + + + + + + + \ No newline at end of file diff --git a/settingsdialog.cpp b/settingsdialog.cpp new file mode 100644 index 0000000..d32c6f3 --- /dev/null +++ b/settingsdialog.cpp @@ -0,0 +1,27 @@ +#include "settingsdialog.h" +#include "ui_settingsdialog.h" + +SettingsDialog::SettingsDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::SettingsDialog) +{ + ui->setupUi(this); + + settings = new QSettings("MyCompany", "MinecraftLauncher", this); + ui->gitRepoEdit->setText(settings->value("gitRepoUrl").toString()); + + // Сигналы от кнопок (OK, Cancel) автоматически подключены к слотам accept() и reject() + // благодаря QDialogButtonBox и .ui файлу +} + +SettingsDialog::~SettingsDialog() +{ + delete ui; +} + +// Эта функция будет вызвана при нажатии кнопки "OK" +void SettingsDialog::accept() +{ + settings->setValue("gitRepoUrl", ui->gitRepoEdit->text()); + QDialog::accept(); // Вызываем базовую реализацию, которая закрывает диалог +} diff --git a/settingsdialog.h b/settingsdialog.h new file mode 100644 index 0000000..a72cfca --- /dev/null +++ b/settingsdialog.h @@ -0,0 +1,27 @@ +#ifndef SETTINGSDIALOG_H +#define SETTINGSDIALOG_H + +#include +#include + +namespace Ui { +class SettingsDialog; +} + +class SettingsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SettingsDialog(QWidget *parent = nullptr); + ~SettingsDialog(); + +private slots: + void accept(); // Переопределяем слот, чтобы сохранить настройки + +private: + Ui::SettingsDialog *ui; + QSettings *settings; +}; + +#endif // SETTINGSDIALOG_H diff --git a/settingsdialog.ui b/settingsdialog.ui new file mode 100644 index 0000000..8965f66 --- /dev/null +++ b/settingsdialog.ui @@ -0,0 +1,74 @@ + + + SettingsDialog + + + + 0 + 0 + 400 + 120 + + + + Настройки + + + + + + URL Git-репозитория с модами: + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + SettingsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SettingsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + \ No newline at end of file