diff --git a/.github/workflows/windows-qt.yml b/.github/workflows/windows-qt.yml index fee202b5c..3b20bf4a7 100644 --- a/.github/workflows/windows-qt.yml +++ b/.github/workflows/windows-qt.yml @@ -11,15 +11,18 @@ on: env: BUILD_TYPE: Release + TARGET_REPO: "DanielSvoboda/teste" + SHADPS4_TOKEN: ${{ secrets.SHADPS4_TOKEN }} jobs: build: runs-on: windows-latest steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 with: - submodules: recursive + submodules: recursive - name: Setup Qt uses: jurplel/install-qt-action@v4 @@ -33,35 +36,86 @@ jobs: - name: Cache CMake dependency source code uses: actions/cache@v4 env: - cache-name: ${{ runner.os }}-qt-cache-cmake-dependency-sources + cache-name: ${{ runner.os }}-qt-cache-cmake-dependency-sources with: - path: | - ${{github.workspace}}/build - key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} - restore-keys: | - ${{ env.cache-name }}- + path: | + ${{ github.workspace }}/build + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + restore-keys: | + ${{ env.cache-name }}- - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -T ClangCL -DENABLE_QT_GUI=ON + run: cmake -B ${{ github.workspace }}/build -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} -T ClangCL -DENABLE_QT_GUI=ON - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel + run: cmake --build ${{ github.workspace }}/build --config ${{ env.BUILD_TYPE }} --parallel - name: Deploy run: | mkdir upload move build/Release/shadPS4.exe upload windeployqt --dir upload upload/shadPS4.exe - + - name: Get date and git hash id: vars shell: pwsh run: | - echo "date=$(Get-Date -Format 'yyyy-MM-dd')" >> $env:GITHUB_OUTPUT - echo "shorthash=$(git rev-parse --short HEAD)" >> $env:GITHUB_OUTPUT + $date = Get-Date -Format 'yyyy-MM-dd' + $shorthash = git rev-parse --short HEAD + echo "date=$date" >> $env:GITHUB_ENV + echo "shorthash=$shorthash" >> $env:GITHUB_ENV + + - name: Create Zip Archive + if: github.event_name == 'push' + run: | + $zipName = "shadPS4-win64-qt-$($env:date)-$($env:shorthash).zip" + Compress-Archive -Path upload/* -DestinationPath $zipName + echo "zipName=$zipName" >> $env:GITHUB_ENV - name: Upload executable uses: actions/upload-artifact@v4 with: name: shadps4-win64-qt-${{ steps.vars.outputs.date }}-${{ steps.vars.outputs.shorthash }} path: upload + + - name: Upload Zip Archive + if: github.event_name == 'push' + uses: actions/upload-artifact@v4 + with: + name: ${{ env.zipName }} + path: ${{ env.zipName }} + + - name: Create GitHub Release + if: github.event_name == 'push' + id: create_release + shell: pwsh + run: | + $date = $env:date + $shorthash = $env:shorthash + $uri = "https://api.github.com/repos/${{ env.TARGET_REPO }}/releases" + $headers = @{ + "Authorization" = "token $($env:SHADPS4_TOKEN)" + "Accept" = "application/vnd.github.v3+json" + } + $body = @{ + "tag_name" = "build-$date-$shorthash" + "name" = "shadPS4-win64-qt-$($env:date)-$($env:shorthash)" + "draft" = $false + "prerelease" = $false + } | ConvertTo-Json + + $response = Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Body $body -ContentType "application/json" + $response.id | Out-File -FilePath release_id.txt + + - name: Upload to GitHub Release + if: github.event_name == 'push' + shell: pwsh + run: | + $release_id = Get-Content release_id.txt + $filePath = $env:zipName + $url = "https://uploads.github.com/repos/${{ env.TARGET_REPO }}/releases/$release_id/assets?name=$filePath" + Write-Output "Uploading $filePath to $url" + Invoke-RestMethod -Uri $url -Method Post -Headers @{ + "Authorization" = "token $($env:SHADPS4_TOKEN)" + "Content-Type" = "application/octet-stream" + } -InFile $filePath \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 0cbfe9626..763000fa2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -628,6 +628,8 @@ set(QT_GUI src/qt_gui/about_dialog.cpp src/qt_gui/about_dialog.ui src/qt_gui/cheats_patches.cpp src/qt_gui/cheats_patches.h + src/qt_gui/checkUpdate.cpp + src/qt_gui/checkUpdate.h src/qt_gui/memory_patcher.cpp src/qt_gui/memory_patcher.h src/qt_gui/main_window_ui.h diff --git a/src/common/config.cpp b/src/common/config.cpp index fb6ee120a..893b1717e 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -21,6 +21,7 @@ static bool useSpecialPad = false; static int specialPadClass = 1; static bool isDebugDump = false; static bool isShowSplash = false; +static bool isAutoUpdate = true; static bool isNullGpu = false; static bool shouldCopyGPUBuffers = false; static bool shouldDumpShaders = false; @@ -102,6 +103,10 @@ bool showSplash() { return isShowSplash; } +bool autoUpdate() { + return isAutoUpdate; +} + bool nullGpu() { return isNullGpu; } @@ -170,6 +175,10 @@ void setShowSplash(bool enable) { isShowSplash = enable; } +void setAutoUpdate(bool enable) { + isAutoUpdate = enable; +} + void setNullGpu(bool enable) { isNullGpu = enable; } @@ -365,6 +374,7 @@ void load(const std::filesystem::path& path) { logType = toml::find_or(general, "logType", "sync"); userName = toml::find_or(general, "userName", "shadPS4"); isShowSplash = toml::find_or(general, "showSplash", true); + isAutoUpdate = toml::find_or(general, "autoUpdate", true); } if (data.contains("Input")) { @@ -457,6 +467,7 @@ void save(const std::filesystem::path& path) { data["General"]["logType"] = logType; data["General"]["userName"] = userName; data["General"]["showSplash"] = isShowSplash; + data["General"]["autoUpdate"] = isAutoUpdate; data["Input"]["useSpecialPad"] = useSpecialPad; data["Input"]["specialPadClass"] = specialPadClass; data["GPU"]["screenWidth"] = screenWidth; @@ -511,6 +522,7 @@ void setDefaultValues() { specialPadClass = 1; isDebugDump = false; isShowSplash = false; + isAutoUpdate = true; isNullGpu = false; shouldDumpShaders = false; shouldDumpPM4 = false; @@ -526,4 +538,4 @@ void setDefaultValues() { gpuId = -1; } -} // namespace Config +} // namespace Config \ No newline at end of file diff --git a/src/common/config.h b/src/common/config.h index 7e717fe71..e7a0e075b 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -26,6 +26,7 @@ s32 getGpuId(); bool debugDump(); bool showSplash(); +bool autoUpdate(); bool nullGpu(); bool copyGPUCmdBuffers(); bool dumpShaders(); @@ -35,6 +36,7 @@ u32 vblankDiv(); void setDebugDump(bool enable); void setShowSplash(bool enable); +void setAutoUpdate(bool enable); void setNullGpu(bool enable); void setCopyGPUCmdBuffers(bool enable); void setDumpShaders(bool enable); diff --git a/src/qt_gui/checkUpdate.cpp b/src/qt_gui/checkUpdate.cpp new file mode 100644 index 000000000..c6057ae30 --- /dev/null +++ b/src/qt_gui/checkUpdate.cpp @@ -0,0 +1,289 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "checkUpdate.h" +//#include "externals/minizip-ng/mz.h" +//#include "externals/minizip-ng/mz_strm.h" +//#include "externals/minizip-ng/mz_strm_buf.h" +//#include "externals/minizip-ng/mz_strm_mem.h" +//#include "externals/minizip-ng/mz_strm_os.h" +//#include "externals/minizip-ng/mz_zip.h" + +using namespace Common::FS; +namespace fs = std::filesystem; + +CheckUpdate::CheckUpdate(QWidget* parent) : QDialog(parent) { + setWindowTitle("Update Check"); + CheckForUpdates(); +} + +void CheckUpdate::CheckForUpdates() { + QNetworkAccessManager* manager = new QNetworkAccessManager(this); + QNetworkRequest request( + QUrl("https://api.github.com/repos/DanielSvoboda/teste/releases/latest")); + QNetworkReply* reply = manager->get(request); + + connect(reply, &QNetworkReply::finished, this, [this, reply]() { + if (reply->error() != QNetworkReply::NoError) { + QMessageBox::warning(this, tr("Error"), + QString(tr("Network error: ") + "\n%1").arg(reply->errorString())); + reply->deleteLater(); + return; + } + + QByteArray response = reply->readAll(); + QJsonDocument jsonDoc(QJsonDocument::fromJson(response)); + QJsonObject jsonObj = jsonDoc.object(); + + QString downloadUrl = + jsonObj["assets"].toArray().first().toObject()["browser_download_url"].toString(); + QString latestVersion = QFileInfo(downloadUrl).baseName(); + QString latestRev = latestVersion.right(7); + + QString currentRev = QString::fromStdString(Common::g_scm_rev).left(7); + + if (latestRev == currentRev) { + setupUI_NoUpdate(); + } else { + updateDownloadUrl = downloadUrl; + setupUI_UpdateAvailable(downloadUrl); + } + + reply->deleteLater(); + }); +} + +void CheckUpdate::setupUI_NoUpdate() { + QVBoxLayout* layout = new QVBoxLayout(this); + + QLabel* noUpdateLabel = new QLabel("Your version is already up to date!.", this); + layout->addWidget(noUpdateLabel); + + autoUpdateCheckBox = new QCheckBox("Auto Update", this); + layout->addWidget(autoUpdateCheckBox); + + autoUpdateCheckBox->setChecked(Config::autoUpdate()); + + connect(autoUpdateCheckBox, &QCheckBox::stateChanged, this, [](int state) { + const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); + Config::setAutoUpdate(state == Qt::Checked); + Config::save(user_dir / "config.toml"); + }); + + setLayout(layout); +} + +void CheckUpdate::setupUI_UpdateAvailable(const QString& downloadUrl) { + QVBoxLayout* layout = new QVBoxLayout(this); + + QLabel* updateLabel = + new QLabel("An update is available!\nDo you want to download and install it?", this); + updateLabel->setAlignment(Qt::AlignCenter); + layout->addWidget(updateLabel); + + // Criar um layout horizontal para os botões + QHBoxLayout* buttonLayout = new QHBoxLayout(); + + yesButton = new QPushButton("Yes", this); + noButton = new QPushButton("No", this); + + // Adicionar os botões ao layout horizontal + buttonLayout->addWidget(yesButton); + buttonLayout->addWidget(noButton); + + // Adicionar o layout horizontal ao layout principal + layout->addLayout(buttonLayout); + + connect(yesButton, &QPushButton::clicked, this, + [this]() { DownloadAndInstallUpdate(updateDownloadUrl); }); + + connect(noButton, &QPushButton::clicked, this, [this]() { close(); }); + + autoUpdateCheckBox = new QCheckBox("Auto Update (Check at Startup)", this); + layout->addWidget(autoUpdateCheckBox); + + autoUpdateCheckBox->setChecked(Config::autoUpdate()); + + connect(autoUpdateCheckBox, &QCheckBox::stateChanged, this, [](int state) { + const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); + Config::setAutoUpdate(state == Qt::Checked); + Config::save(user_dir / "config.toml"); + }); + + setLayout(layout); +} + +void CheckUpdate::DownloadAndInstallUpdate(const QString& url) { + QNetworkAccessManager* manager = new QNetworkAccessManager(this); + QNetworkRequest request(url); + QNetworkReply* reply = manager->get(request); + + connect(reply, &QNetworkReply::finished, this, [this, reply]() { + if (reply->error() != QNetworkReply::NoError) { + QMessageBox::warning(this, tr("Error"), + QString(tr("Network error: ") + "\n%1").arg(reply->errorString())); + reply->deleteLater(); + return; + } + + QString userPath = + QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::UserDir).string()); + QString tempDownloadPath = userPath + "/temp_download_update"; + QDir dir(tempDownloadPath); + if (!dir.exists()) { + dir.mkpath("."); + } + + QString downloadPath = tempDownloadPath + "/temp_download_update.zip"; + QFile file(downloadPath); + if (file.open(QIODevice::WriteOnly)) { + file.write(reply->readAll()); + file.close(); + QMessageBox::information(this, tr("Download Complete"), + tr("The update has been downloaded. Starting installation.")); + Unzip(); + } else { + QMessageBox::warning( + this, tr("Error"), + QString(tr("Failed to save the update file at") + "\n%1").arg(downloadPath)); + } + + reply->deleteLater(); + }); +} + +void CheckUpdate::Unzip() { + //QString userPath = + // QString::fromStdString(Common::FS::GetUserPath(Common::FS::PathType::UserDir).string()); + //QString tempDirPath = userPath + "/temp_download_update"; + //QString zipFilePath = tempDirPath + "/temp_download_update.zip"; + + //if (!fs::exists(zipFilePath.toStdString())) { + // QMessageBox::warning(this, tr("Error"), + // QString(tr("Arquivo zip não encontrado:") + "\n%1").arg(zipFilePath)); + // return; + //} + + //void* zip_reader = NULL; + //void* stream = NULL; + + //mz_stream_os_create(); + //if (mz_stream_os_open(stream, zipFilePath.toStdString().c_str(), MZ_OPEN_MODE_READ) != MZ_OK) { + + // QMessageBox::warning(this, tr("Error"), + // QString(tr("Erro ao abrir o arquivo zip") + "\n%1").arg(zipFilePath)); + // mz_stream_os_delete(&stream); + // return; + //} + + //// Criar o leitor do ZIP + //mz_zip_create(); + //if (mz_zip_open(zip_reader, stream, MZ_OPEN_MODE_READ) != MZ_OK) { + + // QMessageBox::warning( + // this, tr("Error"), + // QString(tr("Erro ao abrir o arquivo zip com mz_zip_open") + "\n%1").arg(zipFilePath)); + + // mz_zip_delete(&zip_reader); + // mz_stream_os_delete(&stream); + // return; + //} + + //// Passa por (arquivo ou diretório) no arquivo zip + //while (mz_zip_goto_next_entry(zip_reader) == MZ_OK) { + // mz_zip_file* file_info = nullptr; + // if (mz_zip_entry_get_info(zip_reader, &file_info) != MZ_OK) { + // QMessageBox::warning( + // this, tr("Error"), + // QString(tr("Erro ao obter informações da entrada no zip.") + "\n%1") + // .arg(zipFilePath)); + // continue; + // } + + // QString caminho_arquivo = tempDirPath + "/" + file_info->filename; + + // // Verifique se a entrada do zip é um diretório + // if (mz_zip_entry_is_dir(zip_reader) == MZ_OK) { + // fs::create_directories(caminho_arquivo.toStdString()); + // QMessageBox::warning(this, tr("Error"), + // QString(tr("Diretório criado:") + "\n%1").arg(caminho_arquivo)); + // } else { + // // Certifique-se de que o diretório pai do arquivo exista + // fs::create_directories(fs::path(caminho_arquivo.toStdString()).parent_path()); + + // // Abra o arquivo de saída para gravação + // std::ofstream arquivo_saida(caminho_arquivo.toStdString(), std::ios::binary); + // if (!arquivo_saida) { + + // QMessageBox::warning( + // this, tr("Error"), + // QString(tr("Erro ao abrir o arquivo de saída:") + "\n%1").arg(caminho_arquivo)); + + // mz_zip_entry_close(zip_reader); + // continue; + // } + + // // Buffer temporário para leitura dos dados + // char buffer[4096]; + // int32_t bytes_lidos; + + // // Leia os dados do arquivo zip e escreva no arquivo de saída + // while ((bytes_lidos = mz_zip_entry_read(zip_reader, buffer, sizeof(buffer))) > 0) { + // arquivo_saida.write(buffer, bytes_lidos); + // } + // arquivo_saida.close(); + // QMessageBox::warning(this, tr("Error"), + // QString(tr("Arquivo extraído:") + "\n%1").arg(caminho_arquivo)); + // } + // mz_zip_entry_close(zip_reader); + //} + + //// Fechar o arquivo zip + //mz_zip_close(zip_reader); + //mz_zip_delete(&zip_reader); + //mz_stream_os_close(stream); + //mz_stream_os_delete(&stream); + + //QMessageBox::warning(this, tr("Error"), + // QString(tr("Descompactação concluída em:") + "\n%1").arg(tempDirPath)); +} + +//// Criar e executar o arquivo batch para atualizar +// QFile batFile(tempDirPath + "/update.bat"); +// if (batFile.open(QIODevice::WriteOnly)) { +// QTextStream out(&batFile); +// out << "@echo off\n"; +// out << "taskkill /IM shadps4.exe /F\n"; +// out << "move /Y \"" << tempDirPath << "\\*\" \"" << userPath << "\"\n"; +// out << "start \"\" \"" << userPath << "\\shadps4.exe\"\n"; +// batFile.close(); + +// if (!QProcess::startDetached(batFile.fileName())) { +// QMessageBox::warning( +// this, tr("Error"), +// tr("Failed to start the update batch file:\n%1").arg(batFile.fileName())); +// } +//} else { +// QMessageBox::warning( +// this, tr("Error"), +// tr("Failed to create the update batch file:\n%1").arg(batFile.fileName())); +//} diff --git a/src/qt_gui/checkUpdate.h b/src/qt_gui/checkUpdate.h new file mode 100644 index 000000000..88ffb02a8 --- /dev/null +++ b/src/qt_gui/checkUpdate.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef CHECKUPDATE_H +#define CHECKUPDATE_H + +#include +#include + +class CheckUpdate : public QDialog { + Q_OBJECT + +public: + explicit CheckUpdate(QWidget* parent = nullptr); + +private slots: + void CheckForUpdates(); + void DownloadAndInstallUpdate(const QString& url); + void Unzip(); + +private: + void setupUI_NoUpdate(); + void setupUI_UpdateAvailable(const QString& downloadUrl); + + QCheckBox* autoUpdateCheckBox; + QPushButton* yesButton; + QPushButton* noButton; + QString updateDownloadUrl; +}; + +#endif // CHECKUPDATE_H diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index e5b502c58..571c87150 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -6,6 +6,7 @@ #include "about_dialog.h" #include "cheats_patches.h" +#include "checkUpdate.h" #include "common/io_file.h" #include "common/string_util.h" #include "common/version.h" @@ -46,6 +47,7 @@ bool MainWindow::Init() { this->show(); // load game list LoadGameLists(); + CheckUpdateMain(true); auto end = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast(end - start); @@ -160,6 +162,18 @@ void MainWindow::LoadGameLists() { } } +void MainWindow::CheckUpdateMain(bool check) { +#ifdef _WIN64 + if (check) { + if (!Config::autoUpdate()) { + return; + } + } + auto checkUpdate = new CheckUpdate(this); + checkUpdate->show(); +#endif +} + void MainWindow::GetPhysicalDevices() { Vulkan::Instance instance(false, false); auto physical_devices = instance.GetPhysicalDevices(); @@ -221,6 +235,8 @@ void MainWindow::CreateConnects() { settingsDialog->exec(); }); + connect(ui->updaterAct, &QAction::triggered, this, [this]() { CheckUpdateMain(false); }); + connect(ui->aboutAct, &QAction::triggered, this, [this]() { auto aboutDialog = new AboutDialog(this); aboutDialog->exec(); @@ -845,6 +861,7 @@ void MainWindow::SetUiIcons(bool isWhite) { ui->bootInstallPkgAct->setIcon(RecolorIcon(ui->bootInstallPkgAct->icon(), isWhite)); ui->bootGameAct->setIcon(RecolorIcon(ui->bootGameAct->icon(), isWhite)); ui->exitAct->setIcon(RecolorIcon(ui->exitAct->icon(), isWhite)); + ui->updaterAct->setIcon(RecolorIcon(ui->updaterAct->icon(), isWhite)); ui->aboutAct->setIcon(RecolorIcon(ui->aboutAct->icon(), isWhite)); ui->setlistModeListAct->setIcon(RecolorIcon(ui->setlistModeListAct->icon(), isWhite)); ui->setlistModeGridAct->setIcon(RecolorIcon(ui->setlistModeGridAct->icon(), isWhite)); @@ -949,4 +966,4 @@ void MainWindow::OnLanguageChanged(const std::string& locale) { Config::setEmulatorLanguage(locale); LoadTranslation(); -} +} \ No newline at end of file diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index d3b83e619..76a9a9ba1 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -54,6 +54,7 @@ private: void CreateDockWindows(); void GetPhysicalDevices(); void LoadGameLists(); + void CheckUpdateMain(bool check); void CreateConnects(); void SetLastUsedTheme(); void SetLastIconSizeBullet(); diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 6ddc4155e..c2235435b 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -26,6 +26,7 @@ public: QAction* downloadCheatsPatchesAct; QAction* dumpGameListAct; QAction* pkgViewerAct; + QAction* updaterAct; QAction* aboutAct; QAction* configureAct; QAction* setThemeDark; @@ -127,6 +128,8 @@ public: pkgViewerAct = new QAction(MainWindow); pkgViewerAct->setObjectName("pkgViewer"); pkgViewerAct->setIcon(QIcon(":images/file_icon.png")); + updaterAct = new QAction(MainWindow); + updaterAct->setObjectName("updaterAct"); aboutAct = new QAction(MainWindow); aboutAct->setObjectName("aboutAct"); aboutAct->setIcon(QIcon(":images/about_icon.png")); @@ -285,6 +288,7 @@ public: menuUtils->addAction(downloadCheatsPatchesAct); menuUtils->addAction(dumpGameListAct); menuUtils->addAction(pkgViewerAct); + menuAbout->addAction(updaterAct); menuAbout->addAction(aboutAct); retranslateUi(MainWindow); @@ -299,6 +303,7 @@ public: bootInstallPkgAct->setText( QCoreApplication::translate("MainWindow", "Install Packages (PKG)", nullptr)); bootGameAct->setText(QCoreApplication::translate("MainWindow", "Boot Game", nullptr)); + updaterAct->setText(QCoreApplication::translate("MainWindow", "Check Update", nullptr)); aboutAct->setText(QCoreApplication::translate("MainWindow", "About shadPS4", nullptr)); configureAct->setText(QCoreApplication::translate("MainWindow", "Configure...", nullptr)); #if QT_CONFIG(tooltip)