#include "mainwindow.h" #include "./ui_mainwindow.h" #include #include #include #include #include #include #include #include #ifdef _WIN64 #include #endif namespace { constexpr uint8_t MAX_INSTALL_STAGES = 3; } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); UpdateOSAgnosticInformation(); net = new QNetworkAccessManager(this); PreInstallMode(); QWidget::setWindowTitle("Keeblarcraft Mod Installer"); // ui->centralwidget->setStyleSheet("background-image:url(background.png); background-position: center;"); // Needs work } MainWindow::~MainWindow() { delete net; delete reply; delete ui; } // Kick off the installation void MainWindow::on_install_update_button_clicked() { if (ConfirmationPopup()) { QNetworkRequest request(downloadUrl); reply = net->get(request); connect(reply, &QNetworkReply::errorOccurred, this, [this](QNetworkReply::NetworkError) { reply->deleteLater(); QMessageBox::warning(nullptr, "Error", reply->errorString()); }); connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(UpdateProgress(qint64, qint64))); connect(reply, SIGNAL(finished()), this, SLOT(FinishedDownloading())); } } // Callback from network manager on download. Update the progress bar as we download void MainWindow::UpdateProgress(qint64 read, qint64 total) { ui->mf_progress_bar->setMaximum(total); ui->mf_progress_bar->setValue(read); } // This is a callback to when the download is completed by the network manager void MainWindow::FinishedDownloading() { QByteArray data = reply->readAll(); QFile file(downloadLocation); if (file.open(QIODevice::WriteOnly)) { qint64 bytesWritten = file.write(data); } else { QMessageBox::warning(nullptr, "Error", file.errorString()); } reply->deleteLater(); // Update button to not be pressable again. Will probably stick this on a timer in the future ui->install_update_button->setEnabled(false); ui->install_update_button->show(); ui->install_update_button->setText(QString("Unzipping...")); // Unzip the file - this version isn't really checking if the file is really us so at the very least the name needs to match. // Otherwise we could end up damaging the ecosystem more than necessary. if (std::filesystem::exists(downloadLocation.toStdString())) { Install(); } else { QMessageBox::critical(nullptr, "Wrong format", "The downloaded file appears to not exist - or is more likely just not named mods.zip -> you either downloaded a bad file or Jesse named his file wrong. Tell him to fix it!!"); PostInstallMode(); } } // Build the unzip command depending on the users operating system std::string MainWindow::BuildUnzipCmd() { std::stringstream ss; #ifdef WIN64 ss << "tar -xf " << downloadLocation.toStdString() << " -C " << modsDir.toStdString(); #elif __unix__ qDebug() << "DL Loc: " << downloadLocation << " modsDir: " << modsDir; ss << "unzip " << downloadLocation.toStdString() << " -d " << modsDir.toStdString(); #endif return ss.str(); } // Installation step covers everything from unzipping to installing the mods to making sure fabric is installed void MainWindow::Install() { uint8_t installStage = 1; ui->mf_progress_bar->setValue(installStage++); ui->mf_progress_bar->setMaximum(MAX_INSTALL_STAGES); modsDir.append("mods"); // COND 1: If mods dir exists we need to nuke it // COND 2: If mods dir exists & it was our install location do NOT nuke it! if (std::filesystem::is_directory(modsDir.toStdString()) && modsDir != downloadLocation) { std::filesystem::remove_all(modsDir.toStdString()); // Force empty the mods folder inside the .minecraft dir } if (!std::filesystem::exists(modsDir.toStdString())) { std::filesystem::create_directory(modsDir.toStdString()); } ui->mf_progress_bar->setValue(installStage++); system(BuildUnzipCmd().c_str()); ui->mf_progress_bar->setValue(installStage++); // Clean up after ourselves; remove the file if possible try { if (std::filesystem::exists(downloadLocation.toStdString())) { std::filesystem::remove(downloadLocation.toStdString()); } } catch (std::exception e) { QString errorMsg = QString("Slight error - unable to cleanup after ourselves and could not remove leftover downloaded zip file located at %1").arg(downloadLocation); QMessageBox::information(nullptr, "Failed to cleanup!", errorMsg); } QMessageBox::information(nullptr, "Completed", "The installer has successfully finished. You may now close the installer and launch Minecraft!"); PostInstallMode(); } // Callback for downloading void MainWindow::Error(QNetworkReply::NetworkError code) { reply->deleteLater(); QMessageBox::warning(nullptr, "Error", reply->errorString()); } // Setup the window to be ready to update/install the mods folder void MainWindow::PreInstallMode() { ui->mf_progress_bar->setEnabled(false); ui->install_update_button->setText("Update / Install"); ui->install_update_button->setVisible(true); ui->close_launcher->setEnabled(false); ui->close_launcher->setVisible(false); } // Set window variables after installation and unzipping void MainWindow::PostInstallMode() { ui->mf_progress_bar->setEnabled(false); ui->install_update_button->setEnabled(false); ui->install_update_button->setVisible(false); ui->close_launcher->setEnabled(true); ui->close_launcher->setVisible(true); } // Make sure the user is sure they are OK with the current configuration bool MainWindow::ConfirmationPopup() { // This is the preinstall pop up box QString tInstallFabric = "YOU MUST HAVE FABRIC INSTALLED PRIOR TO USING THIS INSTALLER"; QString tempInstallPath = "Install to: " + modsDir; QString tempDlSrvr = "Grab mods.zip from: " + downloadUrl.toString(); QString deleteAll = "This installer will DELETE YOUR EXISTING MODS DIRECTORY. Back it up if you care about it beforehand!"; QString formatted = tInstallFabric.append(tempInstallPath).append("\n").append("\n").append(tempDlSrvr).append("\n").append(deleteAll); QMessageBox::StandardButton reply = QMessageBox::question(nullptr, "Confirm install options...", formatted); return reply == QMessageBox::Yes; } // This handles setting data prior to the install period dependent on the users operating system. void MainWindow::UpdateOSAgnosticInformation() { #ifdef _WIN64 char* appDataLoc = std::getenv("APPDATA"); if (appDataLoc) { std::filesystem::path execPath(appDataLoc); downloadLocation = QString::fromStdString(execPath.string()); // Stick the mods inside the roaming directory; but not the .minecraft directory itself. modsDir = downloadLocation + "\\.minecraft\\"; downloadLocation.append("\\mods.zip"); // Should point to %APPDATA%\mods.zip } else { QMessageBox::warning(nullptr, "Warning", "Unable to resolve APPDATA environment variable. You must manually set the .minecraft folder location"); } #elif __unix__ downloadLocation = getenv("HOME"); modsDir = downloadLocation + "/.minecraft/"; // Default value downloadLocation.append("/mods.zip"); #endif // Update the UI to reflect the default values correctly ui->sf_dl_server->setPlainText(downloadUrl.toString()); ui->mcInstallDir->setText(modsDir); } // This just closes the application but DOES call the destructor. // It technically segfaults on the way out for some odd reason; but the pointers are cleaned up. Who knows! void MainWindow::on_close_launcher_clicked() { QApplication::quit(); } // This is called if the users default .minecraft path cannot be found (by a user pressing the button of course) void MainWindow::on_changeInstallPathButton_clicked() { QString directory = QFileDialog::getExistingDirectory(this, tr("Find Files"), QDir::currentPath()); if (!directory.isEmpty()) { // We will trust the user that this is the .minecraft folder and do 0 error checking. modsDir = directory; } ui->mcInstallDir->setText(modsDir); } void MainWindow::on_sf_dl_server_textChanged() { downloadUrl = ui->sf_dl_server->toPlainText(); }