Compare commits

...

4 Commits

Author SHA1 Message Date
Maufeat c04bdcbc6b forgot the files 2026-02-08 06:19:33 +01:00
Maufeat 23b5b23b8b add in menubar "Tools -> Download Override Settings" 2026-02-08 06:15:56 +01:00
Maufeat d5be379a45 add prompt and function for auto update override settings 2026-02-08 06:01:06 +01:00
Maufeat 2e7832762e add game override support with a .ini file 2026-02-08 05:04:18 +01:00
15 changed files with 960 additions and 23 deletions
+1
View File
@@ -795,6 +795,7 @@ struct Values {
// Per-game overrides
bool use_squashed_iterated_blend;
bool enable_global_overrides{true};
Setting<bool> enable_overlay{linkage, false, "enable_overlay", Category::Core};
};
+2
View File
@@ -17,6 +17,8 @@ add_library(core STATIC
constants.h
core.cpp
core.h
game_overrides.cpp
game_overrides.h
game_settings.cpp
game_settings.h
core_timing.cpp
+2 -4
View File
@@ -371,7 +371,8 @@ struct System::Impl {
LOG_ERROR(Core, "Failed to find program id for ROM");
}
GameSettings::LoadOverrides(program_id, gpu_core->Renderer());
GameSettings::LoadOverrides(program_id, gpu_core->Renderer(),
Settings::values.enable_global_overrides);
if (auto room_member = Network::GetRoomMember().lock()) {
Network::GameInfo game_info;
game_info.name = name;
@@ -385,9 +386,6 @@ struct System::Impl {
void ShutdownMainProcess() {
SetShuttingDown(true);
// Reset per-game flags
Settings::values.use_squashed_iterated_blend = false;
is_powered_on = false;
exit_locked = false;
exit_requested = false;
+615
View File
@@ -0,0 +1,615 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/game_overrides.h"
#include "core/game_settings.h"
#include <algorithm>
#include <array>
#include <cctype>
#include <charconv>
#include <fstream>
#include <sstream>
#include <unordered_map>
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "video_core/renderer_base.h"
namespace Core::GameOverrides {
namespace {
std::string Trim(std::string_view str) {
const auto start = str.find_first_not_of(" \t\r\n");
if (start == std::string_view::npos) return "";
const auto end = str.find_last_not_of(" \t\r\n");
return std::string(str.substr(start, end - start + 1));
}
std::string ToLower(std::string str) {
std::transform(str.begin(), str.end(), str.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return str;
}
bool ToBool(const std::string& value) {
return value == "true" || value == "1" || value == "yes" || value == "on";
}
GameSettings::OS DetectOS() {
#if defined(_WIN32)
return GameSettings::OS::Windows;
#elif defined(__FIREOS__)
return GameSettings::OS::FireOS;
#elif defined(__ANDROID__)
return GameSettings::OS::Android;
#elif defined(__OHOS__)
return GameSettings::OS::HarmonyOS;
#elif defined(__HAIKU__)
return GameSettings::OS::HaikuOS;
#elif defined(__DragonFly__)
return GameSettings::OS::DragonFlyBSD;
#elif defined(__NetBSD__)
return GameSettings::OS::NetBSD;
#elif defined(__OpenBSD__)
return GameSettings::OS::OpenBSD;
#elif defined(_AIX)
return GameSettings::OS::AIX;
#elif defined(__managarm__)
return GameSettings::OS::Managarm;
#elif defined(__redox__)
return GameSettings::OS::RedoxOS;
#elif defined(__APPLE__)
return GameSettings::OS::MacOS;
#elif defined(__FreeBSD__)
return GameSettings::OS::FreeBSD;
#elif defined(__sun) && defined(__SVR4)
return GameSettings::OS::Solaris;
#elif defined(__linux__)
return GameSettings::OS::Linux;
#else
return GameSettings::OS::Unknown;
#endif
}
GameSettings::GPUVendor DetectGPUVendor(const std::string& vendor_string) {
std::string gpu = vendor_string;
std::transform(gpu.begin(), gpu.end(), gpu.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
if (gpu.find("nvidia") != std::string::npos || gpu.find("geforce") != std::string::npos) {
return GameSettings::GPUVendor::Nvidia;
}
if (gpu.find("amd") != std::string::npos || gpu.find("radeon") != std::string::npos ||
gpu.find("radv") != std::string::npos) {
return GameSettings::GPUVendor::AMD;
}
if (gpu.find("intel") != std::string::npos) {
return GameSettings::GPUVendor::Intel;
}
if (gpu.find("apple") != std::string::npos || gpu.find("molten") != std::string::npos) {
return GameSettings::GPUVendor::Apple;
}
if (gpu.find("qualcomm") != std::string::npos || gpu.find("adreno") != std::string::npos ||
gpu.find("turnip") != std::string::npos) {
return GameSettings::GPUVendor::Qualcomm;
}
if (gpu.find("mali") != std::string::npos) {
return GameSettings::GPUVendor::ARM;
}
if (gpu.find("powervr") != std::string::npos || gpu.find("pvr") != std::string::npos) {
return GameSettings::GPUVendor::Imagination;
}
if (gpu.find("microsoft") != std::string::npos) {
return GameSettings::GPUVendor::Microsoft;
}
return GameSettings::GPUVendor::Unknown;
}
std::string OSToString(GameSettings::OS os) {
switch (os) {
case GameSettings::OS::Windows: return "windows";
case GameSettings::OS::Linux: return "linux";
case GameSettings::OS::MacOS: return "macos";
case GameSettings::OS::Android: return "android";
case GameSettings::OS::FireOS: return "fireos";
case GameSettings::OS::HarmonyOS: return "harmonyos";
case GameSettings::OS::FreeBSD: return "freebsd";
case GameSettings::OS::DragonFlyBSD: return "dragonflybsd";
case GameSettings::OS::NetBSD: return "netbsd";
case GameSettings::OS::OpenBSD: return "openbsd";
case GameSettings::OS::HaikuOS: return "haikuos";
case GameSettings::OS::AIX: return "aix";
case GameSettings::OS::Managarm: return "managarm";
case GameSettings::OS::RedoxOS: return "redoxos";
case GameSettings::OS::Solaris: return "solaris";
default: return "unknown";
}
}
std::string VendorToString(GameSettings::GPUVendor vendor) {
switch (vendor) {
case GameSettings::GPUVendor::Nvidia: return "nvidia";
case GameSettings::GPUVendor::AMD: return "amd";
case GameSettings::GPUVendor::Intel: return "intel";
case GameSettings::GPUVendor::Apple: return "apple";
case GameSettings::GPUVendor::Qualcomm: return "qualcomm";
case GameSettings::GPUVendor::ARM: return "arm";
case GameSettings::GPUVendor::Imagination: return "imagination";
case GameSettings::GPUVendor::Microsoft: return "microsoft";
default: return "unknown";
}
}
#define SET_OVERRIDE(setting, new_value) \
do { \
s.setting.SetGlobal(false); \
s.setting.SetValue(new_value); \
} while(0)
void ApplySetting(const std::string& key, const std::string& value) {
const std::string k = ToLower(key);
const std::string v = ToLower(value);
auto& s = Settings::values;
if (k == "backend" || k == "renderer_backend") {
if (v == "vulkan") {
SET_OVERRIDE(renderer_backend, Settings::RendererBackend::Vulkan);
} else if (v == "opengl" || v == "opengl_glsl") {
SET_OVERRIDE(renderer_backend, Settings::RendererBackend::OpenGL_GLSL);
} else if (v == "opengl_glasm") {
SET_OVERRIDE(renderer_backend, Settings::RendererBackend::OpenGL_GLASM);
} else if (v == "opengl_spirv") {
SET_OVERRIDE(renderer_backend, Settings::RendererBackend::OpenGL_SPIRV);
} else if (v == "null") {
SET_OVERRIDE(renderer_backend, Settings::RendererBackend::Null);
}
}
else if (k == "vsync") {
SET_OVERRIDE(vsync_mode, ToBool(v) ? Settings::VSyncMode::Fifo : Settings::VSyncMode::Immediate);
}
else if (k == "vsync_mode") {
if (v == "immediate" || v == "off") {
SET_OVERRIDE(vsync_mode, Settings::VSyncMode::Immediate);
} else if (v == "fifo" || v == "on") {
SET_OVERRIDE(vsync_mode, Settings::VSyncMode::Fifo);
} else if (v == "fifo_relaxed") {
SET_OVERRIDE(vsync_mode, Settings::VSyncMode::FifoRelaxed);
} else if (v == "mailbox") {
SET_OVERRIDE(vsync_mode, Settings::VSyncMode::Mailbox);
}
}
else if (k == "gpu_accuracy" || k == "accuracy_level") {
if (v == "low" || v == "fast") {
SET_OVERRIDE(gpu_accuracy, Settings::GpuAccuracy::Low);
} else if (v == "medium" || v == "balanced" || v == "normal") {
SET_OVERRIDE(gpu_accuracy, Settings::GpuAccuracy::Medium);
} else if (v == "high" || v == "accurate") {
SET_OVERRIDE(gpu_accuracy, Settings::GpuAccuracy::High);
}
}
else if (k == "cpu_accuracy") {
if (v == "auto") {
SET_OVERRIDE(cpu_accuracy, Settings::CpuAccuracy::Auto);
} else if (v == "accurate") {
SET_OVERRIDE(cpu_accuracy, Settings::CpuAccuracy::Accurate);
} else if (v == "unsafe") {
SET_OVERRIDE(cpu_accuracy, Settings::CpuAccuracy::Unsafe);
} else if (v == "paranoid") {
SET_OVERRIDE(cpu_accuracy, Settings::CpuAccuracy::Paranoid);
}
}
else if (k == "astc_decode_mode" || k == "accelerate_astc") {
if (v == "cpu") {
SET_OVERRIDE(accelerate_astc, Settings::AstcDecodeMode::Cpu);
} else if (v == "gpu") {
SET_OVERRIDE(accelerate_astc, Settings::AstcDecodeMode::Gpu);
} else if (v == "cpu_async") {
SET_OVERRIDE(accelerate_astc, Settings::AstcDecodeMode::CpuAsynchronous);
}
}
else if (k == "fast_gpu_time") {
if (v == "off" || v == "false" || v == "0" || v == "normal") {
SET_OVERRIDE(fast_gpu_time, Settings::GpuOverclock::Normal);
} else if (v == "medium" || v == "true" || v == "1") {
SET_OVERRIDE(fast_gpu_time, Settings::GpuOverclock::Medium);
} else if (v == "high") {
SET_OVERRIDE(fast_gpu_time, Settings::GpuOverclock::High);
}
}
else if (k == "resolution" || k == "resolution_setup") {
if (v == "0.25x") {
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res1_4X);
} else if (v == "0.5x" || v == "half") {
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res1_2X);
} else if (v == "0.75x") {
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res3_4X);
} else if (v == "1x" || v == "native") {
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res1X);
} else if (v == "1.25x") {
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res5_4X);
} else if (v == "1.5x") {
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res3_2X);
} else if (v == "2x") {
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res2X);
} else if (v == "3x") {
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res3X);
} else if (v == "4x") {
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res4X);
} else if (v == "5x") {
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res5X);
} else if (v == "6x") {
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res6X);
} else if (v == "7x") {
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res7X);
} else if (v == "8x") {
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res8X);
}
}
else if (k == "async_gpu" || k == "use_asynchronous_gpu_emulation") {
SET_OVERRIDE(use_asynchronous_gpu_emulation, ToBool(v));
}
else if (k == "reactive_flushing" || k == "use_reactive_flushing") {
SET_OVERRIDE(use_reactive_flushing, ToBool(v));
}
else if (k == "multicore" || k == "use_multi_core") {
SET_OVERRIDE(use_multi_core, ToBool(v));
}
else if (k == "async_shaders" || k == "use_asynchronous_shaders") {
SET_OVERRIDE(use_asynchronous_shaders, ToBool(v));
}
else if (k == "pipeline_cache" || k == "use_vulkan_driver_pipeline_cache") {
SET_OVERRIDE(use_vulkan_driver_pipeline_cache, ToBool(v));
}
else if (k == "compute_pipelines" || k == "enable_compute_pipelines") {
SET_OVERRIDE(enable_compute_pipelines, ToBool(v));
}
else if (k == "use_disk_shader_cache") {
SET_OVERRIDE(use_disk_shader_cache, ToBool(v));
}
else if (k == "barrier_feedback_loops") {
SET_OVERRIDE(barrier_feedback_loops, ToBool(v));
}
else if (k == "async_presentation") {
SET_OVERRIDE(async_presentation, ToBool(v));
}
else if (k == "use_squashed_iterated_blend") {
s.use_squashed_iterated_blend = ToBool(v);
}
else if (k == "smo" || k == "sync_memory_operations") {
SET_OVERRIDE(sync_memory_operations, ToBool(v));
}
else if (k == "nvdec" || k == "nvdec_emulation") {
if (v == "off" || v == "disabled") {
SET_OVERRIDE(nvdec_emulation, Settings::NvdecEmulation::Off);
} else if (v == "cpu") {
SET_OVERRIDE(nvdec_emulation, Settings::NvdecEmulation::Cpu);
} else if (v == "gpu") {
SET_OVERRIDE(nvdec_emulation, Settings::NvdecEmulation::Gpu);
}
}
else if (k == "enable_buffer_history" || k == "buffer_history") {
SET_OVERRIDE(enable_buffer_history, ToBool(v));
}
else if (k == "fix_bloom_effects" || k == "fix_bloom") {
SET_OVERRIDE(fix_bloom_effects, ToBool(v));
}
else if (k == "dma_accuracy") {
if (v == "default") {
SET_OVERRIDE(dma_accuracy, Settings::DmaAccuracy::Default);
} else if (v == "unsafe" || v == "fast") {
SET_OVERRIDE(dma_accuracy, Settings::DmaAccuracy::Unsafe);
} else if (v == "safe" || v == "stable") {
SET_OVERRIDE(dma_accuracy, Settings::DmaAccuracy::Safe);
}
}
else if (k == "airplane_mode" || k == "airplane") {
SET_OVERRIDE(airplane_mode, ToBool(v));
}
else if (k == "controller_applet_mode" || k == "controller_applet") {
if (v == "lle" || v == "real") {
SET_OVERRIDE(controller_applet_mode, Settings::AppletMode::LLE);
} else if (v == "hle" || v == "custom") {
SET_OVERRIDE(controller_applet_mode, Settings::AppletMode::HLE);
}
}
else {
LOG_WARNING(Core, "Unknown game override setting: {}={}", key, value);
}
}
#undef SET_OVERRIDE
std::optional<Section> ParseSectionHeader(std::string_view header) {
if (header.size() < 2 || header.front() != '[' || header.back() != ']') {
return std::nullopt;
}
header = header.substr(1, header.size() - 2);
Section section{};
const auto pipe_pos = header.find('|');
std::string_view title_part = header;
std::string_view conditions_part;
if (pipe_pos != std::string_view::npos) {
title_part = header.substr(0, pipe_pos);
conditions_part = header.substr(pipe_pos + 1);
}
std::string title_str = Trim(title_part);
if (title_str.starts_with("0x") || title_str.starts_with("0X")) {
title_str = title_str.substr(2);
}
std::uint64_t title_id = 0;
auto [ptr, ec] = std::from_chars(title_str.data(), title_str.data() + title_str.size(),
title_id, 16);
if (ec != std::errc{}) {
return std::nullopt;
}
section.title_id = title_id;
if (!conditions_part.empty()) {
std::string cond_str(conditions_part);
std::istringstream stream(cond_str);
std::string token;
while (std::getline(stream, token, ',')) {
token = Trim(token);
const auto colon_pos = token.find(':');
if (colon_pos == std::string::npos) continue;
std::string cond_key = ToLower(Trim(token.substr(0, colon_pos)));
std::string cond_value = ToLower(Trim(token.substr(colon_pos + 1)));
if (cond_key == "vendor") {
section.condition.vendor = cond_value;
} else if (cond_key == "os") {
section.condition.os.push_back(cond_value);
} else if (cond_key == "cpu" || cond_key == "cpu_backend") {
section.condition.cpu_backend = cond_value;
}
}
}
return section;
}
std::string CpuBackendToString(Settings::CpuBackend backend) {
switch (backend) {
case Settings::CpuBackend::Dynarmic: return "jit";
case Settings::CpuBackend::Nce: return "nce";
default: return "unknown";
}
}
bool ConditionMatches(const Condition& cond, const GameSettings::EnvironmentInfo& env) {
if (cond.vendor.has_value() && cond.vendor.value() != VendorToString(env.vendor)) {
return false;
}
if (!cond.os.empty()) {
const std::string current_os = OSToString(env.os);
bool os_match = std::any_of(cond.os.begin(), cond.os.end(),
[&current_os](const std::string& os) { return os == current_os; });
if (!os_match) {
return false;
}
}
if (cond.cpu_backend.has_value()) {
const std::string current_cpu = CpuBackendToString(Settings::values.cpu_backend.GetValue());
const std::string& required = cond.cpu_backend.value();
if (required == "jit" || required == "dynarmic") {
if (current_cpu != "jit") return false;
} else if (required == "nce") {
if (current_cpu != "nce") return false;
} else {
return false;
}
}
return true;
}
std::vector<Section> ParseOverridesFile(const std::filesystem::path& path) {
std::vector<Section> sections;
std::ifstream file(path);
if (!file.is_open()) {
return sections;
}
Section* current_section = nullptr;
std::string line;
while (std::getline(file, line)) {
line = Trim(line);
if (line.empty() || line.front() == ';' || line.front() == '#') {
continue;
}
if (line.front() == '[' && line.back() == ']') {
auto section = ParseSectionHeader(line);
if (section) {
sections.push_back(std::move(*section));
current_section = &sections.back();
} else {
current_section = nullptr;
LOG_WARNING(Core, "Invalid section header in overrides.ini: {}", line);
}
continue;
}
if (current_section) {
const auto eq_pos = line.find('=');
if (eq_pos != std::string::npos) {
std::string setting_key = Trim(line.substr(0, eq_pos));
std::string setting_value = Trim(line.substr(eq_pos + 1));
current_section->settings.emplace_back(std::move(setting_key), std::move(setting_value));
}
}
}
return sections;
}
bool IsEarlySetting(const std::string& key) {
std::array early_settings = {
"backend",
"renderer_backend",
"multicore",
"use_multi_core",
};
const std::string k = ToLower(key);
return std::ranges::any_of(early_settings,
[&k](const char* s) { return k == s; });
}
bool EarlyConditionMatches(const Condition& cond, GameSettings::OS current_os,
const std::optional<GameSettings::GPUVendor>& detected_vendor) {
if (!cond.os.empty()) {
const std::string current_os_str = OSToString(current_os);
bool os_match = std::any_of(cond.os.begin(), cond.os.end(),
[&current_os_str](const std::string& os) { return os == current_os_str; });
if (!os_match) {
return false;
}
}
if (cond.vendor.has_value()) {
if (!detected_vendor.has_value()) {
return false;
}
if (cond.vendor.value() != VendorToString(*detected_vendor)) {
return false;
}
}
if (cond.cpu_backend.has_value()) {
const std::string current_cpu = CpuBackendToString(Settings::values.cpu_backend.GetValue());
const std::string& required = cond.cpu_backend.value();
if (required == "jit" || required == "dynarmic") {
if (current_cpu != "jit") return false;
} else if (required == "nce" /* || required == "native" */) {
if (current_cpu != "nce") return false;
} else {
return false;
}
}
return true;
}
int GetSpecificity(const Section& s) {
int score = 0;
if (s.condition.vendor.has_value()) score++;
if (!s.condition.os.empty()) score++;
if (s.condition.cpu_backend.has_value()) score++;
return score;
}
} // anonymous namespace
std::filesystem::path GetOverridesPath() {
return Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / "overrides.ini";
}
bool OverridesFileExists() {
return std::filesystem::exists(GetOverridesPath());
}
std::optional<std::uint32_t> GetOverridesFileVersion() {
const auto path = GetOverridesPath();
std::ifstream file(path);
if (!file.is_open()) {
return std::nullopt;
}
std::string line;
if (std::getline(file, line)) {
line = Trim(line);
if (line.starts_with(kVersionPrefix)) {
const auto version_str = line.substr(std::strlen(kVersionPrefix));
std::uint32_t version = 0;
auto [ptr, ec] = std::from_chars(version_str.data(),
version_str.data() + version_str.size(), version);
if (ec == std::errc{}) {
return version;
}
}
}
return std::nullopt;
}
void ApplyEarlyOverrides(std::uint64_t program_id, const std::string& gpu_vendor) {
const auto path = GetOverridesPath();
if (!std::filesystem::exists(path)) {
return;
}
const auto current_os = DetectOS();
std::optional<GameSettings::GPUVendor> detected_vendor;
if (!gpu_vendor.empty()) {
detected_vendor = DetectGPUVendor(gpu_vendor);
}
auto sections = ParseOverridesFile(path);
// Filter matching sections
std::vector<Section> matching;
for (auto& section : sections) {
if (section.title_id != program_id) continue;
if (!EarlyConditionMatches(section.condition, current_os, detected_vendor)) continue;
matching.push_back(std::move(section));
}
// Sort by base first, then os, then vendor, then both
std::sort(matching.begin(), matching.end(),
[](const Section& a, const Section& b) {
return GetSpecificity(a) < GetSpecificity(b);
});
// Apply only early settings, only apply before renderer overrides
for (const auto& section : matching) {
for (const auto& [setting_key, setting_value] : section.settings) {
if (IsEarlySetting(setting_key)) {
LOG_INFO(Core, "Game override {:016X}: {}={}",
program_id, setting_key, setting_value);
ApplySetting(setting_key, setting_value);
}
}
}
}
void ApplyLateOverrides(std::uint64_t program_id, const VideoCore::RendererBase& renderer) {
const auto path = GetOverridesPath();
if (!std::filesystem::exists(path)) {
return;
}
const auto env = GameSettings::DetectEnvironment(renderer);
auto sections = ParseOverridesFile(path);
// Filter matching sections
std::vector<Section> matching;
for (auto& section : sections) {
if (section.title_id != program_id) continue;
if (!ConditionMatches(section.condition, env)) continue;
matching.push_back(std::move(section));
}
// Sort by base first, then single condition, then multiple
std::sort(matching.begin(), matching.end(),
[](const Section& a, const Section& b) {
return GetSpecificity(a) < GetSpecificity(b);
});
// Apply settings (skip early ones, they were already applied, like renderr_backend)
for (const auto& section : matching) {
for (const auto& [setting_key, setting_value] : section.settings) {
if (!IsEarlySetting(setting_key)) {
LOG_INFO(Core, "Game override {:016X}: {}={}",
program_id, setting_key, setting_value);
ApplySetting(setting_key, setting_value);
}
}
}
}
} // namespace Core::GameOverrides
+40
View File
@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <cstdint>
#include <filesystem>
#include <optional>
#include <string>
#include <vector>
namespace VideoCore {
class RendererBase;
}
namespace Core::GameOverrides {
// used to check against GitHub Release Version
// repo at: https://github.com/eden-emulator/eden-overrides
inline constexpr std::uint32_t kOverridesVersion = 1;
inline constexpr const char* kVersionPrefix = "; version=";
struct Condition {
std::optional<std::string> vendor;
std::vector<std::string> os;
std::optional<std::string> cpu_backend;
};
struct Section {
std::uint64_t title_id{};
Condition condition;
std::vector<std::pair<std::string, std::string>> settings;
};
std::filesystem::path GetOverridesPath();
bool OverridesFileExists();
std::optional<std::uint32_t> GetOverridesFileVersion();
void ApplyEarlyOverrides(std::uint64_t program_id, const std::string& gpu_vendor);
void ApplyLateOverrides(std::uint64_t program_id, const VideoCore::RendererBase& renderer);
} // namespace Core::GameOverrides
+23 -17
View File
@@ -1,7 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/game_settings.h"
#include "core/game_overrides.h"
#include <algorithm>
#include <cctype>
@@ -60,15 +61,26 @@ static GPUVendor GetGPU(const std::string& gpu_vendor_string) {
}
}
// legacy (shouldn't be needed anymore, but just in case)
std::string gpu = gpu_vendor_string;
std::transform(gpu.begin(), gpu.end(), gpu.begin(), [](unsigned char c){ return (char)std::tolower(c); });
if (gpu.find("geforce") != std::string::npos) {
return GPUVendor::Nvidia;
}
if (gpu.find("radeon") != std::string::npos || gpu.find("ati") != std::string::npos) {
if (gpu.find("amd") != std::string::npos || gpu.find("radeon") != std::string::npos || gpu.find("ati") != std::string::npos) {
return GPUVendor::AMD;
}
if (gpu.find("intel") != std::string::npos) {
return GPUVendor::Intel;
}
if (gpu.find("apple") != std::string::npos) {
return GPUVendor::Apple;
}
if (gpu.find("qualcomm") != std::string::npos || gpu.find("adreno") != std::string::npos) {
return GPUVendor::Qualcomm;
}
if (gpu.find("mali") != std::string::npos) {
return GPUVendor::ARM;
}
return GPUVendor::Unknown;
}
@@ -119,22 +131,16 @@ EnvironmentInfo DetectEnvironment(const VideoCore::RendererBase& renderer) {
return env;
}
void LoadOverrides(std::uint64_t program_id, const VideoCore::RendererBase& renderer) {
const auto env = DetectEnvironment(renderer);
switch (static_cast<TitleID>(program_id)) {
case TitleID::NinjaGaidenRagebound:
Settings::values.use_squashed_iterated_blend = true;
break;
default:
break;
void LoadEarlyOverrides(std::uint64_t program_id, const std::string& gpu_vendor, bool enabled) {
if (enabled && GameOverrides::OverridesFileExists()) {
GameOverrides::ApplyEarlyOverrides(program_id, gpu_vendor);
}
}
LOG_INFO(Core, "Applied game settings for title ID {:016X} on OS {}, GPU vendor {} ({})",
program_id,
static_cast<int>(env.os),
static_cast<int>(env.vendor),
env.vendor_string);
void LoadOverrides(std::uint64_t program_id, const VideoCore::RendererBase& renderer, bool enabled) {
if (enabled && GameOverrides::OverridesFileExists()) {
GameOverrides::ApplyLateOverrides(program_id, renderer);
}
}
} // namespace Core::GameSettings
+4 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
@@ -55,6 +55,8 @@ struct EnvironmentInfo {
EnvironmentInfo DetectEnvironment(const VideoCore::RendererBase& renderer);
void LoadOverrides(std::uint64_t program_id, const VideoCore::RendererBase& renderer);
void LoadEarlyOverrides(std::uint64_t program_id, const std::string& gpu_vendor, bool enabled = true);
void LoadOverrides(std::uint64_t program_id, const VideoCore::RendererBase& renderer, bool enabled = true);
} // namespace Core::GameSettings
@@ -443,6 +443,15 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
check_for_updates,
tr("Check for updates"),
tr("Whether or not to check for updates upon startup."));
INSERT(UISettings,
enable_global_overrides,
tr("Enable global game overrides"),
tr("When enabled, per-game settings from the global overrides.ini file will be applied."));
INSERT(UISettings,
auto_update_overrides,
tr("Auto-update game overrides"),
tr("Automatically download the latest game overrides list from GitHub on startup."));
INSERT(UISettings, overrides_consent_given, QString(), QString());
// Linux
INSERT(UISettings, enable_gamemode, tr("Enable Gamemode"), QString());
+4
View File
@@ -142,6 +142,10 @@ struct Values {
Setting<bool> check_for_updates{linkage, true, "check_for_updates", Category::UiGeneral};
Setting<bool> enable_global_overrides{linkage, true, "enable_global_overrides", Category::UiGeneral};
Setting<bool> auto_update_overrides{linkage, false, "auto_update_overrides", Category::UiGeneral};
Setting<bool> overrides_consent_given{linkage, false, "overrides_consent_given", Category::UiGeneral};
// Linux/MinGW may support (requires libdl support)
SwitchableSetting<bool> enable_gamemode{linkage,
#ifndef _MSC_VER
+4
View File
@@ -239,6 +239,9 @@ add_executable(yuzu
ryujinx_dialog.h ryujinx_dialog.cpp ryujinx_dialog.ui
main_window.h main_window.cpp main.ui
overrides_updater.h
overrides_updater.cpp
configuration/system/new_user_dialog.h configuration/system/new_user_dialog.cpp configuration/system/new_user_dialog.ui
configuration/system/profile_avatar_dialog.h configuration/system/profile_avatar_dialog.cpp
configuration/addon/mod_select_dialog.h configuration/addon/mod_select_dialog.cpp configuration/addon/mod_select_dialog.ui
@@ -361,6 +364,7 @@ target_sources(yuzu
if (ENABLE_OPENSSL)
target_link_libraries(yuzu PRIVATE OpenSSL::SSL OpenSSL::Crypto)
target_compile_definitions(yuzu PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
endif()
if (APPLE)
+6
View File
@@ -217,6 +217,7 @@
<addaction name="menuInstall_Firmware"/>
<addaction name="action_Verify_installed_contents"/>
<addaction name="action_Data_Manager"/>
<addaction name="action_Download_Override_Settings"/>
<addaction name="separator"/>
<addaction name="menu_cabinet_applet"/>
<addaction name="menu_Applets"/>
@@ -577,6 +578,11 @@
<string>&amp;Data Manager</string>
</property>
</action>
<action name="action_Download_Override_Settings">
<property name="text">
<string>Download &amp;Override Settings</string>
</property>
</action>
<action name="action_Tree_View">
<property name="checkable">
<bool>true</bool>
+31
View File
@@ -45,6 +45,8 @@
#include "configuration/configure_per_game.h"
#include "configuration/configure_tas.h"
#include "overrides_updater.h"
#include "util/clickable_label.h"
#include "util/overlay_dialog.h"
#include "util/controller_navigation.h"
@@ -123,6 +125,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/frontend/applets/software_keyboard.h"
#include "core/frontend/applets/mii_edit.h"
#include "core/frontend/applets/general.h"
#include "core/game_settings.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/am/applet_manager.h"
@@ -547,6 +550,17 @@ MainWindow::MainWindow(bool has_broken_vulkan)
}
#endif
// Check for game overrides updates
overrides_updater = new OverridesUpdater(this);
connect(overrides_updater, &OverridesUpdater::ConfigChanged, this, [this]() {
config->SaveAllValues();
});
connect(overrides_updater, &OverridesUpdater::UpdateCompleted, this,
[this](bool success, const QString& message) {
QMessageBox::information(this, tr("Game Overrides"), message);
});
overrides_updater->CheckAndUpdate();
QtCommon::system->SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
QtCommon::system->RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual,
QtCommon::provider.get());
@@ -1716,6 +1730,11 @@ void MainWindow::ConnectMenuEvents() {
connect_menu(ui->action_About, &MainWindow::OnAbout);
connect_menu(ui->action_Eden_Dependencies, &MainWindow::OnEdenDependencies);
connect_menu(ui->action_Data_Manager, &MainWindow::OnDataDialog);
connect(ui->action_Download_Override_Settings, &QAction::triggered, this, [this] {
if (overrides_updater) {
overrides_updater->DownloadOverrides();
}
});
}
void MainWindow::UpdateMenuState() {
@@ -1880,6 +1899,18 @@ bool MainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPar
ShutdownGame();
}
const bool overrides_enabled = UISettings::values.enable_global_overrides.GetValue();
Settings::values.enable_global_overrides = overrides_enabled;
std::string gpu_vendor;
if (!vk_device_records.empty()) {
const int device_index = Settings::values.vulkan_device.GetValue();
const int safe_index = std::clamp(device_index, 0,
static_cast<int>(vk_device_records.size()) - 1);
gpu_vendor = vk_device_records[safe_index].name;
}
Core::GameSettings::LoadEarlyOverrides(params.program_id, gpu_vendor, overrides_enabled);
if (!render_window->InitRenderTarget()) {
return false;
}
+4
View File
@@ -64,6 +64,7 @@ class QtControllerSelectorDialog;
class QtProfileSelectionDialog;
class QtSoftwareKeyboardDialog;
class QtNXWebEngineView;
class OverridesUpdater;
enum class StartGameType {
Normal, // Can use custom configuration
@@ -564,6 +565,9 @@ private:
QtSoftwareKeyboardDialog* software_keyboard = nullptr;
QtNXWebEngineView* web_applet = nullptr;
// Overrides updater
OverridesUpdater* overrides_updater = nullptr;
// True if amiibo file select is visible
bool is_amiibo_file_select_active{};
+179
View File
@@ -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 {};
}
}
+36
View File
@@ -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";
};