diff --git a/src/yuzu/overrides_updater.cpp b/src/yuzu/overrides_updater.cpp new file mode 100644 index 0000000000..585f7c3b7c --- /dev/null +++ b/src/yuzu/overrides_updater.cpp @@ -0,0 +1,179 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "overrides_updater.h" + +#include +#include +#include +#include +#include + +#include "common/logging/log.h" +#include "core/game_overrides.h" +#include "qt_common/config/uisettings.h" + +#include + +#ifdef YUZU_BUNDLED_OPENSSL +#include +#endif + +OverridesUpdater::OverridesUpdater(QWidget* parent) + : QObject(parent), parent_widget(parent) {} + +OverridesUpdater::~OverridesUpdater() = default; + +void OverridesUpdater::CheckAndUpdate() { + if (!UISettings::values.enable_global_overrides.GetValue()) { + return; + } + + if (!UISettings::values.overrides_consent_given.GetValue()) { + ShowConsentDialog(); + return; + } + + if (UISettings::values.auto_update_overrides.GetValue()) { + (void)QtConcurrent::run([this] { + DownloadAndSave(); + }); + } +} + +void OverridesUpdater::ShowConsentDialog() { + QMessageBox msgBox(parent_widget); + msgBox.setWindowTitle(tr("Game Overrides Database")); + msgBox.setIcon(QMessageBox::Question); + msgBox.setText(tr("Would you like to update the game overrides database?")); + msgBox.setInformativeText( + tr("This will download a config file from GitHub to enable critical per-game settings.\n\n" + "Your choice will be saved. You can change this later in:\n" + "Settings -> General -> Enable global game overrides")); + + QCheckBox autoUpdateCheckbox(tr("Automatically update the list"), &msgBox); + autoUpdateCheckbox.setChecked(true); + msgBox.setCheckBox(&autoUpdateCheckbox); + + auto* yesButton = msgBox.addButton(tr("Yes, Enable"), QMessageBox::AcceptRole); + msgBox.addButton(tr("No Thanks"), QMessageBox::RejectRole); + + msgBox.setDefaultButton(yesButton); + msgBox.exec(); + + auto* clicked = msgBox.clickedButton(); + + UISettings::values.overrides_consent_given.SetValue(true); + + if (clicked == yesButton) { + UISettings::values.auto_update_overrides.SetValue(autoUpdateCheckbox.isChecked()); + UISettings::values.enable_global_overrides.SetValue(true); + emit ConfigChanged(); + DownloadOverrides(); + } else { + UISettings::values.auto_update_overrides.SetValue(false); + UISettings::values.enable_global_overrides.SetValue(false); + emit ConfigChanged(); + } +} + +void OverridesUpdater::DownloadOverrides() { + (void)QtConcurrent::run([this] { + const bool success = DownloadAndSave(); + emit UpdateCompleted(success, success + ? tr("Game overrides updated successfully.") + : tr("Failed to download game overrides.")); + }); +} + +std::optional OverridesUpdater::FetchOverridesFile() { + try { + constexpr std::size_t timeout_seconds = 3; + + std::unique_ptr client = std::make_unique(kOverridesUrl); + client->set_connection_timeout(timeout_seconds); + client->set_read_timeout(timeout_seconds); + client->set_write_timeout(timeout_seconds); + +#ifdef YUZU_BUNDLED_OPENSSL + client->load_ca_cert_store(kCert, sizeof(kCert)); +#endif + + httplib::Request request{ + .method = "GET", + .path = kOverridesPath, + }; + + client->set_follow_location(true); + httplib::Result result = client->send(request); + + if (!result) { + LOG_ERROR(Frontend, "GET to {}{} returned null", kOverridesUrl, kOverridesPath); + return {}; + } + + return result.value().body; + } catch (const std::exception& e) { + LOG_ERROR(Frontend, "Failed to fetch overrides: {}", e.what()); + return {}; + } +} + +bool OverridesUpdater::DownloadAndSave() { + LOG_INFO(Frontend, "Checking for game overrides updates..."); + + const auto response = FetchOverridesFile(); + if (!response) { + return false; + } + + const auto& data = *response; + + auto remote_version = ParseVersion(data); + if (!remote_version) { + LOG_ERROR(Frontend, "Downloaded overrides file has invalid format (no version header)"); + return false; + } + + auto local_version = Core::GameOverrides::GetOverridesFileVersion(); + if (local_version && *local_version >= *remote_version) { + LOG_INFO(Frontend, "Game overrides are up to date (v{})", *local_version); + return true; + } + + const auto path = Core::GameOverrides::GetOverridesPath(); + std::filesystem::create_directories(path.parent_path()); + + std::ofstream file(path, std::ios::binary); + if (!file.is_open()) { + LOG_ERROR(Frontend, "Failed to write overrides file: {}", path.string()); + return false; + } + + file.write(data.data(), static_cast(data.size())); + file.close(); + + LOG_INFO(Frontend, "Game overrides updated to version {}", *remote_version); + return true; +} + +std::optional OverridesUpdater::ParseVersion(const std::string& data) { + // Look for "; version=X" on first line + const auto newline_pos = data.find('\n'); + const std::string first_line = (newline_pos != std::string::npos) + ? data.substr(0, newline_pos) + : data; + + constexpr const char* prefix = "; version="; + const auto prefix_pos = first_line.find(prefix); + if (prefix_pos == std::string::npos) { + return {}; + } + + const auto version_str = first_line.substr(prefix_pos + std::strlen(prefix)); + try { + return static_cast(std::stoul(version_str)); + } catch (...) { + return {}; + } +} diff --git a/src/yuzu/overrides_updater.h b/src/yuzu/overrides_updater.h new file mode 100644 index 0000000000..8dab674bdd --- /dev/null +++ b/src/yuzu/overrides_updater.h @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +class QWidget; + +class OverridesUpdater : public QObject { + Q_OBJECT + +public: + explicit OverridesUpdater(QWidget* parent = nullptr); + ~OverridesUpdater() override; + + void CheckAndUpdate(); + void DownloadOverrides(); + +signals: + void ConfigChanged(); + void UpdateCompleted(bool success, const QString& message); + +private: + void ShowConsentDialog(); + bool DownloadAndSave(); + static std::optional ParseVersion(const std::string& data); + static std::optional FetchOverridesFile(); + + QWidget* parent_widget{}; + + static constexpr const char* kOverridesUrl = "https://raw.githubusercontent.com"; + static constexpr const char* kOverridesPath = "/eden-emulator/eden-overrides/refs/heads/master/overrides.ini"; +};