// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include #include // for wstring support #include #include "common/config.h" #include "common/logging/formatter.h" #include "common/path_util.h" #include "common/scm_rev.h" using std::nullopt; using std::optional; using std::string; namespace toml { template std::filesystem::path find_fs_path_or(const basic_value& v, const K& ky, std::filesystem::path opt) { try { auto str = find(v, ky); if (str.empty()) { return opt; } std::u8string u8str{(char8_t*)&str.front(), (char8_t*)&str.back() + 1}; return std::filesystem::path{u8str}; } catch (...) { return opt; } } // why is it so hard to avoid exceptions with this library template std::optional get_optional(const toml::value& v, const std::string& key) { if (!v.is_table()) return std::nullopt; const auto& tbl = v.as_table(); auto it = tbl.find(key); if (it == tbl.end()) return std::nullopt; if constexpr (std::is_same_v) { if (it->second.is_integer()) { return static_cast(toml::get(it->second)); } } else if constexpr (std::is_same_v) { if (it->second.is_integer()) { return static_cast(toml::get(it->second)); } } else if constexpr (std::is_same_v) { if (it->second.is_floating()) { return toml::get(it->second); } } else if constexpr (std::is_same_v) { if (it->second.is_string()) { return toml::get(it->second); } } else if constexpr (std::is_same_v) { if (it->second.is_boolean()) { return toml::get(it->second); } } else { static_assert([] { return false; }(), "Unsupported type in get_optional"); } return std::nullopt; } } // namespace toml namespace Config { template class ConfigEntry { public: T base_value; optional game_specific_value; ConfigEntry(const T& t = T()) : base_value(t), game_specific_value(nullopt) {} ConfigEntry operator=(const T& t) { base_value = t; return *this; } const T get() const { return game_specific_value.has_value() ? *game_specific_value : base_value; } void setFromToml(const toml::value& v, const std::string& key, bool is_game_specific = false) { if (is_game_specific) { game_specific_value = toml::get_optional(v, key); } else { base_value = toml::get_optional(v, key).value_or(base_value); } } void set(const T value, bool is_game_specific = false) { is_game_specific ? game_specific_value = value : base_value = value; } void setTomlValue(toml::ordered_value& data, const std::string& header, const std::string& key, bool is_game_specific = false) { if (is_game_specific) { data[header][key] = game_specific_value.value_or(base_value); game_specific_value = std::nullopt; } else { data[header][key] = base_value; } } // operator T() { // return get(); // } }; // General static ConfigEntry volumeSlider(100); static ConfigEntry isNeo(false); static ConfigEntry isDevKit(false); static ConfigEntry isPSNSignedIn(false); static ConfigEntry isTrophyPopupDisabled(false); static ConfigEntry trophyNotificationDuration(6.0); static ConfigEntry logFilter(""); static ConfigEntry logType("sync"); static ConfigEntry userName("shadPS4"); static ConfigEntry chooseHomeTab("General"); static ConfigEntry isShowSplash(false); static ConfigEntry isSideTrophy("right"); static ConfigEntry isConnectedToNetwork(false); static bool enableDiscordRPC = false; static bool checkCompatibilityOnStartup = false; static bool compatibilityData = false; static std::filesystem::path sys_modules_path = {}; // Input static ConfigEntry cursorState(HideCursorState::Idle); static ConfigEntry cursorHideTimeout(5); // 5 seconds (default) static ConfigEntry useSpecialPad(false); static ConfigEntry specialPadClass(1); static ConfigEntry isMotionControlsEnabled(true); static ConfigEntry useUnifiedInputConfig(true); static ConfigEntry defaultControllerID(""); static ConfigEntry backgroundControllerInput(false); // Audio static ConfigEntry micDevice("Default Device"); static ConfigEntry mainOutputDevice("Default Device"); static ConfigEntry padSpkOutputDevice("Default Device"); // GPU static ConfigEntry windowWidth(1280); static ConfigEntry windowHeight(720); static ConfigEntry internalScreenWidth(1280); static ConfigEntry internalScreenHeight(720); static ConfigEntry isNullGpu(false); static ConfigEntry shouldCopyGPUBuffers(false); static ConfigEntry readbacksEnabled(false); static ConfigEntry readbackLinearImagesEnabled(false); static ConfigEntry directMemoryAccessEnabled(false); static ConfigEntry shouldDumpShaders(false); static ConfigEntry shouldPatchShaders(false); static ConfigEntry vblankFrequency(60); static ConfigEntry isFullscreen(false); static ConfigEntry fullscreenMode("Windowed"); static ConfigEntry presentMode("Mailbox"); static ConfigEntry isHDRAllowed(false); static ConfigEntry fsrEnabled(true); static ConfigEntry rcasEnabled(true); static ConfigEntry rcasAttenuation(250); // Vulkan static ConfigEntry gpuId(-1); static ConfigEntry vkValidation(false); static ConfigEntry vkValidationSync(false); static ConfigEntry vkValidationGpu(false); static ConfigEntry vkCrashDiagnostic(false); static ConfigEntry vkHostMarkers(false); static ConfigEntry vkGuestMarkers(false); static ConfigEntry rdocEnable(false); // Debug static ConfigEntry isDebugDump(false); static ConfigEntry isShaderDebug(false); static ConfigEntry isSeparateLogFilesEnabled(false); static ConfigEntry isFpsColor(true); static ConfigEntry logEnabled(true); // GUI static bool load_game_size = true; static std::vector settings_install_dirs = {}; std::vector install_dirs_enabled = {}; std::filesystem::path settings_addon_install_dir = {}; std::filesystem::path save_data_path = {}; // Settings ConfigEntry m_language(1); // english // Keys static string trophyKey = ""; // Config version, used to determine if a user's config file is outdated. static string config_version = Common::g_scm_rev; // These two entries aren't stored in the config static bool overrideControllerColor = false; static int controllerCustomColorRGB[3] = {0, 0, 255}; std::filesystem::path getSysModulesPath() { if (sys_modules_path.empty()) { return Common::FS::GetUserPath(Common::FS::PathType::SysModuleDir); } return sys_modules_path; } void setSysModulesPath(const std::filesystem::path& path) { sys_modules_path = path; } int getVolumeSlider() { return volumeSlider.get(); } bool allowHDR() { return isHDRAllowed.get(); } bool GetUseUnifiedInputConfig() { return useUnifiedInputConfig.get(); } void SetUseUnifiedInputConfig(bool use) { useUnifiedInputConfig.base_value = use; } bool GetOverrideControllerColor() { return overrideControllerColor; } void SetOverrideControllerColor(bool enable) { overrideControllerColor = enable; } int* GetControllerCustomColor() { return controllerCustomColorRGB; } bool getLoggingEnabled() { return logEnabled.get(); } void SetControllerCustomColor(int r, int b, int g) { controllerCustomColorRGB[0] = r; controllerCustomColorRGB[1] = b; controllerCustomColorRGB[2] = g; } string getTrophyKey() { return trophyKey; } void setTrophyKey(string key) { trophyKey = key; } bool GetLoadGameSizeEnabled() { return load_game_size; } std::filesystem::path GetSaveDataPath() { if (save_data_path.empty()) { return Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "savedata"; } return save_data_path; } void setVolumeSlider(int volumeValue, bool is_game_specific) { volumeSlider.set(volumeValue, is_game_specific); } void setLoadGameSizeEnabled(bool enable) { load_game_size = enable; } bool isNeoModeConsole() { return isNeo.get(); } bool isDevKitConsole() { return isDevKit.get(); } bool getIsFullscreen() { return isFullscreen.get(); } string getFullscreenMode() { return fullscreenMode.get(); } std::string getPresentMode() { return presentMode.get(); } bool getisTrophyPopupDisabled() { return isTrophyPopupDisabled.get(); } bool getEnableDiscordRPC() { return enableDiscordRPC; } s16 getCursorState() { return cursorState.get(); } int getCursorHideTimeout() { return cursorHideTimeout.get(); } string getMicDevice() { return micDevice.get(); } std::string getMainOutputDevice() { return mainOutputDevice.get(); } std::string getPadSpkOutputDevice() { return padSpkOutputDevice.get(); } double getTrophyNotificationDuration() { return trophyNotificationDuration.get(); } u32 getWindowWidth() { return windowWidth.get(); } u32 getWindowHeight() { return windowHeight.get(); } u32 getInternalScreenWidth() { return internalScreenHeight.get(); } u32 getInternalScreenHeight() { return internalScreenHeight.get(); } s32 getGpuId() { return gpuId.get(); } string getLogFilter() { return logFilter.get(); } string getLogType() { return logType.get(); } string getUserName() { return userName.get(); } string getChooseHomeTab() { return chooseHomeTab.get(); } bool getUseSpecialPad() { return useSpecialPad.get(); } int getSpecialPadClass() { return specialPadClass.get(); } bool getIsMotionControlsEnabled() { return isMotionControlsEnabled.get(); } bool debugDump() { return isDebugDump.get(); } bool collectShadersForDebug() { return isShaderDebug.get(); } bool showSplash() { return isShowSplash.get(); } string sideTrophy() { return isSideTrophy.get(); } bool nullGpu() { return isNullGpu.get(); } bool copyGPUCmdBuffers() { return shouldCopyGPUBuffers.get(); } bool readbacks() { return readbacksEnabled.get(); } bool readbackLinearImages() { return readbackLinearImagesEnabled.get(); } bool directMemoryAccess() { return directMemoryAccessEnabled.get(); } bool dumpShaders() { return shouldDumpShaders.get(); } bool patchShaders() { return shouldPatchShaders.get(); } bool isRdocEnabled() { return rdocEnable.get(); } bool fpsColor() { return isFpsColor.get(); } bool isLoggingEnabled() { return logEnabled.get(); } u32 vblankFreq() { if (vblankFrequency.get() < 60) { vblankFrequency = 60; } return vblankFrequency.get(); } bool vkValidationEnabled() { return vkValidation.get(); } bool vkValidationSyncEnabled() { return vkValidationSync.get(); } bool vkValidationGpuEnabled() { return vkValidationGpu.get(); } bool getVkCrashDiagnosticEnabled() { return vkCrashDiagnostic.get(); } bool getVkHostMarkersEnabled() { return vkHostMarkers.get(); } bool getVkGuestMarkersEnabled() { return vkGuestMarkers.get(); } void setVkCrashDiagnosticEnabled(bool enable, bool is_game_specific) { vkCrashDiagnostic.set(enable, is_game_specific); } void setVkHostMarkersEnabled(bool enable, bool is_game_specific) { vkHostMarkers.set(enable, is_game_specific); } void setVkGuestMarkersEnabled(bool enable, bool is_game_specific) { vkGuestMarkers.set(enable, is_game_specific); } bool getCompatibilityEnabled() { return compatibilityData; } bool getCheckCompatibilityOnStartup() { return checkCompatibilityOnStartup; } bool getIsConnectedToNetwork() { return isConnectedToNetwork.get(); } void setConnectedToNetwork(bool enable, bool is_game_specific) { isConnectedToNetwork.set(enable, is_game_specific); } void setGpuId(s32 selectedGpuId, bool is_game_specific) { gpuId.set(selectedGpuId, is_game_specific); } void setWindowWidth(u32 width, bool is_game_specific) { windowWidth.set(width, is_game_specific); } void setWindowHeight(u32 height, bool is_game_specific) { windowHeight.set(height, is_game_specific); } void setInternalScreenWidth(u32 width) { internalScreenWidth.base_value = width; } void setInternalScreenHeight(u32 height) { internalScreenHeight.base_value = height; } void setDebugDump(bool enable, bool is_game_specific) { isDebugDump.set(enable, is_game_specific); } void setLoggingEnabled(bool enable, bool is_game_specific) { logEnabled.set(enable, is_game_specific); } void setCollectShaderForDebug(bool enable, bool is_game_specific) { isShaderDebug.set(enable, is_game_specific); } void setShowSplash(bool enable, bool is_game_specific) { isShowSplash.set(enable, is_game_specific); } void setSideTrophy(string side, bool is_game_specific) { isSideTrophy.set(side, is_game_specific); } void setNullGpu(bool enable, bool is_game_specific) { isNullGpu.set(enable, is_game_specific); } void setAllowHDR(bool enable, bool is_game_specific) { isHDRAllowed.set(enable, is_game_specific); } void setCopyGPUCmdBuffers(bool enable, bool is_game_specific) { shouldCopyGPUBuffers.set(enable, is_game_specific); } void setReadbacks(bool enable, bool is_game_specific) { readbacksEnabled.set(enable, is_game_specific); } void setReadbackLinearImages(bool enable, bool is_game_specific) { readbackLinearImagesEnabled.set(enable, is_game_specific); } void setDirectMemoryAccess(bool enable, bool is_game_specific) { directMemoryAccessEnabled.set(enable, is_game_specific); } void setDumpShaders(bool enable, bool is_game_specific) { shouldDumpShaders.set(enable, is_game_specific); } void setVkValidation(bool enable, bool is_game_specific) { vkValidation.set(enable, is_game_specific); } void setVkSyncValidation(bool enable, bool is_game_specific) { vkValidationSync.set(enable, is_game_specific); } void setRdocEnabled(bool enable, bool is_game_specific) { rdocEnable.set(enable, is_game_specific); } void setVblankFreq(u32 value, bool is_game_specific) { vblankFrequency.set(value, is_game_specific); } void setIsFullscreen(bool enable, bool is_game_specific) { isFullscreen.set(enable, is_game_specific); } void setFullscreenMode(string mode, bool is_game_specific) { fullscreenMode.set(mode, is_game_specific); } void setPresentMode(std::string mode, bool is_game_specific) { presentMode.set(mode, is_game_specific); } void setisTrophyPopupDisabled(bool disable, bool is_game_specific) { isTrophyPopupDisabled.set(disable, is_game_specific); } void setEnableDiscordRPC(bool enable) { enableDiscordRPC = enable; } void setCursorState(s16 newCursorState, bool is_game_specific) { cursorState.set(newCursorState, is_game_specific); } void setCursorHideTimeout(int newcursorHideTimeout, bool is_game_specific) { cursorHideTimeout.set(newcursorHideTimeout, is_game_specific); } void setMicDevice(std::string device, bool is_game_specific) { micDevice.set(device, is_game_specific); } void setMainOutputDevice(std::string device, bool is_game_specific) { mainOutputDevice.set(device, is_game_specific); } void setPadSpkOutputDevice(std::string device, bool is_game_specific) { padSpkOutputDevice.set(device, is_game_specific); } void setTrophyNotificationDuration(double newTrophyNotificationDuration, bool is_game_specific) { trophyNotificationDuration.set(newTrophyNotificationDuration, is_game_specific); } void setLanguage(u32 language, bool is_game_specific) { m_language.set(language, is_game_specific); } void setNeoMode(bool enable, bool is_game_specific) { isNeo.set(enable, is_game_specific); } void setDevKitConsole(bool enable, bool is_game_specific) { isDevKit.set(enable, is_game_specific); } void setLogType(const string& type, bool is_game_specific) { logType.set(type, is_game_specific); } void setLogFilter(const string& type, bool is_game_specific) { logFilter.set(type, is_game_specific); } void setSeparateLogFilesEnabled(bool enabled, bool is_game_specific) { isSeparateLogFilesEnabled.set(enabled, is_game_specific); } void setUserName(const string& name, bool is_game_specific) { userName.set(name, is_game_specific); } void setChooseHomeTab(const string& type, bool is_game_specific) { chooseHomeTab.set(type, is_game_specific); } void setUseSpecialPad(bool use) { useSpecialPad.base_value = use; } void setSpecialPadClass(int type) { specialPadClass.base_value = type; } void setIsMotionControlsEnabled(bool use, bool is_game_specific) { isMotionControlsEnabled.set(use, is_game_specific); } void setCompatibilityEnabled(bool use) { compatibilityData = use; } void setCheckCompatibilityOnStartup(bool use) { checkCompatibilityOnStartup = use; } bool addGameInstallDir(const std::filesystem::path& dir, bool enabled) { for (const auto& install_dir : settings_install_dirs) { if (install_dir.path == dir) { return false; } } settings_install_dirs.push_back({dir, enabled}); return true; } void removeGameInstallDir(const std::filesystem::path& dir) { auto iterator = std::find_if(settings_install_dirs.begin(), settings_install_dirs.end(), [&dir](const GameInstallDir& install_dir) { return install_dir.path == dir; }); if (iterator != settings_install_dirs.end()) { settings_install_dirs.erase(iterator); } } void setGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled) { auto iterator = std::find_if(settings_install_dirs.begin(), settings_install_dirs.end(), [&dir](const GameInstallDir& install_dir) { return install_dir.path == dir; }); if (iterator != settings_install_dirs.end()) { iterator->enabled = enabled; } } void setAddonInstallDir(const std::filesystem::path& dir) { settings_addon_install_dir = dir; } void setGameInstallDirs(const std::vector& dirs_config) { settings_install_dirs.clear(); for (const auto& dir : dirs_config) { settings_install_dirs.push_back({dir, true}); } } void setAllGameInstallDirs(const std::vector& dirs_config) { settings_install_dirs = dirs_config; } void setSaveDataPath(const std::filesystem::path& path) { save_data_path = path; } const std::vector getGameInstallDirs() { std::vector enabled_dirs; for (const auto& dir : settings_install_dirs) { if (dir.enabled) { enabled_dirs.push_back(dir.path); } } return enabled_dirs; } const std::vector getGameInstallDirsEnabled() { std::vector enabled_dirs; for (const auto& dir : settings_install_dirs) { enabled_dirs.push_back(dir.enabled); } return enabled_dirs; } std::filesystem::path getAddonInstallDir() { if (settings_addon_install_dir.empty()) { // Default for users without a config file or a config file from before this option existed return Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "addcont"; } return settings_addon_install_dir; } u32 GetLanguage() { return m_language.get(); } bool getSeparateLogFilesEnabled() { return isSeparateLogFilesEnabled.get(); } bool getPSNSignedIn() { return isPSNSignedIn.get(); } void setPSNSignedIn(bool sign, bool is_game_specific) { isPSNSignedIn.set(sign, is_game_specific); } string getDefaultControllerID() { return defaultControllerID.get(); } void setDefaultControllerID(string id) { defaultControllerID.base_value = id; } bool getBackgroundControllerInput() { return backgroundControllerInput.get(); } void setBackgroundControllerInput(bool enable, bool is_game_specific) { backgroundControllerInput.set(enable, is_game_specific); } bool getFsrEnabled() { return fsrEnabled.get(); } void setFsrEnabled(bool enable, bool is_game_specific) { fsrEnabled.set(enable, is_game_specific); } bool getRcasEnabled() { return rcasEnabled.get(); } void setRcasEnabled(bool enable, bool is_game_specific) { rcasEnabled.set(enable, is_game_specific); } int getRcasAttenuation() { return rcasAttenuation.get(); } void setRcasAttenuation(int value, bool is_game_specific) { rcasAttenuation.set(value, is_game_specific); } void load(const std::filesystem::path& path, bool is_game_specific) { // If the configuration file does not exist, create it and return, unless it is game specific std::error_code error; if (!std::filesystem::exists(path, error)) { if (!is_game_specific) { save(path); } return; } toml::value data; try { std::ifstream ifs; ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); ifs.open(path, std::ios_base::binary); data = toml::parse(ifs, string{fmt::UTF(path.filename().u8string()).data}); } catch (std::exception& ex) { fmt::print("Got exception trying to load config file. Exception: {}\n", ex.what()); return; } if (data.contains("General")) { const toml::value& general = data.at("General"); volumeSlider.setFromToml(general, "volumeSlider", is_game_specific); isNeo.setFromToml(general, "isPS4Pro", is_game_specific); isDevKit.setFromToml(general, "isDevKit", is_game_specific); isPSNSignedIn.setFromToml(general, "isPSNSignedIn", is_game_specific); isTrophyPopupDisabled.setFromToml(general, "isTrophyPopupDisabled", is_game_specific); trophyNotificationDuration.setFromToml(general, "trophyNotificationDuration", is_game_specific); enableDiscordRPC = toml::find_or(general, "enableDiscordRPC", enableDiscordRPC); logFilter.setFromToml(general, "logFilter", is_game_specific); logType.setFromToml(general, "logType", is_game_specific); userName.setFromToml(general, "userName", is_game_specific); isShowSplash.setFromToml(general, "showSplash", is_game_specific); isSideTrophy.setFromToml(general, "sideTrophy", is_game_specific); compatibilityData = toml::find_or(general, "compatibilityEnabled", compatibilityData); checkCompatibilityOnStartup = toml::find_or(general, "checkCompatibilityOnStartup", checkCompatibilityOnStartup); isConnectedToNetwork.setFromToml(general, "isConnectedToNetwork", is_game_specific); chooseHomeTab.setFromToml(general, "chooseHomeTab", is_game_specific); defaultControllerID.setFromToml(general, "defaultControllerID", is_game_specific); sys_modules_path = toml::find_fs_path_or(general, "sysModulesPath", sys_modules_path); } if (data.contains("Input")) { const toml::value& input = data.at("Input"); cursorState.setFromToml(input, "cursorState", is_game_specific); cursorHideTimeout.setFromToml(input, "cursorHideTimeout", is_game_specific); useSpecialPad.setFromToml(input, "useSpecialPad", is_game_specific); specialPadClass.setFromToml(input, "specialPadClass", is_game_specific); isMotionControlsEnabled.setFromToml(input, "isMotionControlsEnabled", is_game_specific); useUnifiedInputConfig.setFromToml(input, "useUnifiedInputConfig", is_game_specific); backgroundControllerInput.setFromToml(input, "backgroundControllerInput", is_game_specific); } if (data.contains("Audio")) { const toml::value& audio = data.at("Audio"); micDevice.setFromToml(audio, "micDevice", is_game_specific); mainOutputDevice.setFromToml(audio, "mainOutputDevice", is_game_specific); padSpkOutputDevice.setFromToml(audio, "padSpkOutputDevice", is_game_specific); } if (data.contains("GPU")) { const toml::value& gpu = data.at("GPU"); windowWidth.setFromToml(gpu, "screenWidth", is_game_specific); windowHeight.setFromToml(gpu, "screenHeight", is_game_specific); internalScreenWidth.setFromToml(gpu, "internalScreenWidth", is_game_specific); internalScreenHeight.setFromToml(gpu, "internalScreenHeight", is_game_specific); isNullGpu.setFromToml(gpu, "nullGpu", is_game_specific); shouldCopyGPUBuffers.setFromToml(gpu, "copyGPUBuffers", is_game_specific); readbacksEnabled.setFromToml(gpu, "readbacks", is_game_specific); readbackLinearImagesEnabled.setFromToml(gpu, "readbackLinearImages", is_game_specific); directMemoryAccessEnabled.setFromToml(gpu, "directMemoryAccess", is_game_specific); shouldDumpShaders.setFromToml(gpu, "dumpShaders", is_game_specific); shouldPatchShaders.setFromToml(gpu, "patchShaders", is_game_specific); vblankFrequency.setFromToml(gpu, "vblankFrequency", is_game_specific); isFullscreen.setFromToml(gpu, "Fullscreen", is_game_specific); fullscreenMode.setFromToml(gpu, "FullscreenMode", is_game_specific); presentMode.setFromToml(gpu, "presentMode", is_game_specific); isHDRAllowed.setFromToml(gpu, "allowHDR", is_game_specific); fsrEnabled.setFromToml(gpu, "fsrEnabled", is_game_specific); rcasEnabled.setFromToml(gpu, "rcasEnabled", is_game_specific); rcasAttenuation.setFromToml(gpu, "rcasAttenuation", is_game_specific); } if (data.contains("Vulkan")) { const toml::value& vk = data.at("Vulkan"); gpuId.setFromToml(vk, "gpuId", is_game_specific); vkValidation.setFromToml(vk, "validation", is_game_specific); vkValidationSync.setFromToml(vk, "validation_sync", is_game_specific); vkValidationGpu.setFromToml(vk, "validation_gpu", is_game_specific); vkCrashDiagnostic.setFromToml(vk, "crashDiagnostic", is_game_specific); vkHostMarkers.setFromToml(vk, "hostMarkers", is_game_specific); vkGuestMarkers.setFromToml(vk, "guestMarkers", is_game_specific); rdocEnable.setFromToml(vk, "rdocEnable", is_game_specific); } string current_version = {}; if (data.contains("Debug")) { const toml::value& debug = data.at("Debug"); isDebugDump.setFromToml(debug, "DebugDump", is_game_specific); isSeparateLogFilesEnabled.setFromToml(debug, "isSeparateLogFilesEnabled", is_game_specific); isShaderDebug.setFromToml(debug, "CollectShader", is_game_specific); isFpsColor.setFromToml(debug, "FPSColor", is_game_specific); logEnabled.setFromToml(debug, "logEnabled", is_game_specific); current_version = toml::find_or(debug, "ConfigVersion", current_version); } if (data.contains("GUI")) { const toml::value& gui = data.at("GUI"); load_game_size = toml::find_or(gui, "loadGameSizeEnabled", load_game_size); const auto install_dir_array = toml::find_or>(gui, "installDirs", {}); try { install_dirs_enabled = toml::find>(gui, "installDirsEnabled"); } catch (...) { // If it does not exist, assume that all are enabled. install_dirs_enabled.resize(install_dir_array.size(), true); } if (install_dirs_enabled.size() < install_dir_array.size()) { install_dirs_enabled.resize(install_dir_array.size(), true); } settings_install_dirs.clear(); for (size_t i = 0; i < install_dir_array.size(); i++) { settings_install_dirs.push_back( {std::filesystem::path{install_dir_array[i]}, install_dirs_enabled[i]}); } save_data_path = toml::find_fs_path_or(gui, "saveDataPath", save_data_path); settings_addon_install_dir = toml::find_fs_path_or(gui, "addonInstallDir", settings_addon_install_dir); } if (data.contains("Settings")) { const toml::value& settings = data.at("Settings"); m_language.setFromToml(settings, "consoleLanguage", is_game_specific); } if (data.contains("Keys")) { const toml::value& keys = data.at("Keys"); trophyKey = toml::find_or(keys, "TrophyKey", trophyKey); } // Run save after loading to generate any missing fields with default values. if (config_version != current_version && !is_game_specific) { save(path); } } void sortTomlSections(toml::ordered_value& data) { toml::ordered_value ordered_data; std::vector section_order = {"General", "Input", "GPU", "Vulkan", "Debug", "Keys", "GUI", "Settings"}; for (const auto& section : section_order) { if (data.contains(section)) { std::vector keys; for (const auto& item : data.at(section).as_table()) { keys.push_back(item.first); } std::sort(keys.begin(), keys.end(), [](const string& a, const string& b) { return std::lexicographical_compare( a.begin(), a.end(), b.begin(), b.end(), [](char a_char, char b_char) { return std::tolower(a_char) < std::tolower(b_char); }); }); toml::ordered_value ordered_section; for (const auto& key : keys) { ordered_section[key] = data.at(section).at(key); } ordered_data[section] = ordered_section; } } data = ordered_data; } void save(const std::filesystem::path& path, bool is_game_specific) { toml::ordered_value data; std::error_code error; if (std::filesystem::exists(path, error)) { try { std::ifstream ifs; ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); ifs.open(path, std::ios_base::binary); data = toml::parse( ifs, string{fmt::UTF(path.filename().u8string()).data}); } catch (const std::exception& ex) { fmt::print("Exception trying to parse config file. Exception: {}\n", ex.what()); return; } } else { if (error) { fmt::print("Filesystem error: {}\n", error.message()); } fmt::print("Saving new configuration file {}\n", fmt::UTF(path.u8string())); } // Entries saved by the game-specific settings GUI volumeSlider.setTomlValue(data, "General", "volumeSlider", is_game_specific); isTrophyPopupDisabled.setTomlValue(data, "General", "isTrophyPopupDisabled", is_game_specific); trophyNotificationDuration.setTomlValue(data, "General", "trophyNotificationDuration", is_game_specific); logFilter.setTomlValue(data, "General", "logFilter", is_game_specific); logType.setTomlValue(data, "General", "logType", is_game_specific); userName.setTomlValue(data, "General", "userName", is_game_specific); chooseHomeTab.setTomlValue(data, "General", "chooseHomeTab", is_game_specific); isShowSplash.setTomlValue(data, "General", "showSplash", is_game_specific); isSideTrophy.setTomlValue(data, "General", "sideTrophy", is_game_specific); isNeo.setTomlValue(data, "General", "isPS4Pro", is_game_specific); isDevKit.setTomlValue(data, "General", "isDevKit", is_game_specific); isPSNSignedIn.setTomlValue(data, "General", "isPSNSignedIn", is_game_specific); isConnectedToNetwork.setTomlValue(data, "General", "isConnectedToNetwork", is_game_specific); cursorState.setTomlValue(data, "Input", "cursorState", is_game_specific); cursorHideTimeout.setTomlValue(data, "Input", "cursorHideTimeout", is_game_specific); isMotionControlsEnabled.setTomlValue(data, "Input", "isMotionControlsEnabled", is_game_specific); backgroundControllerInput.setTomlValue(data, "Input", "backgroundControllerInput", is_game_specific); micDevice.setTomlValue(data, "Audio", "micDevice", is_game_specific); mainOutputDevice.setTomlValue(data, "Audio", "mainOutputDevice", is_game_specific); padSpkOutputDevice.setTomlValue(data, "Audio", "padSpkOutputDevice", is_game_specific); windowWidth.setTomlValue(data, "GPU", "screenWidth", is_game_specific); windowHeight.setTomlValue(data, "GPU", "screenHeight", is_game_specific); isNullGpu.setTomlValue(data, "GPU", "nullGpu", is_game_specific); shouldCopyGPUBuffers.setTomlValue(data, "GPU", "copyGPUBuffers", is_game_specific); readbacksEnabled.setTomlValue(data, "GPU", "readbacks", is_game_specific); readbackLinearImagesEnabled.setTomlValue(data, "GPU", "readbackLinearImages", is_game_specific); shouldDumpShaders.setTomlValue(data, "GPU", "dumpShaders", is_game_specific); vblankFrequency.setTomlValue(data, "GPU", "vblankFrequency", is_game_specific); isFullscreen.setTomlValue(data, "GPU", "Fullscreen", is_game_specific); fullscreenMode.setTomlValue(data, "GPU", "FullscreenMode", is_game_specific); presentMode.setTomlValue(data, "GPU", "presentMode", is_game_specific); isHDRAllowed.setTomlValue(data, "GPU", "allowHDR", is_game_specific); fsrEnabled.setTomlValue(data, "GPU", "fsrEnabled", is_game_specific); rcasEnabled.setTomlValue(data, "GPU", "rcasEnabled", is_game_specific); rcasAttenuation.setTomlValue(data, "GPU", "rcasAttenuation", is_game_specific); directMemoryAccessEnabled.setTomlValue(data, "GPU", "directMemoryAccess", is_game_specific); gpuId.setTomlValue(data, "Vulkan", "gpuId", is_game_specific); vkValidation.setTomlValue(data, "Vulkan", "validation", is_game_specific); vkValidationSync.setTomlValue(data, "Vulkan", "validation_sync", is_game_specific); vkCrashDiagnostic.setTomlValue(data, "Vulkan", "crashDiagnostic", is_game_specific); vkHostMarkers.setTomlValue(data, "Vulkan", "hostMarkers", is_game_specific); vkGuestMarkers.setTomlValue(data, "Vulkan", "guestMarkers", is_game_specific); rdocEnable.setTomlValue(data, "Vulkan", "rdocEnable", is_game_specific); isDebugDump.setTomlValue(data, "Debug", "DebugDump", is_game_specific); isShaderDebug.setTomlValue(data, "Debug", "CollectShader", is_game_specific); isSeparateLogFilesEnabled.setTomlValue(data, "Debug", "isSeparateLogFilesEnabled", is_game_specific); logEnabled.setTomlValue(data, "Debug", "logEnabled", is_game_specific); m_language.setTomlValue(data, "Settings", "consoleLanguage", is_game_specific); if (!is_game_specific) { std::vector install_dirs; std::vector install_dirs_enabled; // temporary structure for ordering struct DirEntry { string path_str; bool enabled; }; std::vector sorted_dirs; for (const auto& dirInfo : settings_install_dirs) { sorted_dirs.push_back( {string{fmt::UTF(dirInfo.path.u8string()).data}, dirInfo.enabled}); } // Sort directories alphabetically std::sort(sorted_dirs.begin(), sorted_dirs.end(), [](const DirEntry& a, const DirEntry& b) { return std::lexicographical_compare( a.path_str.begin(), a.path_str.end(), b.path_str.begin(), b.path_str.end(), [](char a_char, char b_char) { return std::tolower(a_char) < std::tolower(b_char); }); }); for (const auto& entry : sorted_dirs) { install_dirs.push_back(entry.path_str); install_dirs_enabled.push_back(entry.enabled); } // Non game-specific entries data["General"]["enableDiscordRPC"] = enableDiscordRPC; data["General"]["compatibilityEnabled"] = compatibilityData; data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup; data["General"]["sysModulesPath"] = string{fmt::UTF(sys_modules_path.u8string()).data}; data["GUI"]["installDirs"] = install_dirs; data["GUI"]["installDirsEnabled"] = install_dirs_enabled; data["GUI"]["saveDataPath"] = string{fmt::UTF(save_data_path.u8string()).data}; data["GUI"]["loadGameSizeEnabled"] = load_game_size; data["GUI"]["addonInstallDir"] = string{fmt::UTF(settings_addon_install_dir.u8string()).data}; data["Debug"]["ConfigVersion"] = config_version; data["Keys"]["TrophyKey"] = trophyKey; // Do not save these entries in the game-specific dialog since they are not in the GUI data["General"]["defaultControllerID"] = defaultControllerID.base_value; data["Input"]["useSpecialPad"] = useSpecialPad.base_value; data["Input"]["specialPadClass"] = specialPadClass.base_value; data["Input"]["useUnifiedInputConfig"] = useUnifiedInputConfig.base_value; data["GPU"]["internalScreenWidth"] = internalScreenWidth.base_value; data["GPU"]["internalScreenHeight"] = internalScreenHeight.base_value; data["GPU"]["patchShaders"] = shouldPatchShaders.base_value; data["Vulkan"]["validation_gpu"] = vkValidationGpu.base_value; data["Debug"]["FPSColor"] = isFpsColor.base_value; } // Sorting of TOML sections sortTomlSections(data); std::ofstream file(path, std::ios::binary); file << data; file.close(); } void setDefaultValues(bool is_game_specific) { // Entries with game-specific settings that are in the game-specific setings GUI but not in // the global settings GUI if (is_game_specific) { readbacksEnabled.set(false, is_game_specific); readbackLinearImagesEnabled.set(false, is_game_specific); isNeo.set(false, is_game_specific); isDevKit.set(false, is_game_specific); isPSNSignedIn.set(false, is_game_specific); isConnectedToNetwork.set(false, is_game_specific); directMemoryAccessEnabled.set(false, is_game_specific); } // Entries with game-specific settings that are in both the game-specific and global GUI // GS - General volumeSlider.set(100, is_game_specific); isTrophyPopupDisabled.set(false, is_game_specific); trophyNotificationDuration.set(6.0, is_game_specific); logFilter.set("", is_game_specific); logType.set("sync", is_game_specific); userName.set("shadPS4", is_game_specific); chooseHomeTab.set("General", is_game_specific); isShowSplash.set(false, is_game_specific); isSideTrophy.set("right", is_game_specific); // GS - Input cursorState.set(HideCursorState::Idle, is_game_specific); cursorHideTimeout.set(5, is_game_specific); isMotionControlsEnabled.set(true, is_game_specific); backgroundControllerInput.set(false, is_game_specific); // GS - Audio micDevice.set("Default Device", is_game_specific); // GS - GPU windowWidth.set(1280, is_game_specific); windowHeight.set(720, is_game_specific); isNullGpu.set(false, is_game_specific); shouldCopyGPUBuffers.set(false, is_game_specific); shouldDumpShaders.set(false, is_game_specific); vblankFrequency.set(60, is_game_specific); isFullscreen.set(false, is_game_specific); fullscreenMode.set("Windowed", is_game_specific); presentMode.set("Mailbox", is_game_specific); isHDRAllowed.set(false, is_game_specific); fsrEnabled.set(true, is_game_specific); rcasEnabled.set(true, is_game_specific); rcasAttenuation.set(250, is_game_specific); // GS - Vulkan gpuId.set(-1, is_game_specific); vkValidation.set(false, is_game_specific); vkValidationSync.set(false, is_game_specific); vkValidationGpu.set(false, is_game_specific); vkCrashDiagnostic.set(false, is_game_specific); vkHostMarkers.set(false, is_game_specific); vkGuestMarkers.set(false, is_game_specific); rdocEnable.set(false, is_game_specific); // GS - Debug isDebugDump.set(false, is_game_specific); isShaderDebug.set(false, is_game_specific); isSeparateLogFilesEnabled.set(false, is_game_specific); logEnabled.set(true, is_game_specific); // GS - Settings m_language.set(1, is_game_specific); // All other entries if (!is_game_specific) { // General enableDiscordRPC = false; compatibilityData = false; checkCompatibilityOnStartup = false; // Input useSpecialPad.base_value = false; specialPadClass.base_value = 1; useUnifiedInputConfig.base_value = true; controllerCustomColorRGB[0] = 0; controllerCustomColorRGB[1] = 0; controllerCustomColorRGB[2] = 255; // TODO: Change to be game specific mainOutputDevice = "Default Device"; padSpkOutputDevice = "Default Device"; // GPU shouldPatchShaders.base_value = false; internalScreenWidth.base_value = 1280; internalScreenHeight.base_value = 720; // GUI load_game_size = true; // Debug isFpsColor.base_value = true; } } constexpr std::string_view GetDefaultGlobalConfig() { return R"(# Anything put here will be loaded for all games, # alongside the game's config or default.ini depending on your preference. hotkey_renderdoc_capture = f12 hotkey_fullscreen = f11 hotkey_show_fps = f10 hotkey_pause = f9 hotkey_reload_inputs = f8 hotkey_toggle_mouse_to_joystick = f7 hotkey_toggle_mouse_to_gyro = f6 hotkey_quit = lctrl, lshift, end )"; } constexpr std::string_view GetDefaultInputConfig() { return R"(#Feeling lost? Check out the Help section! # Keyboard bindings triangle = kp8 circle = kp6 cross = kp2 square = kp4 # Alternatives for users without a keypad triangle = c circle = b cross = n square = v l1 = q r1 = u l2 = e r2 = o l3 = x r3 = m options = enter touchpad_center = space pad_up = up pad_down = down pad_left = left pad_right = right axis_left_x_minus = a axis_left_x_plus = d axis_left_y_minus = w axis_left_y_plus = s axis_right_x_minus = j axis_right_x_plus = l axis_right_y_minus = i axis_right_y_plus = k # Controller bindings triangle = triangle cross = cross square = square circle = circle l1 = l1 l2 = l2 l3 = l3 r1 = r1 r2 = r2 r3 = r3 options = options touchpad_center = back pad_up = pad_up pad_down = pad_down pad_left = pad_left pad_right = pad_right axis_left_x = axis_left_x axis_left_y = axis_left_y axis_right_x = axis_right_x axis_right_y = axis_right_y # Range of deadzones: 1 (almost none) to 127 (max) analog_deadzone = leftjoystick, 2, 127 analog_deadzone = rightjoystick, 2, 127 override_controller_color = false, 0, 0, 255 )"; } std::filesystem::path GetFoolproofInputConfigFile(const string& game_id) { // Read configuration file of the game, and if it doesn't exist, generate it from default // If that doesn't exist either, generate that from getDefaultConfig() and try again // If even the folder is missing, we start with that. const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "input_config"; const auto config_file = config_dir / (game_id + ".ini"); const auto default_config_file = config_dir / "default.ini"; // Ensure the config directory exists if (!std::filesystem::exists(config_dir)) { std::filesystem::create_directories(config_dir); } // Check if the default config exists if (!std::filesystem::exists(default_config_file)) { // If the default config is also missing, create it from getDefaultConfig() const auto default_config = GetDefaultInputConfig(); std::ofstream default_config_stream(default_config_file); if (default_config_stream) { default_config_stream << default_config; } } // if empty, we only need to execute the function up until this point if (game_id.empty()) { return default_config_file; } // Create global config if it doesn't exist yet if (game_id == "global" && !std::filesystem::exists(config_file)) { if (!std::filesystem::exists(config_file)) { const auto global_config = GetDefaultGlobalConfig(); std::ofstream global_config_stream(config_file); if (global_config_stream) { global_config_stream << global_config; } } } // If game-specific config doesn't exist, create it from the default config if (!std::filesystem::exists(config_file)) { std::filesystem::copy(default_config_file, config_file); } return config_file; } void resetGameSpecificValue(std::string entry) { if (entry == "volumeSlider") { volumeSlider.game_specific_value = std::nullopt; } } } // namespace Config