From e37df05d75b07c3904d97a9edf3060c2b0702252 Mon Sep 17 00:00:00 2001 From: Nayla Hanegan Date: Tue, 7 Jan 2025 00:29:02 -0500 Subject: [PATCH] initial updater work --- Source/Core/Common/HttpRequest.cpp | 8 +- .../Core/Core/MarioPartyNetplay/Discord.cpp | 8 +- Source/Core/Core/MarioPartyNetplay/Discord.h | 5 + .../Core/Core/MarioPartyNetplay/Gamestate.cpp | 5 + .../Core/Core/MarioPartyNetplay/Gamestate.h | 5 + Source/Core/DolphinQt/CMakeLists.txt | 6 + Source/Core/DolphinQt/DolphinQt.vcxproj | 8 + Source/Core/DolphinQt/MainWindow.cpp | 39 +++ Source/Core/DolphinQt/MainWindow.h | 1 + .../DownloadUpdateDialog.cpp | 116 +++++++++ .../MarioPartyNetplay/DownloadUpdateDialog.h | 31 +++ .../MarioPartyNetplay/InstallUpdateDialog.cpp | 222 ++++++++++++++++++ .../MarioPartyNetplay/InstallUpdateDialog.h | 40 ++++ .../MarioPartyNetplay/UpdateDialog.cpp | 112 +++++++++ .../MarioPartyNetplay/UpdateDialog.h | 50 ++++ Source/Core/DolphinQt/MenuBar.cpp | 11 +- Source/Core/DolphinQt/MenuBar.h | 1 + 17 files changed, 657 insertions(+), 11 deletions(-) create mode 100644 Source/Core/DolphinQt/MarioPartyNetplay/DownloadUpdateDialog.cpp create mode 100644 Source/Core/DolphinQt/MarioPartyNetplay/DownloadUpdateDialog.h create mode 100644 Source/Core/DolphinQt/MarioPartyNetplay/InstallUpdateDialog.cpp create mode 100644 Source/Core/DolphinQt/MarioPartyNetplay/InstallUpdateDialog.h create mode 100644 Source/Core/DolphinQt/MarioPartyNetplay/UpdateDialog.cpp create mode 100644 Source/Core/DolphinQt/MarioPartyNetplay/UpdateDialog.h diff --git a/Source/Core/Common/HttpRequest.cpp b/Source/Core/Common/HttpRequest.cpp index cf26710372..c9a09565a3 100644 --- a/Source/Core/Common/HttpRequest.cpp +++ b/Source/Core/Common/HttpRequest.cpp @@ -239,10 +239,11 @@ HttpRequest::Response HttpRequest::Impl::Fetch(const std::string& url, Method me m_response_headers.clear(); curl_easy_setopt(m_curl.get(), CURLOPT_POST, method == Method::POST); curl_easy_setopt(m_curl.get(), CURLOPT_URL, url.c_str()); + if (method == Method::POST && multiform.empty()) { - curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDS, payload); - curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDSIZE, size); + curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDS, payload); + curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDSIZE, size); } curl_mime* form = nullptr; @@ -271,6 +272,9 @@ HttpRequest::Response HttpRequest::Impl::Fetch(const std::string& url, Method me else list = curl_slist_append(list, (name + ": " + *value).c_str()); } + + list = curl_slist_append(list, "User-Agent: Dolphin-MPN/1.0"); + curl_easy_setopt(m_curl.get(), CURLOPT_HTTPHEADER, list); curl_easy_setopt(m_curl.get(), CURLOPT_HEADERFUNCTION, header_callback); diff --git a/Source/Core/Core/MarioPartyNetplay/Discord.cpp b/Source/Core/Core/MarioPartyNetplay/Discord.cpp index 1efa1dabe2..88de54f7b1 100644 --- a/Source/Core/Core/MarioPartyNetplay/Discord.cpp +++ b/Source/Core/Core/MarioPartyNetplay/Discord.cpp @@ -1,3 +1,8 @@ +/* +* Dolphin for Mario Party Netplay +* Copyright (C) 2025 Tabitha Hanegan +*/ + #include "Discord.h" #include #include "Core/Config/NetplaySettings.h" @@ -8,9 +13,6 @@ bool mpn_update_discord() { - // if (!Memory::IsInitialized()) - // return false; - DiscordRichPresence RichPresence = {}; RichPresence.largeImageKey = CurrentState.Image ? CurrentState.Image : "default"; diff --git a/Source/Core/Core/MarioPartyNetplay/Discord.h b/Source/Core/Core/MarioPartyNetplay/Discord.h index abf3802a3c..3922ed6cf7 100644 --- a/Source/Core/Core/MarioPartyNetplay/Discord.h +++ b/Source/Core/Core/MarioPartyNetplay/Discord.h @@ -1,3 +1,8 @@ +/* +* Dolphin for Mario Party Netplay +* Copyright (C) 2025 Tabitha Hanegan +*/ + #include #include "Gamestate.h" diff --git a/Source/Core/Core/MarioPartyNetplay/Gamestate.cpp b/Source/Core/Core/MarioPartyNetplay/Gamestate.cpp index 32a2176dcd..f8beec8782 100644 --- a/Source/Core/Core/MarioPartyNetplay/Gamestate.cpp +++ b/Source/Core/Core/MarioPartyNetplay/Gamestate.cpp @@ -1,3 +1,8 @@ +/* +* Dolphin for Mario Party Netplay +* Copyright (C) 2025 Tabitha Hanegan +*/ + #include "Gamestate.h" #include "Core/System.h" diff --git a/Source/Core/Core/MarioPartyNetplay/Gamestate.h b/Source/Core/Core/MarioPartyNetplay/Gamestate.h index 150e7671b2..521b31868a 100644 --- a/Source/Core/Core/MarioPartyNetplay/Gamestate.h +++ b/Source/Core/Core/MarioPartyNetplay/Gamestate.h @@ -1,3 +1,8 @@ +/* +* Dolphin for Mario Party Netplay +* Copyright (C) 2025 Tabitha Hanegan +*/ + #ifndef MPN_GAMESTATE_H #define MPN_GAMESTATE_H diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 186e70221b..0258764e57 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -274,6 +274,12 @@ add_executable(dolphin-mpn Main.cpp MainWindow.cpp MainWindow.h + MarioPartyNetplay/DownloadUpdateDialog.cpp + MarioPartyNetplay/DownloadUpdateDialog.h + MarioPartyNetplay/InstallUpdateDialog.cpp + MarioPartyNetplay/InstallUpdateDialog.h + MarioPartyNetplay/UpdateDialog.cpp + MarioPartyNetplay/UpdateDialog.h MenuBar.cpp MenuBar.h NetPlay/ChunkedProgressDialog.cpp diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index 3636535b41..d7f1d7224b 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -186,6 +186,9 @@ + + + @@ -401,6 +404,9 @@ + + + @@ -481,6 +487,7 @@ + @@ -489,6 +496,7 @@ + diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 5e47649182..6547ce2652 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -17,6 +17,9 @@ #include #include #include +#include +#include +#include #include @@ -37,6 +40,9 @@ #include "Common/ScopeGuard.h" #include "Common/Version.h" #include "Common/WindowSystemInfo.h" +#include "Common/HttpRequest.h" +#include "Common/ScopeGuard.h" +#include "Common/StringUtil.h" #include "Core/AchievementManager.h" #include "Core/Boot/Boot.h" @@ -101,6 +107,9 @@ #include "DolphinQt/HotkeyScheduler.h" #include "DolphinQt/InfinityBase/InfinityBaseWindow.h" #include "DolphinQt/MenuBar.h" +#include "DolphinQt/MarioPartyNetplay/DownloadUpdateDialog.h" +#include "DolphinQt/MarioPartyNetplay/InstallUpdateDialog.h" +#include "DolphinQt/MarioPartyNetplay/UpdateDialog.h" #include "DolphinQt/NKitWarningDialog.h" #include "DolphinQt/NetPlay/NetPlayBrowser.h" #include "DolphinQt/NetPlay/NetPlayDialog.h" @@ -601,6 +610,7 @@ void MainWindow::ConnectMenuBar() &GameList::OnGameListVisibilityChanged); connect(m_menu_bar, &MenuBar::ShowAboutDialog, this, &MainWindow::ShowAboutDialog); + connect(m_menu_bar, &MenuBar::ShowUpdateDialog, this, &MainWindow::ShowUpdateDialog); connect(m_game_list, &GameList::SelectionChanged, m_menu_bar, &MenuBar::SelectionChanged); connect(this, &MainWindow::ReadOnlyModeChanged, m_menu_bar, &MenuBar::ReadOnlyModeChanged); @@ -1320,6 +1330,35 @@ void MainWindow::ShowAboutDialog() about.exec(); } +void MainWindow::ShowUpdateDialog() +{ + Common::HttpRequest httpRequest; + + // Make the GET request + auto response = httpRequest.Get("https://api.github.com/repos/MarioPartyNetplay/Dolphin-MPN/releases/latest"); + + if (response) + { + // Access the underlying vector and convert it to QByteArray + QByteArray responseData(reinterpret_cast(response->data()), response->size()); + + // Parse the JSON response + QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData); + QJsonObject jsonObject = jsonDoc.object(); + + // Create and show the UpdateDialog with the fetched data + bool forced = false; // Set this based on your logic + UserInterface::Dialog::UpdateDialog updater(this, jsonObject, forced); + SetQWidgetWindowDecorations(&updater); + updater.exec(); + } + else + { + // Handle error + QMessageBox::critical(this, tr("Error"), tr("Failed to fetch update information.")); + } +} + void MainWindow::ShowHotkeyDialog() { if (!m_hotkey_window) diff --git a/Source/Core/DolphinQt/MainWindow.h b/Source/Core/DolphinQt/MainWindow.h index be39ee0ea7..bf3abb94cd 100644 --- a/Source/Core/DolphinQt/MainWindow.h +++ b/Source/Core/DolphinQt/MainWindow.h @@ -169,6 +169,7 @@ private: void ShowGraphicsWindow(); void ShowFreeLookWindow(); void ShowAboutDialog(); + void ShowUpdateDialog(); void ShowHotkeyDialog(); void ShowNetPlaySetupDialog(); void ShowNetPlayBrowser(); diff --git a/Source/Core/DolphinQt/MarioPartyNetplay/DownloadUpdateDialog.cpp b/Source/Core/DolphinQt/MarioPartyNetplay/DownloadUpdateDialog.cpp new file mode 100644 index 0000000000..b32f461e37 --- /dev/null +++ b/Source/Core/DolphinQt/MarioPartyNetplay/DownloadUpdateDialog.cpp @@ -0,0 +1,116 @@ +/* +* Dolphin for Mario Party Netplay +* Copyright (C) 2025 Tabitha Hanegan +*/ + +#include "DownloadUpdateDialog.h" +#include +#include +#include +#include +#include +#include + +// Callback function for libcurl to write data +size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) +{ + ((std::string*)userp)->append((char*)contents, size * nmemb); + return size * nmemb; +} + +// Constructor implementation +DownloadUpdateDialog::DownloadUpdateDialog(QWidget* parent, const QUrl& url, const QString& filename) + : QDialog(parent), filename(filename) +{ + // Create UI components + QVBoxLayout* layout = new QVBoxLayout(this); + QLabel* label = new QLabel(QStringLiteral("Downloading %1...").arg(this->filename), this); + progressBar = new QProgressBar(this); + + // Set up the layout + layout->addWidget(label); + layout->addWidget(progressBar); + + // Set the layout for the dialog + setLayout(layout); + + // Start the download + startDownload(url); +} + +// Destructor implementation +DownloadUpdateDialog::~DownloadUpdateDialog() +{ +} + +// Returns the temporary directory +QString DownloadUpdateDialog::GetTempDirectory() const +{ + return temporaryDirectory; +} + +// Returns the filename +QString DownloadUpdateDialog::GetFileName() const +{ + return filename; +} + +// Update the progress bar +void DownloadUpdateDialog::updateProgress(qint64 size, qint64 total) +{ + progressBar->setMaximum(total); + progressBar->setMinimum(0); + progressBar->setValue(size); +} + +// Starts the download process +void DownloadUpdateDialog::startDownload(const QUrl& url) +{ + CURL* curl; + CURLcode res; + std::string readBuffer; + + curl = curl_easy_init(); + if (curl) + { + // Convert QUrl to std::string properly + std::string urlStr = url.toString().toStdString(); + curl_easy_setopt(curl, CURLOPT_URL, urlStr.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); + + // Set the progress function + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); + curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, [](void* clientp, double totalToDownload, double nowDownloaded, double totalToUpload, double nowUploaded) { + DownloadUpdateDialog* dialog = static_cast(clientp); + dialog->updateProgress(static_cast(nowDownloaded), static_cast(totalToDownload)); + return 0; // Return 0 to continue the download + }); + curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, this); + + res = curl_easy_perform(curl); + if (res != CURLE_OK) + { + // Use QString::fromStdString() for proper string conversion + QString errorMsg = QString::fromStdString(std::string(curl_easy_strerror(res))); + QMessageBox::critical(this, tr("Error"), tr("Failed to download update file: %1").arg(errorMsg)); + reject(); + } + else + { + QFile file(filename); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) + { + QMessageBox::critical(this, tr("Error"), tr("Failed to open file for writing.")); + reject(); + } + else + { + file.write(readBuffer.c_str(), readBuffer.size()); + file.close(); + accept(); + } + } + curl_easy_cleanup(curl); + } +} \ No newline at end of file diff --git a/Source/Core/DolphinQt/MarioPartyNetplay/DownloadUpdateDialog.h b/Source/Core/DolphinQt/MarioPartyNetplay/DownloadUpdateDialog.h new file mode 100644 index 0000000000..ecec1c7553 --- /dev/null +++ b/Source/Core/DolphinQt/MarioPartyNetplay/DownloadUpdateDialog.h @@ -0,0 +1,31 @@ +/* +* Dolphin for Mario Party Netplay +* Copyright (C) 2025 Tabitha Hanegan +*/ + +#pragma once + +#include +#include +#include +#include + +class DownloadUpdateDialog : public QDialog +{ + Q_OBJECT + +public: + DownloadUpdateDialog(QWidget* parent, const QUrl& url, const QString& filename); + ~DownloadUpdateDialog(); + + QString GetTempDirectory() const; + QString GetFileName() const; + +private: + QString filename; // Member variable for filename + QString temporaryDirectory; // Member variable for temporary directory + QProgressBar* progressBar; // Declare progressBar as a member variable + + void startDownload(const QUrl& url); + void updateProgress(qint64 size, qint64 total); // Method to update progress +}; diff --git a/Source/Core/DolphinQt/MarioPartyNetplay/InstallUpdateDialog.cpp b/Source/Core/DolphinQt/MarioPartyNetplay/InstallUpdateDialog.cpp new file mode 100644 index 0000000000..0d15b4a988 --- /dev/null +++ b/Source/Core/DolphinQt/MarioPartyNetplay/InstallUpdateDialog.cpp @@ -0,0 +1,222 @@ +/* +* Dolphin for Mario Party Netplay +* Copyright (C) 2025 Tabitha Hanegan +*/ + +#include "InstallUpdateDialog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Constructor implementation +InstallUpdateDialog::InstallUpdateDialog(QWidget *parent, QString installationDirectory, QString temporaryDirectory, QString filename) + : DownloadUpdateDialog(parent, QUrl(), filename), + installationDirectory(installationDirectory), + temporaryDirectory(temporaryDirectory) +{ + // Create UI components + QVBoxLayout* layout = new QVBoxLayout(this); + QLabel* label = new QLabel(QStringLiteral("Installing %1...").arg(this->filename), this); + QProgressBar* progressBar = new QProgressBar(this); + + // Set up the layout + layout->addWidget(label); + layout->addWidget(progressBar); + + setLayout(layout); + + startTimer(100); +} + +// Destructor implementation +InstallUpdateDialog::~InstallUpdateDialog(void) +{ +} + +void InstallUpdateDialog::install(void) +{ + QString fullFilePath = this->temporaryDirectory + QStringLiteral("/") + this->filename; + QString appPath = QCoreApplication::applicationDirPath(); + QString appPid = QString::number(QCoreApplication::applicationPid()); + + // Convert paths to use the right path separator + this->temporaryDirectory = QDir::toNativeSeparators(this->temporaryDirectory); + fullFilePath = QDir::toNativeSeparators(fullFilePath); + appPath = QDir::toNativeSeparators(appPath); + + if (this->filename.endsWith(QStringLiteral(".exe"))) + { + this->label->setText(QStringLiteral("Executing %1...").arg(this->filename)); + QStringList scriptLines = + { + QStringLiteral("@echo off"), + QStringLiteral("("), + QStringLiteral(" echo == Attempting to kill PID ") + appPid, + QStringLiteral(" taskkill /F /PID:") + appPid, + QStringLiteral(" echo == Attempting to start '") + fullFilePath + QStringLiteral("'"), + QStringLiteral(" \"") + fullFilePath + QStringLiteral("\" /CLOSEAPPLICATIONS /NOCANCEL /MERGETASKS=\"!desktopicon\" /SILENT /DIR=\"") + appPath + QStringLiteral("\""), + QStringLiteral(")"), + QStringLiteral("IF NOT ERRORLEVEL 0 ("), + QStringLiteral(" start \"\" cmd /c \"echo Update failed, check the log for more information && pause\""), + QStringLiteral(")"), + // Remove temporary directory at last + QStringLiteral("rmdir /S /Q \"") + this->temporaryDirectory + QStringLiteral("\""), + }; + this->writeAndRunScript(scriptLines); + this->accept(); + return; + } + + this->label->setText(QStringLiteral("Extracting %1...").arg(this->filename)); + this->progressBar->setValue(50); + + QDir dir(this->temporaryDirectory); + if (!dir.mkdir(QStringLiteral("extract"))) + { + QMessageBox::critical(this, QStringLiteral("Error"), QStringLiteral("Failed to create extract directory.")); + this->reject(); + return; + } + + QString extractDirectory = this->temporaryDirectory + QStringLiteral("/extract"); + + if (!unzipFile(fullFilePath.toStdString(), extractDirectory.toStdString())) + { + QMessageBox::critical(this, QStringLiteral("Error"), QStringLiteral("Unzip failed: Unable to extract files.")); + this->reject(); + return; + } + + this->label->setText(QStringLiteral("Executing update script...")); + this->progressBar->setValue(100); + + extractDirectory = QDir::toNativeSeparators(extractDirectory); + + QStringList scriptLines = + { + QStringLiteral("@echo off"), + QStringLiteral("("), + QStringLiteral(" echo == Attempting to remove '") + fullFilePath + QStringLiteral("'"), + QStringLiteral(" del /F /Q \"") + fullFilePath + QStringLiteral("\""), + QStringLiteral(" echo == Attempting to kill PID ") + appPid, + QStringLiteral(" taskkill /F /PID:") + appPid, + QStringLiteral(" echo == Attempting to copy '") + extractDirectory + QStringLiteral("' to '") + appPath + QStringLiteral("'"), + QStringLiteral(" xcopy /S /Y /I \"") + extractDirectory + QStringLiteral("\\*\" \"") + appPath + QStringLiteral("\""), + QStringLiteral(" echo == Attempting to start '") + appPath + QStringLiteral("\\Dolphin-MPN.exe'"), + QStringLiteral(" start \"\" \"") + appPath + QStringLiteral("\\Dolphin-MPN.exe\""), + QStringLiteral(")"), + QStringLiteral("IF NOT ERRORLEVEL 0 ("), + QStringLiteral(" start \"\" cmd /c \"echo Update failed && pause\""), + QStringLiteral(")"), + // Remove temporary directory at last + QStringLiteral("rmdir /S /Q \"") + this->temporaryDirectory + QStringLiteral("\""), + }; + this->writeAndRunScript(scriptLines); +} + +bool InstallUpdateDialog::unzipFile(const std::string& zipFilePath, const std::string& destDir) +{ + unzFile zipFile = unzOpen(zipFilePath.c_str()); + if (!zipFile) + { + return false; // Failed to open zip file + } + + if (unzGoToFirstFile(zipFile) != UNZ_OK) + { + unzClose(zipFile); + return false; // No files in zip + } + + do + { + char filename[256]; + unz_file_info fileInfo; + if (unzGetCurrentFileInfo(zipFile, &fileInfo, filename, sizeof(filename), nullptr, 0, nullptr, 0) != UNZ_OK) + { + unzClose(zipFile); + return false; // Failed to get file info + } + + // Create full path for the extracted file using std::string concatenation + std::string fullPath = destDir + "/" + std::string(filename); + + // Create directories if needed + QString qFullPath = QString::fromStdString(fullPath); + QDir().mkpath(QFileInfo(qFullPath).path()); + + // Open the file in the zip + if (unzOpenCurrentFile(zipFile) != UNZ_OK) + { + unzClose(zipFile); + return false; // Failed to open file in zip + } + + // Write the file to disk + QFile outFile(qFullPath); + if (!outFile.open(QIODevice::WriteOnly)) + { + unzCloseCurrentFile(zipFile); + unzClose(zipFile); + return false; // Failed to create output file + } + + char buffer[8192]; + int bytesRead; + while ((bytesRead = unzReadCurrentFile(zipFile, buffer, sizeof(buffer))) > 0) + { + outFile.write(buffer, bytesRead); + } + + outFile.close(); + unzCloseCurrentFile(zipFile); + } while (unzGoToNextFile(zipFile) == UNZ_OK); + + unzClose(zipFile); + return true; // Successfully unzipped all files +} + +void InstallUpdateDialog::writeAndRunScript(QStringList stringList) +{ + QString scriptPath = this->temporaryDirectory + QStringLiteral("/update.bat"); + + QFile scriptFile(scriptPath); + if (!scriptFile.open(QIODevice::WriteOnly | QIODevice::Text)) + { + QMessageBox::critical(this, tr("Error"), tr("Failed to open file for writing: %1").arg(filename)); + 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::timerEvent(QTimerEvent *event) +{ + this->killTimer(event->timerId()); + this->install(); +} diff --git a/Source/Core/DolphinQt/MarioPartyNetplay/InstallUpdateDialog.h b/Source/Core/DolphinQt/MarioPartyNetplay/InstallUpdateDialog.h new file mode 100644 index 0000000000..39140bbb2c --- /dev/null +++ b/Source/Core/DolphinQt/MarioPartyNetplay/InstallUpdateDialog.h @@ -0,0 +1,40 @@ +/* +* Dolphin for Mario Party Netplay +* Copyright (C) 2025 Tabitha Hanegan +*/ + +#include "DownloadUpdateDialog.h" +#include +#include +#include +#include + +// Forward declarations +QT_BEGIN_NAMESPACE +class QString; +class QLabel; +class QProgressBar; +QT_END_NAMESPACE + +class InstallUpdateDialog : public DownloadUpdateDialog +{ + Q_OBJECT + +public: + InstallUpdateDialog(QWidget *parent, QString installationDirectory, QString temporaryDirectory, QString filename); + ~InstallUpdateDialog(); + + void install(void); + +private: + QString installationDirectory; + QString temporaryDirectory; + QString filename; + QLabel* label; + QProgressBar* progressBar; + + void writeAndRunScript(QStringList stringList); + void launchProcess(QString file, QStringList arguments); + void timerEvent(QTimerEvent* event); + bool unzipFile(const std::string& zipFilePath, const std::string& destDir); +}; diff --git a/Source/Core/DolphinQt/MarioPartyNetplay/UpdateDialog.cpp b/Source/Core/DolphinQt/MarioPartyNetplay/UpdateDialog.cpp new file mode 100644 index 0000000000..e421b7b703 --- /dev/null +++ b/Source/Core/DolphinQt/MarioPartyNetplay/UpdateDialog.cpp @@ -0,0 +1,112 @@ +/* +* Dolphin for Mario Party Netplay +* Copyright (C) 2025 Tabitha Hanegan +*/ + +#include "UpdateDialog.h" +#include "DownloadUpdateDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace UserInterface::Dialog; + +UpdateDialog::UpdateDialog(QWidget *parent, QJsonObject jsonObject, bool forced) + : QDialog(parent) +{ + this->jsonObject = jsonObject; + + // Create UI components + QVBoxLayout* mainLayout = new QVBoxLayout(this); + + // Create and set up the label + label = new QLabel(this); + QString tagName = jsonObject.value(QStringLiteral("tag_name")).toString(); + label->setText(QStringLiteral("%1 Available").arg(tagName)); + mainLayout->addWidget(label); + + // Create and set up the text edit + textEdit = new QTextEdit(this); + textEdit->setText(jsonObject.value(QStringLiteral("body")).toString()); + textEdit->setReadOnly(true); + mainLayout->addWidget(textEdit); + + // Create and set up the button box + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + QPushButton* updateButton = buttonBox->button(QDialogButtonBox::Ok); + updateButton->setText(QStringLiteral("Update")); + mainLayout->addWidget(buttonBox); + + // Connect signals + connect(buttonBox, &QDialogButtonBox::accepted, this, &UpdateDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &UpdateDialog::reject); + + // Set the layout + setLayout(mainLayout); + setWindowTitle(QStringLiteral("Update Available")); + resize(400, 300); +} + +UpdateDialog::~UpdateDialog() +{ +} + +QString UpdateDialog::GetFileName() +{ + return this->filename; +} + +QUrl UpdateDialog::GetUrl() +{ + return this->url; +} + +void UpdateDialog::accept() +{ + QJsonArray jsonArray = jsonObject[QStringLiteral("assets")].toArray(); + QString filenameToDownload; + QUrl urlToDownload; + + for (const QJsonValue& value : jsonArray) + { + QJsonObject object = value.toObject(); + + QString filename = object.value(QStringLiteral("name")).toString(); + QUrl url(object.value(QStringLiteral("browser_download_url")).toString()); + + if (QSysInfo::productType() == QStringLiteral("windows")) { + if (filename.contains(QStringLiteral("win32")) || + filename.contains(QStringLiteral("windows")) || + filename.contains(QStringLiteral("win64"))) + { + filenameToDownload = filename; + urlToDownload = url; + break; + } + } + else if (QSysInfo::productType() == QStringLiteral("macos")) { + if (filename.contains(QStringLiteral("darwin")) || + filename.contains(QStringLiteral("macOS"))) + { + filenameToDownload = filename; + urlToDownload = url; + break; + } + } + } + + this->url = urlToDownload; + this->filename = filenameToDownload; + QDialog::accept(); +} diff --git a/Source/Core/DolphinQt/MarioPartyNetplay/UpdateDialog.h b/Source/Core/DolphinQt/MarioPartyNetplay/UpdateDialog.h new file mode 100644 index 0000000000..4656d1d6bc --- /dev/null +++ b/Source/Core/DolphinQt/MarioPartyNetplay/UpdateDialog.h @@ -0,0 +1,50 @@ +/* +* Dolphin for Mario Party Netplay +* Copyright (C) 2025 Tabitha Hanegan +*/ +#pragma once + +#include +#include +#include + +class QLabel; +class QTextEdit; +class QDialogButtonBox; + +namespace UserInterface +{ +namespace Dialog +{ + +class UpdateDialog : public QDialog +{ + Q_OBJECT + +private: + QJsonObject jsonObject; + QString filename; + QUrl url; + + // UI elements + QLabel* label; + QTextEdit* textEdit; + QDialogButtonBox* buttonBox; + +#ifdef _WIN32 + bool isWin32Setup = false; +#endif // _WIN32 + +public: + explicit UpdateDialog(QWidget *parent, QJsonObject jsonObject, bool forced); + ~UpdateDialog(); + + QString GetFileName(); + QUrl GetUrl(); + +private slots: + void accept() override; +}; + +} // namespace Dialog +} // namespace UserInterface diff --git a/Source/Core/DolphinQt/MenuBar.cpp b/Source/Core/DolphinQt/MenuBar.cpp index 1a37662819..de28567130 100644 --- a/Source/Core/DolphinQt/MenuBar.cpp +++ b/Source/Core/DolphinQt/MenuBar.cpp @@ -621,6 +621,10 @@ void MenuBar::AddHelpMenu() QMenu* help_menu = addMenu(tr("&Help")); QAction* website = help_menu->addAction(tr("&Website")); + + QAction* updaterCheck = help_menu->addAction(tr("Check For &Updates")); + connect(updaterCheck, &QAction::triggered, this, &MenuBar::ShowUpdateDialog); + connect(website, &QAction::triggered, this, []() { QDesktopServices::openUrl(QUrl(QStringLiteral("https://dolphin-emu.org/"))); }); QAction* documentation = help_menu->addAction(tr("Online &Documentation")); @@ -629,12 +633,7 @@ void MenuBar::AddHelpMenu() }); QAction* github = help_menu->addAction(tr("&GitHub Repository")); connect(github, &QAction::triggered, this, []() { - QDesktopServices::openUrl(QUrl(QStringLiteral("https://github.com/dolphin-emu/dolphin"))); - }); - QAction* bugtracker = help_menu->addAction(tr("&Bug Tracker")); - connect(bugtracker, &QAction::triggered, this, []() { - QDesktopServices::openUrl( - QUrl(QStringLiteral("https://bugs.dolphin-emu.org/projects/emulator"))); + QDesktopServices::openUrl(QUrl(QStringLiteral("https://github.com/MarioPartyNetplay/Dolphin-MPN"))); }); #ifndef __APPLE__ diff --git a/Source/Core/DolphinQt/MenuBar.h b/Source/Core/DolphinQt/MenuBar.h index 29457c15f7..cf022f4d87 100644 --- a/Source/Core/DolphinQt/MenuBar.h +++ b/Source/Core/DolphinQt/MenuBar.h @@ -87,6 +87,7 @@ signals: void BootGameCubeIPL(DiscIO::Region region); void ShowFIFOPlayer(); void ShowAboutDialog(); + void ShowUpdateDialog(); void ShowCheatsManager(); void ShowResourcePackManager(); void ShowSkylanderPortal();