diff --git a/CMakeLists.txt b/CMakeLists.txt index 768e0678..e205e58d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.15) option(PORTABLE_INSTALL "Portable Installation" ON) option(NO_GIT_CLONE "Disables git clone usage for 3rdParty dependencies" OFF) - +option(UPDATER "Enables updater" ${PORTABLE_INSTALL}) project(RMG) find_package(Git REQUIRED) @@ -22,6 +22,11 @@ if (NOT PORTABLE_INSTALL AND (WIN32 OR MSYS)) set(PORTABLE_INSTALL ON) endif() +if (NOT PORTABLE_INSTALL AND UPDATER) + message(WARNING "Not-Portable builds don't support the updater, disabling UPDATER") + set(UPDATER OFF) +endif() + if (PORTABLE_INSTALL) set(CMAKE_INSTALL_PREFIX "") set(INSTALL_PATH "Bin/${CMAKE_BUILD_TYPE}") diff --git a/Source/RMG-Core/CMakeLists.txt b/Source/RMG-Core/CMakeLists.txt index f3d465af..7e780965 100644 --- a/Source/RMG-Core/CMakeLists.txt +++ b/Source/RMG-Core/CMakeLists.txt @@ -33,6 +33,7 @@ set(RMG_CORE_SOURCES VidExt.cpp Video.cpp Error.cpp + Unzip.cpp Core.cpp Key.cpp Rom.cpp diff --git a/Source/RMG-Core/Core.hpp b/Source/RMG-Core/Core.hpp index 491896af..831d1926 100644 --- a/Source/RMG-Core/Core.hpp +++ b/Source/RMG-Core/Core.hpp @@ -24,6 +24,7 @@ #include "Plugins.hpp" #include "Cheats.hpp" #include "Error.hpp" +#include "Unzip.hpp" #include "Video.hpp" #include "Key.hpp" #include "Rom.hpp" diff --git a/Source/RMG-Core/Settings/Settings.cpp b/Source/RMG-Core/Settings/Settings.cpp index 375428b0..4411e24f 100644 --- a/Source/RMG-Core/Settings/Settings.cpp +++ b/Source/RMG-Core/Settings/Settings.cpp @@ -161,6 +161,9 @@ static l_Setting get_setting(SettingsID settingId) case SettingsID::GUI_AutomaticFullscreen: setting = {SETTING_SECTION_GUI, "AutomaticFullscreen", false}; break; + case SettingsID::GUI_CheckForUpdates: + setting = {SETTING_SECTION_GUI, "CheckForUpdates", true}; + break; case SettingsID::GUI_Version: setting = {SETTING_SECTION_GUI, "Version", CORE_VERSION}; break; diff --git a/Source/RMG-Core/Settings/SettingsID.hpp b/Source/RMG-Core/Settings/SettingsID.hpp index 25de0a92..bca0a560 100644 --- a/Source/RMG-Core/Settings/SettingsID.hpp +++ b/Source/RMG-Core/Settings/SettingsID.hpp @@ -13,6 +13,7 @@ enum class SettingsID GUI_PauseEmulationOnFocusLoss, GUI_ResumeEmulationOnFocus, GUI_AutomaticFullscreen, + GUI_CheckForUpdates, GUI_Version, // Core Plugin Settings diff --git a/Source/RMG-Core/Unzip.cpp b/Source/RMG-Core/Unzip.cpp new file mode 100644 index 00000000..eb63c15e --- /dev/null +++ b/Source/RMG-Core/Unzip.cpp @@ -0,0 +1,169 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "Unzip.hpp" +#include "Error.hpp" + +#include +#include +#include +#include + +// +// Local Defines +// + +#define UNZIP_READ_SIZE 67108860 /* 64 MiB */ + +// +// Exported Functions +// + +bool CoreUnzip(std::filesystem::path file, std::filesystem::path path) +{ + std::string error; + + unzFile zipFile; + unz_global_info zipInfo; + std::ofstream outputStream; + char* read_buffer = (char*)malloc(UNZIP_READ_SIZE); + size_t bytes_read = 0; + + if (read_buffer == nullptr) + { + error = "CoreUnzip: malloc Failed!"; + CoreSetError(error); + return false; + } + + zipFile = unzOpen(file.string().c_str()); + if (zipFile == nullptr) + { + free(read_buffer); + error = "CoreUnzip: unzOpen Failed!"; + CoreSetError(error); + return false; + } + + if (unzGetGlobalInfo(zipFile, &zipInfo) != UNZ_OK) + { + free(read_buffer); + error = "CoreUnzip: unzGetGlobalInfo Failed!"; + CoreSetError(error); + return false; + } + + + for (int i = 0; i < zipInfo.number_entry; i++) + { + unz_file_info fileInfo; + char fileName[PATH_MAX]; + std::filesystem::path targetPath; + + // ensure we can retrieve the current file info + if (unzGetCurrentFileInfo(zipFile, &fileInfo, fileName, PATH_MAX, nullptr, 0, nullptr, 0) != UNZ_OK) + { + free(read_buffer); + unzClose(zipFile); + error = "CoreUnzip: unzGetCurrentFileInfo Failed!"; + CoreSetError(error); + return false; + } + + targetPath = path; + targetPath += "/"; + targetPath += fileName; + + if (targetPath.string().ends_with("/")) + { // directory + try + { + if (!std::filesystem::is_directory(targetPath) && + !std::filesystem::create_directory(targetPath)) + { + throw std::exception(); + } + } + catch (...) + { + free(read_buffer); + unzClose(zipFile); + error = "CoreUnzip: std::filesystem::create_directory("; + error += targetPath.string(); + error += ") Failed!"; + CoreSetError(error); + return false; + } + } + else + { // file + if (unzOpenCurrentFile(zipFile) != UNZ_OK) + { + free(read_buffer); + unzClose(zipFile); + error = "CoreUnzip: unzOpenCurrentFile Failed!"; + CoreSetError(error); + return false; + } + + outputStream.open(targetPath, std::ios::trunc | std::ios::binary); + if (!outputStream.is_open()) + { + free(read_buffer); + unzCloseCurrentFile(zipFile); + unzClose(zipFile); + error = "CoreUnzip: failed to open file!"; + error += targetPath.string(); + CoreSetError(error); + return false; + } + + do + { + bytes_read = unzReadCurrentFile(zipFile, read_buffer, UNZIP_READ_SIZE); + if (bytes_read < 0) + { + free(read_buffer); + unzCloseCurrentFile(zipFile); + unzClose(zipFile); + error = "CoreUnzip: unzReadCurrentFile Failed!"; + CoreSetError(error); + return false; + } + else + { // write data to file + outputStream.write(read_buffer, bytes_read); + } + } while (bytes_read > 0); + + outputStream.close(); + unzCloseCurrentFile(zipFile); + } + + // break when we've iterated over all entries + if ((i + 1) >= zipInfo.number_entry) + { + break; + } + + // move to next file + if (unzGoToNextFile(zipFile) != UNZ_OK) + { + free(read_buffer); + unzClose(zipFile); + error = "CoreUnzip: unzGoToNextFile Failed!"; + CoreSetError(error); + return false; + } + } + + unzClose(zipFile); + free(read_buffer); + return true; +} diff --git a/Source/RMG-Core/Unzip.hpp b/Source/RMG-Core/Unzip.hpp new file mode 100644 index 00000000..c773b11b --- /dev/null +++ b/Source/RMG-Core/Unzip.hpp @@ -0,0 +1,18 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef CORE_UNZIP_HPP +#define CORE_UNZIP_HPP + +#include + +// attempts to unzip the file to path +bool CoreUnzip(std::filesystem::path file, std::filesystem::path path); + +#endif // CORE_UNZIP_HPP \ No newline at end of file diff --git a/Source/RMG/CMakeLists.txt b/Source/RMG/CMakeLists.txt index 5dd7e0b2..e8eadf06 100644 --- a/Source/RMG/CMakeLists.txt +++ b/Source/RMG/CMakeLists.txt @@ -7,6 +7,11 @@ set(CMAKE_AUTOUIC ON) find_package(Qt5 COMPONENTS Gui Widgets Core REQUIRED) +if (UPDATER) +find_package(Qt5 COMPONENTS Network REQUIRED) +add_definitions(-DUPDATER) +endif(UPDATER) + find_package(PkgConfig REQUIRED) pkg_check_modules(SDL2 REQUIRED sdl2) @@ -42,6 +47,17 @@ set(RMG_SOURCES main.cpp ) +if (UPDATER) +list(APPEND RMG_SOURCES + UserInterface/Dialog/UpdateDialog.cpp + UserInterface/Dialog/UpdateDialog.ui + UserInterface/Dialog/DownloadUpdateDialog.cpp + UserInterface/Dialog/DownloadUpdateDialog.ui + UserInterface/Dialog/InstallUpdateDialog.cpp + UserInterface/Dialog/InstallUpdateDialog.ui +) +endif(UPDATER) + if (CMAKE_BUILD_TYPE MATCHES Release) add_executable(RMG WIN32 ${RMG_SOURCES}) else() @@ -61,3 +77,8 @@ target_include_directories(RMG PRIVATE ) target_link_libraries(RMG Qt5::Gui Qt5::Widgets) + +if (UPDATER) +target_link_libraries(RMG Qt5::Network) +endif(UPDATER) + diff --git a/Source/RMG/UserInterface/Dialog/DownloadUpdateDialog.cpp b/Source/RMG/UserInterface/Dialog/DownloadUpdateDialog.cpp new file mode 100644 index 00000000..32334c46 --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/DownloadUpdateDialog.cpp @@ -0,0 +1,102 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "DownloadUpdateDialog.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace UserInterface::Dialog; + +DownloadUpdateDialog::DownloadUpdateDialog(QWidget *parent, QUrl url, QString filename) : QDialog(parent, Qt::WindowSystemMenuHint | Qt::WindowTitleHint) +{ + this->setupUi(this); + + this->filename = filename; + this->label->setText("Downloading " + filename + "..."); + + QNetworkAccessManager* networkAccessManager = new QNetworkAccessManager(this); + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); + + this->reply = networkAccessManager->get(request); + connect(reply, &QNetworkReply::downloadProgress, this, &DownloadUpdateDialog::on_reply_downloadProgress); + connect(reply, &QNetworkReply::finished, this, &DownloadUpdateDialog::on_reply_finished); +} + +DownloadUpdateDialog::~DownloadUpdateDialog(void) +{ +} + +QString DownloadUpdateDialog::GetTempDirectory(void) +{ + return this->temporaryDirectory; +} + +QString DownloadUpdateDialog::GetFileName(void) +{ + return this->filename; +} + +void DownloadUpdateDialog::showErrorMessage(QString error, QString details) +{ + QMessageBox msgBox((this->isVisible() ? this : this->parentWidget())); + msgBox.setIcon(QMessageBox::Icon::Critical); + msgBox.setWindowTitle("Error"); + msgBox.setText(error); + msgBox.setDetailedText(details); + msgBox.addButton(QMessageBox::Ok); + msgBox.exec(); +} + +void DownloadUpdateDialog::on_reply_downloadProgress(qint64 size, qint64 total) +{ + this->progressBar->setMaximum(total); + this->progressBar->setMinimum(0); + this->progressBar->setValue(size); +} + +void DownloadUpdateDialog::on_reply_finished(void) +{ + if (this->reply->error()) + { + this->showErrorMessage("Failed to download update file!", this->reply->errorString()); + this->reply->deleteLater(); + this->reject(); + return; + } + + QTemporaryDir temporaryDir; + temporaryDir.setAutoRemove(false); + if (!temporaryDir.isValid()) + { + this->showErrorMessage("Failed to create temporary directory!", ""); + this->reply->deleteLater(); + this->reject(); + return; + } + + this->temporaryDirectory = temporaryDir.path(); + + QFile file(temporaryDir.filePath(this->filename)); + file.open(QIODevice::WriteOnly); + file.write(this->reply->readAll()); + file.close(); + + this->reply->deleteLater(); + this->accept(); +} + diff --git a/Source/RMG/UserInterface/Dialog/DownloadUpdateDialog.hpp b/Source/RMG/UserInterface/Dialog/DownloadUpdateDialog.hpp new file mode 100644 index 00000000..c7e6f467 --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/DownloadUpdateDialog.hpp @@ -0,0 +1,52 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef DOWNLOADUPDATEDIALOG_HPP +#define DOWNLOADUPDATEDIALOG_HPP + +#include +#include +#include +#include + +#include +#include + +#include "ui_DownloadUpdateDialog.h" + +namespace UserInterface +{ +namespace Dialog +{ +class DownloadUpdateDialog : public QDialog, private Ui::DownloadUpdateDialog +{ + Q_OBJECT + + private: + QNetworkReply *reply; + QString temporaryDirectory; + QString filename; + + void showErrorMessage(QString error, QString details); + + public: + DownloadUpdateDialog(QWidget *parent, QUrl url, QString filename); + ~DownloadUpdateDialog(void); + + QString GetTempDirectory(void); + QString GetFileName(void); + + private slots: + void on_reply_downloadProgress(qint64 size, qint64 total); + void on_reply_finished(void); +}; +} // namespace Dialog +} // namespace UserInterface + +#endif // DOWNLOADUPDATEDIALOG_HPP \ No newline at end of file diff --git a/Source/RMG/UserInterface/Dialog/DownloadUpdateDialog.ui b/Source/RMG/UserInterface/Dialog/DownloadUpdateDialog.ui new file mode 100644 index 00000000..70c3932a --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/DownloadUpdateDialog.ui @@ -0,0 +1,35 @@ + + + DownloadUpdateDialog + + + + 0 + 0 + 238 + 62 + + + + Downloading Update + + + + + + Downloading ... + + + + + + + 0 + + + + + + + + diff --git a/Source/RMG/UserInterface/Dialog/InstallUpdateDialog.cpp b/Source/RMG/UserInterface/Dialog/InstallUpdateDialog.cpp new file mode 100644 index 00000000..e031f237 --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/InstallUpdateDialog.cpp @@ -0,0 +1,157 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "InstallUpdateDialog.hpp" +#include "RMG-Core/Error.hpp" + +#include +#include +#include +#include + +using namespace UserInterface::Dialog; + +InstallUpdateDialog::InstallUpdateDialog(QWidget *parent, QString installationDirectory, QString temporaryDirectory, QString filename) : QDialog(parent, Qt::WindowSystemMenuHint | Qt::WindowTitleHint) +{ + this->setupUi(this); + + this->installationDirectory = installationDirectory; + this->temporaryDirectory = temporaryDirectory; + this->filename = filename; + this->install(); +} + +InstallUpdateDialog::~InstallUpdateDialog(void) +{ +} + +void InstallUpdateDialog::install(void) +{ + this->label->setText("Extracting " + this->filename + "..."); + this->progressBar->setValue(50); + + QString fullFilePath; + fullFilePath = this->temporaryDirectory; + fullFilePath += "/" + this->filename; + + QString appPath = QCoreApplication::applicationDirPath(); + QString appPid = QString::number(QCoreApplication::applicationPid()); + +#ifdef _WIN32 + if (this->filename.endsWith(".exe")) + { + QStringList scriptLines = + { + "@echo off", + "taskkill /F /PID:" + appPid, + "\"" + fullFilePath + "\" /CLOSEAPPLICATIONS /SILENT /DIR=\"" + appPath + "\"", + "rmdir /S /Q \"" + this->temporaryDirectory + "\"", + }; + this->writeAndRunScript(scriptLines); + this->accept(); + return; + } +#endif // _WIN32 + + QDir dir(this->temporaryDirectory); + if (!dir.mkdir("extract")) + { + this->showErrorMessage("QDir::mkdir() Failed!", ""); + this->reject(); + return; + } + + QString extractDirectory; + extractDirectory = this->temporaryDirectory; + extractDirectory += "/extract"; + + if (!CoreUnzip(fullFilePath.toStdU32String(), extractDirectory.toStdU32String())) + { + this->showErrorMessage("CoreUnzip() Failed!", QString::fromStdString(CoreGetError())); + this->reject(); + return; + } + +#ifdef _WIN32 + QStringList scriptLines = + { + "@echo off", + "del /F /Q \"" + fullFilePath + "\"", + "taskkill /F /PID:" + appPid, + "copy /Y \"" + extractDirectory + "\\*\" \"" + appPath + "/\"", + "start \"\" \"" + appPath + "\\RMG.exe\"", + "rmdir /S /Q \"" + this->temporaryDirectory + "\"", + }; + this->writeAndRunScript(scriptLines); +#else // Linux + QStringList scriptLines = + { + "#!/usr/bin/env bash", + "set -e", + "rm -rf \"" + fullFilePath + "\"", + "kill -1 " + appPid, + "cp -R \"" + extractDirectory + "/\"* \"" + appPath + "/\"", + "chmod +x \"" + appPath + "/RMG\"", + "\"" + appPath + "/RMG\"&", + "rm -rf \"" + this->temporaryDirectory + "\"" , + }; + this->writeAndRunScript(scriptLines); +#endif // _WIN32 +} + +void InstallUpdateDialog::writeAndRunScript(QStringList stringList) +{ + QString scriptPath; + scriptPath = this->temporaryDirectory; +#ifdef _WIN32 + scriptPath += "/update.bat"; +#else + scriptPath += "/update.sh"; +#endif // _WIN32 + + QFile scriptFile(scriptPath); + if (!scriptFile.open(QIODevice::WriteOnly | QIODevice::Text)) + { + this->showErrorMessage("QFile::open() Failed!", ""); + return; + } + + QTextStream textStream(&scriptFile); + + // write script to file + for (const QString& str : stringList) + { + textStream << str << "\n"; + } + + scriptFile.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner); + scriptFile.close(); + + this->launchProcess(scriptPath, {}); +} + +void InstallUpdateDialog::launchProcess(QString file, QStringList arguments) +{ + QProcess process; + process.setProgram(file); + process.setArguments(arguments); + process.startDetached(); +} + +void InstallUpdateDialog::showErrorMessage(QString error, QString details) +{ + QMessageBox msgBox((this->isVisible() ? this : this->parentWidget())); + msgBox.setIcon(QMessageBox::Icon::Critical); + msgBox.setWindowTitle("Error"); + msgBox.setText(error); + msgBox.setDetailedText(details); + msgBox.addButton(QMessageBox::Ok); + msgBox.exec(); +} + diff --git a/Source/RMG/UserInterface/Dialog/InstallUpdateDialog.hpp b/Source/RMG/UserInterface/Dialog/InstallUpdateDialog.hpp new file mode 100644 index 00000000..24a29f91 --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/InstallUpdateDialog.hpp @@ -0,0 +1,46 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef INSTALLUPDATEDIALOG_HPP +#define INSTALLUPDATEDIALOG_HPP + +#include +#include + +#include + +#include "ui_InstallUpdateDialog.h" + +namespace UserInterface +{ +namespace Dialog +{ +class InstallUpdateDialog : public QDialog, private Ui::InstallUpdateDialog +{ + Q_OBJECT + + private: + QString installationDirectory; + QString temporaryDirectory; + QString filename; + + void install(void); + + void writeAndRunScript(QStringList stringList); + void launchProcess(QString file, QStringList arguments); + void showErrorMessage(QString error, QString details); + + public: + InstallUpdateDialog(QWidget *parent, QString installationDirectory, QString temporaryDirectory, QString filename); + ~InstallUpdateDialog(void); +}; +} // namespace Dialog +} // namespace UserInterface + +#endif // INSTALLUPDATEDIALOG_HPP \ No newline at end of file diff --git a/Source/RMG/UserInterface/Dialog/InstallUpdateDialog.ui b/Source/RMG/UserInterface/Dialog/InstallUpdateDialog.ui new file mode 100644 index 00000000..46039f43 --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/InstallUpdateDialog.ui @@ -0,0 +1,35 @@ + + + InstallUpdateDialog + + + + 0 + 0 + 223 + 62 + + + + Installing Update + + + + + + Extracting ... + + + + + + + 24 + + + + + + + + diff --git a/Source/RMG/UserInterface/Dialog/UpdateDialog.cpp b/Source/RMG/UserInterface/Dialog/UpdateDialog.cpp new file mode 100644 index 00000000..85dadf19 --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/UpdateDialog.cpp @@ -0,0 +1,112 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "UpdateDialog.hpp" +#include "DownloadUpdateDialog.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace UserInterface::Dialog; + +UpdateDialog::UpdateDialog(QWidget *parent, QJsonObject jsonObject) : QDialog(parent, Qt::WindowSystemMenuHint | Qt::WindowTitleHint) +{ + this->setupUi(this); + + this->jsonObject = jsonObject; + + this->label->setText(jsonObject.value("tag_name").toString() + " Available"); + this->textEdit->setText(jsonObject.value("body").toString()); + + // change ok button text to 'Update' + QPushButton* button = this->buttonBox->button(QDialogButtonBox::Ok); + button->setText("Update"); +} + +UpdateDialog::~UpdateDialog(void) +{ +} + +void UpdateDialog::on_disableUpdateCheckCheckBox_stateChanged(int state) +{ + CoreSettingsSetValue(SettingsID::GUI_CheckForUpdates, (state == Qt::Unchecked)); + CoreSettingsSave(); +} + +QString UpdateDialog::GetFileName(void) +{ + return this->filename; +} + +QUrl UpdateDialog::GetUrl(void) +{ + return this->url; +} + +void UpdateDialog::accept(void) +{ + QJsonArray jsonArray = jsonObject["assets"].toArray(); + QString filenameToDownload; + QUrl urlToDownload; + +#ifdef _WIN32 + this->isWin32Setup = QFile::exists("unins000.exe") && QFile::exists("unins000.dat"); +#endif // _WIN32 + + for (const QJsonValue& value : jsonArray) + { + QJsonObject object = value.toObject(); + + QString filename = object.value("name").toString(); + QString url = object.value("browser_download_url").toString(); + +#ifdef _WIN32 + if (this->isWin32Setup) + { + if (filename.contains("Windows64") && + filename.contains("Setup")) + { + filenameToDownload = filename; + urlToDownload = QUrl(url); + break; + } + } + else + { + if (filename.contains("Windows64") && + filename.contains("Portable")) + { + filenameToDownload = filename; + urlToDownload = QUrl(url); + break; + } + } +#else + if (filename.contains("Linux64") && + filename.contains("Portable")) + { + filenameToDownload = filename; + urlToDownload = QUrl(url); + break; + } +#endif // _WIN32 + } + + this->url = urlToDownload; + this->filename = filenameToDownload; + QDialog::accept(); +} diff --git a/Source/RMG/UserInterface/Dialog/UpdateDialog.hpp b/Source/RMG/UserInterface/Dialog/UpdateDialog.hpp new file mode 100644 index 00000000..2fb80c77 --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/UpdateDialog.hpp @@ -0,0 +1,53 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef UPDATEDIALOG_HPP +#define UPDATEDIALOG_HPP + +#include +#include +#include +#include + +#include + +#include "ui_UpdateDialog.h" + +namespace UserInterface +{ +namespace Dialog +{ +class UpdateDialog : public QDialog, private Ui::UpdateDialog +{ + Q_OBJECT + + private: + QJsonObject jsonObject; + QString filename; + QUrl url; +#ifdef _WIN32 + bool isWin32Setup = false; +#endif // _WIN32 + + public: + UpdateDialog(QWidget *parent, QJsonObject jsonObject); + ~UpdateDialog(void); + + QString GetFileName(void); + QUrl GetUrl(void); + + private slots: + void on_disableUpdateCheckCheckBox_stateChanged(int state); + + void accept(void) Q_DECL_OVERRIDE; +}; +} // namespace Dialog +} // namespace UserInterface + +#endif // UPDATEDIALOG_HPP \ No newline at end of file diff --git a/Source/RMG/UserInterface/Dialog/UpdateDialog.ui b/Source/RMG/UserInterface/Dialog/UpdateDialog.ui new file mode 100644 index 00000000..0d09e06a --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/UpdateDialog.ui @@ -0,0 +1,91 @@ + + + UpdateDialog + + + + 0 + 0 + 351 + 419 + + + + A new version is available + + + + + + v0.2.2 Available + + + Qt::AlignCenter + + + + + + + QTextEdit::NoWrap + + + true + + + + + + + Don't check for updates again + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + UpdateDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + UpdateDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Source/RMG/UserInterface/MainWindow.cpp b/Source/RMG/UserInterface/MainWindow.cpp index c93bc31f..36fb3702 100644 --- a/Source/RMG/UserInterface/MainWindow.cpp +++ b/Source/RMG/UserInterface/MainWindow.cpp @@ -10,6 +10,11 @@ #include "MainWindow.hpp" #include "UserInterface/Dialog/AboutDialog.hpp" +#ifdef UPDATER +#include "UserInterface/Dialog/UpdateDialog.hpp" +#include "UserInterface/Dialog/DownloadUpdateDialog.hpp" +#include "UserInterface/Dialog/InstallUpdateDialog.hpp" +#endif // UPDATER #include "UserInterface/EventFilter.hpp" #include "Utilities/QtKeyToSdl2Key.hpp" #include "Callbacks.hpp" @@ -60,6 +65,10 @@ bool MainWindow::Init(QGuiApplication* app) this->ui_Actions_Init(); this->ui_Actions_Connect(); +#ifdef UPDATER + this->ui_CheckForUpdates(); +#endif // UPDATER + this->menuBar_Init(); this->menuBar_Setup(false, false); @@ -705,6 +714,20 @@ void MainWindow::ui_Actions_Connect(void) connect(this->action_Help_About, &QAction::triggered, this, &MainWindow::on_Action_Help_About); } +#ifdef UPDATER +void MainWindow::ui_CheckForUpdates(void) +{ + if (!CoreSettingsGetBoolValue(SettingsID::GUI_CheckForUpdates)) + { + return; + } + + QNetworkAccessManager* networkAccessManager = new QNetworkAccessManager(this); + connect(networkAccessManager, &QNetworkAccessManager::finished, this, &MainWindow::on_networkAccessManager_Finished); + networkAccessManager->get(QNetworkRequest(QUrl("https://api.github.com/repos/Rosalie241/RMG/releases/latest"))); +} +#endif // UPDATER + void MainWindow::timerEvent(QTimerEvent *event) { int timerId = event->timerId(); @@ -825,6 +848,61 @@ void MainWindow::on_QGuiApplication_applicationStateChanged(Qt::ApplicationState } } +#ifdef UPDATER +void MainWindow::on_networkAccessManager_Finished(QNetworkReply* reply) +{ + if (reply->error()) + { + reply->deleteLater(); + return; + } + + QJsonDocument jsonDocument = QJsonDocument::fromJson(reply->readAll()); + QJsonObject jsonObject = jsonDocument.object(); + + QString currentVersion = QString(VERSION_STR); + QString latestVersion = jsonObject.value("tag_name").toString(); + + reply->deleteLater(); + + // make sure the current version is valid + // and not a dev version + if (currentVersion.size() != 6) + { + return; + } + + // do nothing when versions match + if (currentVersion == latestVersion) + { + return; + } + + int ret = 0; + + Dialog::UpdateDialog updateDialog(this, jsonObject); + ret = updateDialog.exec(); + if (ret != QDialog::Accepted) + { + return; + } + + Dialog::DownloadUpdateDialog downloadUpdateDialog(this, updateDialog.GetUrl(), updateDialog.GetFileName()); + ret = downloadUpdateDialog.exec(); + if (ret != QDialog::Accepted) + { + return; + } + + Dialog::InstallUpdateDialog installUpdateDialog(this, QCoreApplication::applicationDirPath(), downloadUpdateDialog.GetTempDirectory(), downloadUpdateDialog.GetFileName()); + ret = installUpdateDialog.exec(); + if (ret != QDialog::Accepted) + { + return; + } +} +#endif // UPDATER + void MainWindow::on_Action_File_OpenRom(void) { bool isRunning = CoreIsEmulationRunning(); diff --git a/Source/RMG/UserInterface/MainWindow.hpp b/Source/RMG/UserInterface/MainWindow.hpp index c6404e81..e3340301 100644 --- a/Source/RMG/UserInterface/MainWindow.hpp +++ b/Source/RMG/UserInterface/MainWindow.hpp @@ -27,6 +27,13 @@ #include #include +#ifdef UPDATER +#include +#include +#include +#include +#endif // UPDATER + namespace UserInterface { class MainWindow : public QMainWindow @@ -134,6 +141,9 @@ class MainWindow : public QMainWindow void ui_Actions_Remove(void); void ui_Actions_Connect(void); +#ifdef UPDATER + void ui_CheckForUpdates(void); +#endif // UPDATER protected: void timerEvent(QTimerEvent *) Q_DECL_OVERRIDE; @@ -143,6 +153,10 @@ class MainWindow : public QMainWindow void on_EventFilter_FileDropped(QDropEvent *); void on_QGuiApplication_applicationStateChanged(Qt::ApplicationState); + +#ifdef UPDATER + void on_networkAccessManager_Finished(QNetworkReply *); +#endif // UPDATER void on_Action_File_OpenRom(void); void on_Action_File_OpenCombo(void); diff --git a/Source/Script/BundleDependencies.sh b/Source/Script/BundleDependencies.sh index 867a2072..6c57aad1 100755 --- a/Source/Script/BundleDependencies.sh +++ b/Source/Script/BundleDependencies.sh @@ -40,4 +40,8 @@ done windeployqt "$exe" +# needed by Qt at runtime +cp "$path/libcrypto-1_1-x64.dll" "$bin_dir/" +cp "$path/libssl-1_1-x64.dll" "$bin_dir/" + exit 0