2 changed files with 215 additions and 0 deletions
@ -0,0 +1,179 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
|
||||
|
#include "overrides_updater.h"
|
||||
|
|
||||
|
#include <QCheckBox>
|
||||
|
#include <QMessageBox>
|
||||
|
#include <QPushButton>
|
||||
|
#include <QtConcurrent>
|
||||
|
#include <fstream>
|
||||
|
|
||||
|
#include "common/logging/log.h"
|
||||
|
#include "core/game_overrides.h"
|
||||
|
#include "qt_common/config/uisettings.h"
|
||||
|
|
||||
|
#include <httplib.h>
|
||||
|
|
||||
|
#ifdef YUZU_BUNDLED_OPENSSL
|
||||
|
#include <openssl/cert.h>
|
||||
|
#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<std::string> OverridesUpdater::FetchOverridesFile() { |
||||
|
try { |
||||
|
constexpr std::size_t timeout_seconds = 3; |
||||
|
|
||||
|
std::unique_ptr<httplib::Client> client = std::make_unique<httplib::Client>(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<std::streamsize>(data.size())); |
||||
|
file.close(); |
||||
|
|
||||
|
LOG_INFO(Frontend, "Game overrides updated to version {}", *remote_version); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
std::optional<std::uint32_t> 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::uint32_t>(std::stoul(version_str)); |
||||
|
} catch (...) { |
||||
|
return {}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <QObject> |
||||
|
#include <QString> |
||||
|
#include <optional> |
||||
|
|
||||
|
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<std::uint32_t> ParseVersion(const std::string& data); |
||||
|
static std::optional<std::string> 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"; |
||||
|
}; |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue