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/CMakeLists.txt.user b/CMakeLists.txt.user new file mode 100644 index 0000000..9d354e7 --- /dev/null +++ b/CMakeLists.txt.user @@ -0,0 +1,314 @@ + + + + + + EnvironmentId + {137161dd-990c-4c0b-851a-d10872a75af9} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + true + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 0 + 80 + true + true + 1 + 0 + false + true + false + 2 + true + true + 0 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + false + true + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + false + + + 0 + true + + true + true + Builtin.DefaultTidyAndClazy + 16 + true + + G:/Documents/The_Grand_Archives/Qt_projects/minecraft-launcher/CMakeLists.txt + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + true + Desktop Qt 6.9.2 MinGW 64-bit + Desktop Qt 6.9.2 MinGW 64-bit + qt.qt6.692.win64_mingw_kit + 0 + 0 + 0 + + Debug + 2 + false + + -DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DQT_MAINTENANCE_TOOL:FILEPATH=E:/Qt/MaintenanceTool.exe +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_BUILD_TYPE:STRING=Debug +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} +-DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON + 0 + G:\Documents\The_Grand_Archives\Qt_projects\minecraft-launcher\build\Desktop_Qt_6_9_2_MinGW_64_bit-Debug + + + + + all + + false + + true + Собрать + CMakeProjectManager.MakeStep + + 1 + Сборка + Сборка + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + Собрать + CMakeProjectManager.MakeStep + + 1 + Очистка + Очистка + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Отладка + CMakeProjectManager.CMakeBuildConfiguration + 0 + 0 + + + 0 + Развёртывание + Развёртывание + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + + + + + + + + false + + true + ApplicationManagerPlugin.Deploy.CMakePackageStep + + + install-package --acknowledge + true + Install Application Manager package + ApplicationManagerPlugin.Deploy.InstallPackageStep + + + + + + + + 2 + Развёртывание + Развёртывание + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ApplicationManagerPlugin.Deploy.Configuration + + 2 + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph "dwarf,4096" -F 250 + minecraft-launcher + CMakeProjectManager.CMakeRunConfiguration. + minecraft-launcher + false + true + true + true + G:/Documents/The_Grand_Archives/Qt_projects/minecraft-launcher/build/Desktop_Qt_6_9_2_MinGW_64_bit-Debug + + 1 + + 1 + + + 0 + Развёртывание + Развёртывание + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + + + + + + + + false + + true + ApplicationManagerPlugin.Deploy.CMakePackageStep + + + install-package --acknowledge + true + Install Application Manager package + ApplicationManagerPlugin.Deploy.InstallPackageStep + + + + + + + + 2 + Развёртывание + Развёртывание + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ApplicationManagerPlugin.Deploy.Configuration + + 2 + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph "dwarf,4096" -F 250 + minecraft-launcher + CMakeProjectManager.CMakeRunConfiguration. + minecraft-launcher + false + true + true + true + G:/Documents/The_Grand_Archives/Qt_projects/minecraft-launcher/build/Desktop_Qt_6_9_2_MinGW_64_bit-Debug + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + 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