diff --git a/src/common/settings.h b/src/common/settings.h index 41f766a5e7..f2a360b69b 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -795,6 +795,7 @@ struct Values { // Per-game overrides bool use_squashed_iterated_blend; + bool enable_global_overrides{true}; Setting enable_overlay{linkage, false, "enable_overlay", Category::Core}; }; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 14eb331d24..e55ce014ef 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -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 diff --git a/src/core/core.cpp b/src/core/core.cpp index 50572ea449..ac1ce754ac 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -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; diff --git a/src/core/game_overrides.cpp b/src/core/game_overrides.cpp new file mode 100644 index 0000000000..626091fb0f --- /dev/null +++ b/src/core/game_overrides.cpp @@ -0,0 +1,597 @@ +// 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 +#include +#include +#include +#include +#include +#include + +#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(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(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
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(), + [¤t_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
ParseOverridesFile(const std::filesystem::path& path) { + std::vector
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 = §ions.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& 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(), + [¤t_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 GetOverridesFileVersion() { + // later used for/if download check override 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 detected_vendor; + if (!gpu_vendor.empty()) { + detected_vendor = DetectGPUVendor(gpu_vendor); + } + + auto sections = ParseOverridesFile(path); + + // Filter matching sections + std::vector
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
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 diff --git a/src/core/game_overrides.h b/src/core/game_overrides.h new file mode 100644 index 0000000000..3d23bf246f --- /dev/null +++ b/src/core/game_overrides.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +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 vendor; + std::vector os; + std::optional cpu_backend; + }; + + struct Section { + std::uint64_t title_id{}; + Condition condition; + std::vector> settings; + }; + + std::filesystem::path GetOverridesPath(); + bool OverridesFileExists(); + std::optional 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 diff --git a/src/core/game_settings.cpp b/src/core/game_settings.cpp index da1530e026..6f3dd3023c 100644 --- a/src/core/game_settings.cpp +++ b/src/core/game_settings.cpp @@ -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 #include @@ -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(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(env.os), - static_cast(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 diff --git a/src/core/game_settings.h b/src/core/game_settings.h index 327a1a4e2c..2cc1375efa 100644 --- a/src/core/game_settings.h +++ b/src/core/game_settings.h @@ -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 diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp index 5d4185b47d..e55e867cb8 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -443,6 +443,10 @@ std::unique_ptr 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.")); // Linux INSERT(UISettings, enable_gamemode, tr("Enable Gamemode"), QString()); diff --git a/src/qt_common/config/uisettings.h b/src/qt_common/config/uisettings.h index 3c75268377..3ec7988300 100644 --- a/src/qt_common/config/uisettings.h +++ b/src/qt_common/config/uisettings.h @@ -142,6 +142,8 @@ struct Values { Setting check_for_updates{linkage, true, "check_for_updates", Category::UiGeneral}; + Setting enable_global_overrides{linkage, true, "enable_global_overrides", Category::UiGeneral}; + // Linux/MinGW may support (requires libdl support) SwitchableSetting enable_gamemode{linkage, #ifndef _MSC_VER diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp index 2e5548c775..7ca84e60dd 100644 --- a/src/yuzu/main_window.cpp +++ b/src/yuzu/main_window.cpp @@ -123,6 +123,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" @@ -1880,6 +1881,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(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; }