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