// 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