From 319db3bebe0e8a788c4e14bc3e3b1e3c3580f7f5 Mon Sep 17 00:00:00 2001 From: rainmakerv2 <30595646+rainmakerv3@users.noreply.github.com> Date: Wed, 10 Sep 2025 17:18:39 +0800 Subject: [PATCH] Qt: Add GUI for game-specific settings (#3533) * Open settings dialog from context menu * initial version complete * add context menu item to delete game config if it exists * Create game config from base value instead of default value * Require confirmation before deleting game configs with menu item * fix rebase * Reset game specific values when creating a new game config * Add icon for entries with game config * clang format * Add submenu for game-specific settings * Log if game-specific config exists, remove hidden tab from tab selection * Add other experimental options to game-specific GUI * clang format * Add flag to specify if game-specific file needs creation * refactor: remove additional arguments, reset game-specific status on save instead * Fix return * cleanup - remove unneeded load * Set tab to general if hidden tab is set as default * Cleanup variable names and strings, default tab fix, volumeslider fix * cleanup: missed a couple of variables to standardize * More readable way to reset volume slider --- REUSE.toml | 1 + src/common/config.cpp | 565 ++++++++++++++++++--------------- src/common/config.h | 105 +++--- src/emulator.cpp | 5 + src/images/game_settings.png | Bin 0 -> 11368 bytes src/qt_gui/game_grid_frame.cpp | 18 ++ src/qt_gui/game_grid_frame.h | 1 + src/qt_gui/game_list_frame.cpp | 11 +- src/qt_gui/gui_context_menus.h | 42 ++- src/qt_gui/settings_dialog.cpp | 535 ++++++++++++++++++------------- src/qt_gui/settings_dialog.h | 8 +- src/qt_gui/settings_dialog.ui | 226 +++++++------ src/shadps4.qrc | 1 + 13 files changed, 893 insertions(+), 625 deletions(-) create mode 100644 src/images/game_settings.png diff --git a/REUSE.toml b/REUSE.toml index dc593e9a8..a94827b3b 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -74,6 +74,7 @@ path = [ "src/images/youtube.svg", "src/images/trophy.wav", "src/images/hotkey.png", + "src/images/game_settings.png", "src/shadps4.qrc", "src/shadps4.rc", "src/qt_gui/translations/update_translation.sh", diff --git a/src/common/config.cpp b/src/common/config.cpp index 1462f2aa6..e6ccbe346 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -94,6 +94,18 @@ public: 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(); // } @@ -242,8 +254,8 @@ std::filesystem::path GetSaveDataPath() { return save_data_path; } -void setVolumeSlider(int volumeValue) { - volumeSlider.base_value = volumeValue; +void setVolumeSlider(int volumeValue, bool is_game_specific) { + volumeSlider.set(volumeValue, is_game_specific); } void setLoadGameSizeEnabled(bool enable) { @@ -426,16 +438,16 @@ bool getVkGuestMarkersEnabled() { return vkGuestMarkers.get(); } -void setVkCrashDiagnosticEnabled(bool enable) { - vkCrashDiagnostic.base_value = enable; +void setVkCrashDiagnosticEnabled(bool enable, bool is_game_specific) { + vkCrashDiagnostic.set(enable, is_game_specific); } -void setVkHostMarkersEnabled(bool enable) { - vkHostMarkers.base_value = enable; +void setVkHostMarkersEnabled(bool enable, bool is_game_specific) { + vkHostMarkers.set(enable, is_game_specific); } -void setVkGuestMarkersEnabled(bool enable) { - vkGuestMarkers.base_value = enable; +void setVkGuestMarkersEnabled(bool enable, bool is_game_specific) { + vkGuestMarkers.set(enable, is_game_specific); } bool getCompatibilityEnabled() { @@ -450,16 +462,20 @@ bool getIsConnectedToNetwork() { return isConnectedToNetwork.get(); } -void setGpuId(s32 selectedGpuId) { - gpuId.base_value = selectedGpuId; +void setConnectedToNetwork(bool enable, bool is_game_specific) { + isConnectedToNetwork.set(enable, is_game_specific); } -void setWindowWidth(u32 width) { - windowWidth.base_value = width; +void setGpuId(s32 selectedGpuId, bool is_game_specific) { + gpuId.set(selectedGpuId, is_game_specific); } -void setWindowHeight(u32 height) { - windowHeight.base_value = height; +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) { @@ -470,132 +486,136 @@ void setInternalScreenHeight(u32 height) { internalScreenHeight.base_value = height; } -void setDebugDump(bool enable) { - isDebugDump.base_value = enable; +void setDebugDump(bool enable, bool is_game_specific) { + isDebugDump.set(enable, is_game_specific); } -void setLoggingEnabled(bool enable) { - logEnabled.base_value = enable; +void setLoggingEnabled(bool enable, bool is_game_specific) { + logEnabled.set(enable, is_game_specific); } -void setCollectShaderForDebug(bool enable) { - isShaderDebug.base_value = enable; +void setCollectShaderForDebug(bool enable, bool is_game_specific) { + isShaderDebug.set(enable, is_game_specific); } -void setShowSplash(bool enable) { - isShowSplash.base_value = enable; +void setShowSplash(bool enable, bool is_game_specific) { + isShowSplash.set(enable, is_game_specific); } -void setSideTrophy(string side) { - isSideTrophy.base_value = side; +void setSideTrophy(string side, bool is_game_specific) { + isSideTrophy.set(side, is_game_specific); } -void setNullGpu(bool enable) { - isNullGpu.base_value = enable; +void setNullGpu(bool enable, bool is_game_specific) { + isNullGpu.set(enable, is_game_specific); } -void setAllowHDR(bool enable) { - isHDRAllowed.base_value = enable; +void setAllowHDR(bool enable, bool is_game_specific) { + isHDRAllowed.set(enable, is_game_specific); } -void setCopyGPUCmdBuffers(bool enable) { - shouldCopyGPUBuffers.base_value = enable; +void setCopyGPUCmdBuffers(bool enable, bool is_game_specific) { + shouldCopyGPUBuffers.set(enable, is_game_specific); } -void setReadbacks(bool enable) { - readbacksEnabled.base_value = enable; +void setReadbacks(bool enable, bool is_game_specific) { + readbacksEnabled.set(enable, is_game_specific); } -void setReadbackLinearImages(bool enable) { - readbackLinearImagesEnabled.base_value = enable; +void setReadbackLinearImages(bool enable, bool is_game_specific) { + readbackLinearImagesEnabled.set(enable, is_game_specific); } -void setDirectMemoryAccess(bool enable) { - directMemoryAccessEnabled.base_value = enable; +void setDirectMemoryAccess(bool enable, bool is_game_specific) { + directMemoryAccessEnabled.set(enable, is_game_specific); } -void setDumpShaders(bool enable) { - shouldDumpShaders.base_value = enable; +void setDumpShaders(bool enable, bool is_game_specific) { + shouldDumpShaders.set(enable, is_game_specific); } -void setVkValidation(bool enable) { - vkValidation.base_value = enable; +void setVkValidation(bool enable, bool is_game_specific) { + vkValidation.set(enable, is_game_specific); } -void setVkSyncValidation(bool enable) { - vkValidationSync.base_value = enable; +void setVkSyncValidation(bool enable, bool is_game_specific) { + vkValidationSync.set(enable, is_game_specific); } -void setRdocEnabled(bool enable) { - rdocEnable.base_value = enable; +void setRdocEnabled(bool enable, bool is_game_specific) { + rdocEnable.set(enable, is_game_specific); } -void setVblankFreq(u32 value) { - vblankFrequency.base_value = value; +void setVblankFreq(u32 value, bool is_game_specific) { + vblankFrequency.set(value, is_game_specific); } -void setIsFullscreen(bool enable) { - isFullscreen.base_value = enable; +void setIsFullscreen(bool enable, bool is_game_specific) { + isFullscreen.set(enable, is_game_specific); } -void setFullscreenMode(string mode) { - fullscreenMode.base_value = mode; +void setFullscreenMode(string mode, bool is_game_specific) { + fullscreenMode.set(mode, is_game_specific); } -void setPresentMode(std::string mode) { - presentMode.base_value = mode; +void setPresentMode(std::string mode, bool is_game_specific) { + presentMode.set(mode, is_game_specific); } -void setisTrophyPopupDisabled(bool disable) { - isTrophyPopupDisabled.base_value = disable; +void setisTrophyPopupDisabled(bool disable, bool is_game_specific) { + isTrophyPopupDisabled.set(disable, is_game_specific); } void setEnableDiscordRPC(bool enable) { enableDiscordRPC = enable; } -void setCursorState(s16 newCursorState) { - cursorState.base_value = newCursorState; +void setCursorState(s16 newCursorState, bool is_game_specific) { + cursorState.set(newCursorState, is_game_specific); } -void setCursorHideTimeout(int newcursorHideTimeout) { - cursorHideTimeout.base_value = newcursorHideTimeout; +void setCursorHideTimeout(int newcursorHideTimeout, bool is_game_specific) { + cursorHideTimeout.set(newcursorHideTimeout, is_game_specific); } -void setMicDevice(string device) { - micDevice.base_value = device; +void setMicDevice(string device, bool is_game_specific) { + micDevice.set(device, is_game_specific); } -void setTrophyNotificationDuration(double newTrophyNotificationDuration) { - trophyNotificationDuration.base_value = newTrophyNotificationDuration; +void setTrophyNotificationDuration(double newTrophyNotificationDuration, bool is_game_specific) { + trophyNotificationDuration.set(newTrophyNotificationDuration, is_game_specific); } -void setLanguage(u32 language) { - m_language.base_value = language; +void setLanguage(u32 language, bool is_game_specific) { + m_language.set(language, is_game_specific); } -void setNeoMode(bool enable) { - isNeo.base_value = enable; +void setNeoMode(bool enable, bool is_game_specific) { + isNeo.set(enable, is_game_specific); } -void setLogType(const string& type) { - logType.base_value = type; +void setDevKitConsole(bool enable, bool is_game_specific) { + isDevKit.set(enable, is_game_specific); } -void setLogFilter(const string& type) { - logFilter.base_value = type; +void setLogType(const string& type, bool is_game_specific) { + logType.set(type, is_game_specific); } -void setSeparateLogFilesEnabled(bool enabled) { - isSeparateLogFilesEnabled.base_value = enabled; +void setLogFilter(const string& type, bool is_game_specific) { + logFilter.set(type, is_game_specific); } -void setUserName(const string& type) { - userName.base_value = type; +void setSeparateLogFilesEnabled(bool enabled, bool is_game_specific) { + isSeparateLogFilesEnabled.set(enabled, is_game_specific); } -void setChooseHomeTab(const string& type) { - chooseHomeTab.base_value = type; +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) { @@ -606,8 +626,8 @@ void setSpecialPadClass(int type) { specialPadClass.base_value = type; } -void setIsMotionControlsEnabled(bool use) { - isMotionControlsEnabled.base_value = use; +void setIsMotionControlsEnabled(bool use, bool is_game_specific) { + isMotionControlsEnabled.set(use, is_game_specific); } void setCompatibilityEnabled(bool use) { @@ -703,8 +723,8 @@ bool getPSNSignedIn() { return isPSNSignedIn.get(); } -void setPSNSignedIn(bool sign) { - isPSNSignedIn.base_value = sign; +void setPSNSignedIn(bool sign, bool is_game_specific) { + isPSNSignedIn.set(sign, is_game_specific); } string getDefaultControllerID() { @@ -719,32 +739,32 @@ bool getBackgroundControllerInput() { return backgroundControllerInput.get(); } -void setBackgroundControllerInput(bool enable) { - backgroundControllerInput.base_value = enable; +void setBackgroundControllerInput(bool enable, bool is_game_specific) { + backgroundControllerInput.set(enable, is_game_specific); } bool getFsrEnabled() { return fsrEnabled.get(); } -void setFsrEnabled(bool enable) { - fsrEnabled.base_value = enable; +void setFsrEnabled(bool enable, bool is_game_specific) { + fsrEnabled.set(enable, is_game_specific); } bool getRcasEnabled() { return rcasEnabled.get(); } -void setRcasEnabled(bool enable) { - rcasEnabled.base_value = enable; +void setRcasEnabled(bool enable, bool is_game_specific) { + rcasEnabled.set(enable, is_game_specific); } int getRcasAttenuation() { return rcasAttenuation.get(); } -void setRcasAttenuation(int value) { - rcasAttenuation.base_value = value; +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) { @@ -934,7 +954,7 @@ void sortTomlSections(toml::ordered_value& data) { data = ordered_data; } -void save(const std::filesystem::path& path) { +void save(const std::filesystem::path& path, bool is_game_specific) { toml::ordered_value data; std::error_code error; @@ -956,100 +976,124 @@ void save(const std::filesystem::path& path) { fmt::print("Saving new configuration file {}\n", fmt::UTF(path.u8string())); } - data["General"]["volumeSlider"] = volumeSlider.base_value; - data["General"]["isPS4Pro"] = isNeo.base_value; - data["General"]["isDevKit"] = isDevKit.base_value; - data["General"]["isPSNSignedIn"] = isPSNSignedIn.base_value; - data["General"]["isTrophyPopupDisabled"] = isTrophyPopupDisabled.base_value; - data["General"]["trophyNotificationDuration"] = trophyNotificationDuration.base_value; - data["General"]["logFilter"] = logFilter.base_value; - data["General"]["logType"] = logType.base_value; - data["General"]["userName"] = userName.base_value; - data["General"]["chooseHomeTab"] = chooseHomeTab.base_value; - data["General"]["showSplash"] = isShowSplash.base_value; - data["General"]["sideTrophy"] = isSideTrophy.base_value; - data["General"]["isConnectedToNetwork"] = isConnectedToNetwork.base_value; - data["General"]["defaultControllerID"] = defaultControllerID.base_value; - data["General"]["enableDiscordRPC"] = enableDiscordRPC; - data["General"]["compatibilityEnabled"] = compatibilityData; - data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup; - data["Input"]["cursorState"] = cursorState.base_value; - data["Input"]["cursorHideTimeout"] = cursorHideTimeout.base_value; - data["Input"]["useSpecialPad"] = useSpecialPad.base_value; - data["Input"]["specialPadClass"] = specialPadClass.base_value; - data["Input"]["isMotionControlsEnabled"] = isMotionControlsEnabled.base_value; - data["Input"]["useUnifiedInputConfig"] = useUnifiedInputConfig.base_value; - data["Input"]["micDevice"] = micDevice.base_value; - data["Input"]["backgroundControllerInput"] = backgroundControllerInput.base_value; - data["GPU"]["screenWidth"] = windowWidth.base_value; - data["GPU"]["screenHeight"] = windowHeight.base_value; - data["GPU"]["internalScreenWidth"] = internalScreenWidth.base_value; - data["GPU"]["internalScreenHeight"] = internalScreenHeight.base_value; - data["GPU"]["nullGpu"] = isNullGpu.base_value; - data["GPU"]["copyGPUBuffers"] = shouldCopyGPUBuffers.base_value; - data["GPU"]["readbacks"] = readbacksEnabled.base_value; - data["GPU"]["readbackLinearImages"] = readbackLinearImagesEnabled.base_value; - data["GPU"]["directMemoryAccess"] = directMemoryAccessEnabled.base_value; - data["GPU"]["dumpShaders"] = shouldDumpShaders.base_value; - data["GPU"]["patchShaders"] = shouldPatchShaders.base_value; - data["GPU"]["vblankFrequency"] = vblankFrequency.base_value; - data["GPU"]["Fullscreen"] = isFullscreen.base_value; - data["GPU"]["FullscreenMode"] = fullscreenMode.base_value; - data["GPU"]["presentMode"] = presentMode.base_value; - data["GPU"]["allowHDR"] = isHDRAllowed.base_value; - data["GPU"]["fsrEnabled"] = fsrEnabled.base_value; - data["GPU"]["rcasEnabled"] = rcasEnabled.base_value; - data["GPU"]["rcasAttenuation"] = rcasAttenuation.base_value; - data["Vulkan"]["gpuId"] = gpuId.base_value; - data["Vulkan"]["validation"] = vkValidation.base_value; - data["Vulkan"]["validation_sync"] = vkValidationSync.base_value; - data["Vulkan"]["validation_gpu"] = vkValidationGpu.base_value; - data["Vulkan"]["crashDiagnostic"] = vkCrashDiagnostic.base_value; - data["Vulkan"]["hostMarkers"] = vkHostMarkers.base_value; - data["Vulkan"]["guestMarkers"] = vkGuestMarkers.base_value; - data["Vulkan"]["rdocEnable"] = rdocEnable.base_value; - data["Debug"]["DebugDump"] = isDebugDump.base_value; - data["Debug"]["CollectShader"] = isShaderDebug.base_value; - data["Debug"]["isSeparateLogFilesEnabled"] = isSeparateLogFilesEnabled.base_value; - data["Debug"]["FPSColor"] = isFpsColor.base_value; - data["Debug"]["logEnabled"] = logEnabled.base_value; - data["Debug"]["ConfigVersion"] = config_version; - data["Keys"]["TrophyKey"] = trophyKey; + // 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); - std::vector install_dirs; - std::vector install_dirs_enabled; + cursorState.setTomlValue(data, "Input", "cursorState", is_game_specific); + cursorHideTimeout.setTomlValue(data, "Input", "cursorHideTimeout", is_game_specific); + isMotionControlsEnabled.setTomlValue(data, "Input", "isMotionControlsEnabled", + is_game_specific); + micDevice.setTomlValue(data, "Input", "micDevice", is_game_specific); + backgroundControllerInput.setTomlValue(data, "Input", "backgroundControllerInput", + is_game_specific); - // temporary structure for ordering - struct DirEntry { - string path_str; - bool enabled; - }; + 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); - std::vector sorted_dirs; - for (const auto& dirInfo : settings_install_dirs) { - sorted_dirs.push_back({string{fmt::UTF(dirInfo.path.u8string()).data}, dirInfo.enabled}); + 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", "logEnable", is_game_specific); + + m_language.setTomlValue(data, "Settings", "consoleLanguage", is_game_specific); + + // All other entries + 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["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; } - // 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); - } - - 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["Settings"]["consoleLanguage"] = m_language.base_value; - // Sorting of TOML sections sortTomlSections(data); @@ -1058,82 +1102,101 @@ void save(const std::filesystem::path& path) { file.close(); } -void setDefaultValues() { - // General - volumeSlider = 100; - isNeo = false; - isDevKit = false; - isPSNSignedIn = false; - isTrophyPopupDisabled = false; - trophyNotificationDuration = 6.0; - enableDiscordRPC = false; - logFilter = ""; - logType = "sync"; - userName = "shadPS4"; - chooseHomeTab = "General"; - isShowSplash = false; - isSideTrophy = "right"; - compatibilityData = false; - checkCompatibilityOnStartup = false; - isConnectedToNetwork = false; +void setDefaultValues(bool is_game_specific) { - // Input - cursorState = HideCursorState::Idle; - cursorHideTimeout = 5; - useSpecialPad = false; - specialPadClass = 1; - isMotionControlsEnabled = true; - useUnifiedInputConfig = true; - overrideControllerColor = false; - controllerCustomColorRGB[0] = 0; - controllerCustomColorRGB[1] = 0; - controllerCustomColorRGB[2] = 255; - micDevice = "Default Device"; - backgroundControllerInput = false; + // Entries with game-specific settings that are in the game-specific setings GUI but not in + // the global settings GUI + if (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); + } - // GPU - windowWidth = 1280; - windowHeight = 720; - internalScreenWidth = 1280; - internalScreenHeight = 720; - isNullGpu = false; - shouldCopyGPUBuffers = false; - readbacksEnabled = false; - readbackLinearImagesEnabled = false; - directMemoryAccessEnabled = false; - shouldDumpShaders = false; - shouldPatchShaders = false; - vblankFrequency = 60; - isFullscreen = false; - fullscreenMode = "Windowed"; - presentMode = "Mailbox"; - isHDRAllowed = false; - fsrEnabled = true; - rcasEnabled = true; - rcasAttenuation = 250; + // 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); - // Vulkan - gpuId = -1; - vkValidation = false; - vkValidationSync = false; - vkValidationGpu = false; - vkCrashDiagnostic = false; - vkHostMarkers = false; - vkGuestMarkers = false; - rdocEnable = false; + // GS - Input + cursorState.set(HideCursorState::Idle, is_game_specific); + cursorHideTimeout.set(5, is_game_specific); + isMotionControlsEnabled.set(true, is_game_specific); + micDevice.set("Default Device", is_game_specific); + backgroundControllerInput.set(false, is_game_specific); - // Debug - isDebugDump = false; - isShaderDebug = false; - isSeparateLogFilesEnabled = false; - isFpsColor = true; - logEnabled = true; + // 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); + readbacksEnabled.set(false, is_game_specific); + readbackLinearImagesEnabled.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); - // GUI - load_game_size = true; + // 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); - // Settings - m_language = 1; + // 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; + overrideControllerColor = false; + controllerCustomColorRGB[0] = 0; + controllerCustomColorRGB[1] = 0; + controllerCustomColorRGB[2] = 255; + + // 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() { @@ -1272,4 +1335,10 @@ std::filesystem::path GetFoolproofInputConfigFile(const string& game_id) { return config_file; } +void resetGameSpecificValue(std::string entry) { + if (entry == "volumeSlider") { + volumeSlider.game_specific_value = std::nullopt; + } +} + } // namespace Config diff --git a/src/common/config.h b/src/common/config.h index 6631723af..1e285a13b 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -17,110 +17,118 @@ struct GameInstallDir { enum HideCursorState : int { Never, Idle, Always }; void load(const std::filesystem::path& path, bool is_game_specific = false); -void save(const std::filesystem::path& path); +void save(const std::filesystem::path& path, bool is_game_specific = false); +void resetGameSpecificValue(std::string entry); int getVolumeSlider(); -void setVolumeSlider(int volumeValue); +void setVolumeSlider(int volumeValue, bool is_game_specific = false); std::string getTrophyKey(); void setTrophyKey(std::string key); bool getIsFullscreen(); -void setIsFullscreen(bool enable); +void setIsFullscreen(bool enable, bool is_game_specific = false); std::string getFullscreenMode(); -void setFullscreenMode(std::string mode); +void setFullscreenMode(std::string mode, bool is_game_specific = false); std::string getPresentMode(); -void setPresentMode(std::string mode); +void setPresentMode(std::string mode, bool is_game_specific = false); u32 getWindowWidth(); u32 getWindowHeight(); -void setWindowWidth(u32 width); -void setWindowHeight(u32 height); +void setWindowWidth(u32 width, bool is_game_specific = false); +void setWindowHeight(u32 height, bool is_game_specific = false); u32 getInternalScreenWidth(); u32 getInternalScreenHeight(); void setInternalScreenWidth(u32 width); void setInternalScreenHeight(u32 height); bool debugDump(); -void setDebugDump(bool enable); +void setDebugDump(bool enable, bool is_game_specific = false); s32 getGpuId(); -void setGpuId(s32 selectedGpuId); +void setGpuId(s32 selectedGpuId, bool is_game_specific = false); bool allowHDR(); -void setAllowHDR(bool enable); +void setAllowHDR(bool enable, bool is_game_specific = false); bool collectShadersForDebug(); -void setCollectShaderForDebug(bool enable); +void setCollectShaderForDebug(bool enable, bool is_game_specific = false); bool showSplash(); -void setShowSplash(bool enable); +void setShowSplash(bool enable, bool is_game_specific = false); std::string sideTrophy(); -void setSideTrophy(std::string side); +void setSideTrophy(std::string side, bool is_game_specific = false); bool nullGpu(); -void setNullGpu(bool enable); +void setNullGpu(bool enable, bool is_game_specific = false); bool copyGPUCmdBuffers(); -void setCopyGPUCmdBuffers(bool enable); +void setCopyGPUCmdBuffers(bool enable, bool is_game_specific = false); bool readbacks(); -void setReadbacks(bool enable); +void setReadbacks(bool enable, bool is_game_specific = false); bool readbackLinearImages(); -void setReadbackLinearImages(bool enable); +void setReadbackLinearImages(bool enable, bool is_game_specific = false); bool directMemoryAccess(); -void setDirectMemoryAccess(bool enable); +void setDirectMemoryAccess(bool enable, bool is_game_specific = false); bool dumpShaders(); -void setDumpShaders(bool enable); +void setDumpShaders(bool enable, bool is_game_specific = false); u32 vblankFreq(); -void setVblankFreq(u32 value); +void setVblankFreq(u32 value, bool is_game_specific = false); bool getisTrophyPopupDisabled(); -void setisTrophyPopupDisabled(bool disable); +void setisTrophyPopupDisabled(bool disable, bool is_game_specific = false); s16 getCursorState(); -void setCursorState(s16 cursorState); +void setCursorState(s16 cursorState, bool is_game_specific = false); bool vkValidationEnabled(); -void setVkValidation(bool enable); +void setVkValidation(bool enable, bool is_game_specific = false); bool vkValidationSyncEnabled(); -void setVkSyncValidation(bool enable); +void setVkSyncValidation(bool enable, bool is_game_specific = false); bool getVkCrashDiagnosticEnabled(); -void setVkCrashDiagnosticEnabled(bool enable); +void setVkCrashDiagnosticEnabled(bool enable, bool is_game_specific = false); bool getVkHostMarkersEnabled(); -void setVkHostMarkersEnabled(bool enable); +void setVkHostMarkersEnabled(bool enable, bool is_game_specific = false); bool getVkGuestMarkersEnabled(); -void setVkGuestMarkersEnabled(bool enable); +void setVkGuestMarkersEnabled(bool enable, bool is_game_specific = false); bool getEnableDiscordRPC(); void setEnableDiscordRPC(bool enable); bool isRdocEnabled(); -void setRdocEnabled(bool enable); +void setRdocEnabled(bool enable, bool is_game_specific = false); std::string getLogType(); -void setLogType(const std::string& type); +void setLogType(const std::string& type, bool is_game_specific = false); std::string getLogFilter(); -void setLogFilter(const std::string& type); +void setLogFilter(const std::string& type, bool is_game_specific = false); double getTrophyNotificationDuration(); -void setTrophyNotificationDuration(double newTrophyNotificationDuration); +void setTrophyNotificationDuration(double newTrophyNotificationDuration, + bool is_game_specific = false); int getCursorHideTimeout(); std::string getMicDevice(); -void setCursorHideTimeout(int newcursorHideTimeout); -void setMicDevice(std::string device); -void setSeparateLogFilesEnabled(bool enabled); +void setCursorHideTimeout(int newcursorHideTimeout, bool is_game_specific = false); +void setMicDevice(std::string device, bool is_game_specific = false); +void setSeparateLogFilesEnabled(bool enabled, bool is_game_specific = false); bool getSeparateLogFilesEnabled(); u32 GetLanguage(); -void setLanguage(u32 language); +void setLanguage(u32 language, bool is_game_specific = false); void setUseSpecialPad(bool use); bool getUseSpecialPad(); void setSpecialPadClass(int type); int getSpecialPadClass(); bool getPSNSignedIn(); -void setPSNSignedIn(bool sign); // no ui setting -bool patchShaders(); // no set -bool fpsColor(); // no set +void setPSNSignedIn(bool sign, bool is_game_specific = false); +bool patchShaders(); // no set +bool fpsColor(); // no set bool isNeoModeConsole(); -void setNeoMode(bool enable); // no ui setting -bool isDevKitConsole(); // no set +void setNeoMode(bool enable, bool is_game_specific = false); +bool isDevKitConsole(); +void setDevKitConsole(bool enable, bool is_game_specific = false); + bool vkValidationGpuEnabled(); // no set bool getIsMotionControlsEnabled(); -void setIsMotionControlsEnabled(bool use); +void setIsMotionControlsEnabled(bool use, bool is_game_specific = false); std::string getDefaultControllerID(); void setDefaultControllerID(std::string id); bool getBackgroundControllerInput(); -void setBackgroundControllerInput(bool enable); +void setBackgroundControllerInput(bool enable, bool is_game_specific = false); bool getLoggingEnabled(); -void setLoggingEnabled(bool enable); +void setLoggingEnabled(bool enable, bool is_game_specific = false); bool getFsrEnabled(); -void setFsrEnabled(bool enable); +void setFsrEnabled(bool enable, bool is_game_specific = false); bool getRcasEnabled(); -void setRcasEnabled(bool enable); +void setRcasEnabled(bool enable, bool is_game_specific = false); int getRcasAttenuation(); -void setRcasAttenuation(int value); +void setRcasAttenuation(int value, bool is_game_specific = false); +bool getIsConnectedToNetwork(); +void setConnectedToNetwork(bool enable, bool is_game_specific = false); +void setUserName(const std::string& name, bool is_game_specific = false); +void setChooseHomeTab(const std::string& type, bool is_game_specific = false); // TODO bool GetLoadGameSizeEnabled(); @@ -128,7 +136,6 @@ std::filesystem::path GetSaveDataPath(); void setLoadGameSizeEnabled(bool enable); bool getCompatibilityEnabled(); bool getCheckCompatibilityOnStartup(); -bool getIsConnectedToNetwork(); std::string getUserName(); std::string getChooseHomeTab(); bool GetUseUnifiedInputConfig(); @@ -137,8 +144,6 @@ bool GetOverrideControllerColor(); void SetOverrideControllerColor(bool enable); int* GetControllerCustomColor(); void SetControllerCustomColor(int r, int b, int g); -void setUserName(const std::string& type); -void setChooseHomeTab(const std::string& type); void setGameInstallDirs(const std::vector& dirs_config); void setAllGameInstallDirs(const std::vector& dirs_config); void setSaveDataPath(const std::filesystem::path& path); @@ -154,7 +159,7 @@ const std::vector getGameInstallDirs(); const std::vector getGameInstallDirsEnabled(); std::filesystem::path getAddonInstallDir(); -void setDefaultValues(); +void setDefaultValues(bool is_game_specific = false); constexpr std::string_view GetDefaultGlobalConfig(); std::filesystem::path GetFoolproofInputConfigFile(const std::string& game_id = ""); diff --git a/src/emulator.cpp b/src/emulator.cpp index 1388c9027..26ff40dc1 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -136,8 +136,13 @@ void Emulator::Run(std::filesystem::path file, const std::vector ar LOG_INFO(Loader, "Description {}", Common::g_scm_desc); LOG_INFO(Loader, "Remote {}", Common::g_scm_remote_url); + const bool has_game_config = std::filesystem::exists( + Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (id + ".toml")); + LOG_INFO(Config, "Game-specific config exists: {}", has_game_config); + LOG_INFO(Config, "General LogType: {}", Config::getLogType()); LOG_INFO(Config, "General isNeo: {}", Config::isNeoModeConsole()); + LOG_INFO(Config, "General isDevKit: {}", Config::isDevKitConsole()); LOG_INFO(Config, "General isConnectedToNetwork: {}", Config::getIsConnectedToNetwork()); LOG_INFO(Config, "General isPsnSignedIn: {}", Config::getPSNSignedIn()); LOG_INFO(Config, "GPU isNullGpu: {}", Config::nullGpu()); diff --git a/src/images/game_settings.png b/src/images/game_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..6147052287f4111491116c9453ab80c896fb4d3f GIT binary patch literal 11368 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 literal 0 HcmV?d00001 diff --git a/src/qt_gui/game_grid_frame.cpp b/src/qt_gui/game_grid_frame.cpp index dda73fa17..7608e1150 100644 --- a/src/qt_gui/game_grid_frame.cpp +++ b/src/qt_gui/game_grid_frame.cpp @@ -126,6 +126,7 @@ void GameGridFrame::PopulateGameGrid(QVector m_games_search, bool from image_label->setPixmap(QPixmap::fromImage(icon)); image_label->move(0, 0); SetFavoriteIcon(image_container, m_games_, gameCounter); + SetGameConfigIcon(image_container, m_games_, gameCounter); QLabel* name_label = new QLabel(QString::fromStdString(m_games_[gameCounter].serial)); name_label->setAlignment(Qt::AlignHCenter); @@ -254,6 +255,23 @@ void GameGridFrame::SetFavoriteIcon(QWidget* parentWidget, QVector m_g label->setObjectName("favoriteIcon"); } +void GameGridFrame::SetGameConfigIcon(QWidget* parentWidget, QVector m_games_, + int gameCounter) { + std::string serialStr = m_games_[gameCounter].serial; + + bool hasGameConfig = std::filesystem::exists( + Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (serialStr + ".toml")); + + QLabel* label = new QLabel(parentWidget); + label->setPixmap(QPixmap(":images/game_settings.png") + .scaled(icon_size / 3.8, icon_size / 3.8, Qt::KeepAspectRatio, + Qt::SmoothTransformation)); + label->move(2, 2); + label->raise(); + label->setVisible(hasGameConfig); + label->setObjectName("gameConfigIcon"); +} + void GameGridFrame::SortByFavorite(QVector* game_list) { std::sort(game_list->begin(), game_list->end(), [this](const GameInfo& a, const GameInfo& b) { return this->CompareWithFavorite(a, b); diff --git a/src/qt_gui/game_grid_frame.h b/src/qt_gui/game_grid_frame.h index 0a12deb1c..4da5b9147 100644 --- a/src/qt_gui/game_grid_frame.h +++ b/src/qt_gui/game_grid_frame.h @@ -40,6 +40,7 @@ private: std::filesystem::path m_current_game_path; // Track current game path to detect changes std::shared_ptr m_gui_settings; void SetFavoriteIcon(QWidget* parentWidget, QVector m_games_, int gameCounter); + void SetGameConfigIcon(QWidget* parentWidget, QVector m_games_, int gameCounter); bool CompareWithFavorite(GameInfo a, GameInfo b); public: diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index e0995d3ef..bdbb3e81f 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -83,10 +83,7 @@ GameListFrame::GameListFrame(std::shared_ptr gui_settings, connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) { int changedFavorite = m_gui_context_menus.RequestGameMenu( pos, m_game_info->m_games, m_compat_info, m_gui_settings, this, true); - if (changedFavorite) { - last_favorite = m_game_info->m_games[this->currentRow()].serial; - PopulateGameList(false); - } + PopulateGameList(false); }); connect(this, &QTableWidget::cellClicked, this, [=, this](int row, int column) { @@ -146,6 +143,12 @@ void GameListFrame::PopulateGameList(bool isInitialPopulation) { for (int i = 0; i < m_game_info->m_games.size(); i++) { SetTableItem(i, 1, QString::fromStdString(m_game_info->m_games[i].name)); + if (std::filesystem::exists(Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / + (m_game_info->m_games[i].serial + ".toml"))) { + QTableWidgetItem* name_item = item(i, 1); + name_item->setIcon(QIcon(":images/game_settings.png")); + } + SetTableItem(i, 3, QString::fromStdString(m_game_info->m_games[i].serial)); SetRegionFlag(i, 4, QString::fromStdString(m_game_info->m_games[i].region)); SetTableItem(i, 5, QString::fromStdString(m_game_info->m_games[i].fw)); diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index 110421002..f5426bd54 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -17,6 +17,7 @@ #include "compatibility_info.h" #include "game_info.h" #include "gui_settings.h" +#include "settings_dialog.h" #include "trophy_viewer.h" #ifdef Q_OS_WIN @@ -66,6 +67,24 @@ public: menu.addMenu(openFolderMenu); + QMenu* gameConfigMenu = new QMenu(tr("Game-specific Settings..."), widget); + QAction gameConfigConfigure(tr("Configure Game-specific Settings"), widget); + QAction gameConfigCreate(tr("Create Game-specific Settings from Global Settings"), widget); + QAction gameConfigDelete(tr("Delete Game-specific Settings"), widget); + + if (std::filesystem::exists(Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / + (m_games[itemID].serial + ".toml"))) { + gameConfigMenu->addAction(&gameConfigConfigure); + } else { + gameConfigMenu->addAction(&gameConfigCreate); + } + + if (std::filesystem::exists(Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / + (m_games[itemID].serial + ".toml"))) + gameConfigMenu->addAction(&gameConfigDelete); + + menu.addMenu(gameConfigMenu); + QString serialStr = QString::fromStdString(m_games[itemID].serial); QList list = gui_settings::Var2List(m_gui_settings->GetValue(gui::favorites_list)); bool isFavorite = list.contains(serialStr); @@ -76,6 +95,7 @@ public: } else { toggleFavorite = new QAction(tr("Add to Favorites"), widget); } + QAction createShortcut(tr("Create Shortcut"), widget); QAction openCheats(tr("Cheats / Patches"), widget); QAction openSfoViewer(tr("SFO Viewer"), widget); @@ -123,9 +143,9 @@ public: // Compatibility submenu. QMenu* compatibilityMenu = new QMenu(tr("Compatibility..."), widget); - QAction* updateCompatibility = new QAction(tr("Update database"), widget); - QAction* viewCompatibilityReport = new QAction(tr("View report"), widget); - QAction* submitCompatibilityReport = new QAction(tr("Submit a report"), widget); + QAction* updateCompatibility = new QAction(tr("Update Database"), widget); + QAction* viewCompatibilityReport = new QAction(tr("View Report"), widget); + QAction* submitCompatibilityReport = new QAction(tr("Submit a Report"), widget); compatibilityMenu->addAction(updateCompatibility); compatibilityMenu->addAction(viewCompatibilityReport); @@ -387,6 +407,22 @@ public: [trophyViewer]() { trophyViewer->deleteLater(); }); } + if (selected == &gameConfigConfigure || selected == &gameConfigCreate) { + auto settingsWindow = new SettingsDialog(m_gui_settings, m_compat_info, widget, true, + serialStr.toStdString()); + settingsWindow->exec(); + } + + if (selected == &gameConfigDelete) { + if (QMessageBox::Yes == QMessageBox::question(widget, tr("Confirm deletion"), + tr("Delete game-specific settings?"), + QMessageBox::Yes | QMessageBox::No)) { + std::filesystem::remove( + Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / + (m_games[itemID].serial + ".toml")); + } + } + if (selected == &createShortcut) { QString targetPath; Common::FS::PathToQString(targetPath, m_games[itemID].path); diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 3f53e46bd..d94470128 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -77,22 +77,48 @@ QMap micMap; int backgroundImageOpacitySlider_backup; int bgm_volume_backup; -int volume_slider_backup; static std::vector m_physical_devices; SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, std::shared_ptr m_compat_info, - QWidget* parent) - : QDialog(parent), ui(new Ui::SettingsDialog), m_gui_settings(std::move(gui_settings)) { + QWidget* parent, bool is_game_specific, std::string gsc_serial) + : QDialog(parent), ui(new Ui::SettingsDialog), m_gui_settings(std::move(gui_settings)), + game_specific(is_game_specific), gs_serial(gsc_serial) { + ui->setupUi(this); ui->tabWidgetSettings->setUsesScrollButtons(false); + initialHeight = this->height(); // Add a small clear "x" button inside the Log Filter input ui->logFilterLineEdit->setClearButtonEnabled(true); - initialHeight = this->height(); - const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); + if (game_specific) { + ui->tabWidgetSettings->setTabVisible(5, false); + ui->chooseHomeTabComboBox->removeItem(5); + + ui->label_Trophy->setVisible(false); + ui->trophyKeyLineEdit->setVisible(false); + ui->CompatgroupBox->setVisible(false); + ui->gameSizeCheckBox->setVisible(false); + ui->GUIBackgroundImageGroupBox->setVisible(false); + ui->GUIMusicGroupBox->setVisible(false); + ui->gameSizeCheckBox->setVisible(false); + ui->updaterGroupBox->setVisible(false); + ui->discordRPCCheckbox->setVisible(false); + ui->emulatorLanguageGroupBox->setVisible(false); + } else { + ui->dmaCheckBox->setVisible(false); + ui->devkitCheckBox->setVisible(false); + ui->neoCheckBox->setVisible(false); + ui->networkConnectedCheckBox->setVisible(false); + ui->psnSignInCheckBox->setVisible(false); + } + + std::filesystem::path config_file = + game_specific + ? Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (gs_serial + ".toml") + : Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"; ui->buttonBox->button(QDialogButtonBox::StandardButton::Close)->setFocus(); @@ -168,25 +194,23 @@ SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close); connect(ui->buttonBox, &QDialogButtonBox::clicked, this, - [this, config_dir](QAbstractButton* button) { + [this, config_file](QAbstractButton* button) { if (button == ui->buttonBox->button(QDialogButtonBox::Save)) { is_saving = true; - UpdateSettings(); - Config::save(config_dir / "config.toml"); + UpdateSettings(game_specific); + Config::save(config_file, game_specific); QWidget::close(); } else if (button == ui->buttonBox->button(QDialogButtonBox::Apply)) { - UpdateSettings(); - Config::save(config_dir / "config.toml"); + UpdateSettings(game_specific); + Config::save(config_file, game_specific); } else if (button == ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)) { - Config::setDefaultValues(); setDefaultValues(); - Config::save(config_dir / "config.toml"); + Config::setDefaultValues(game_specific); + Config::save(config_file, game_specific); LoadValuesFromConfig(); } else if (button == ui->buttonBox->button(QDialogButtonBox::Close)) { ui->backgroundImageOpacitySlider->setValue(backgroundImageOpacitySlider_backup); emit BackgroundOpacityChanged(backgroundImageOpacitySlider_backup); - ui->horizontalVolumeSlider->setValue(volume_slider_backup); - Config::setVolumeSlider(volume_slider_backup); ui->BGMVolumeSlider->setValue(bgm_volume_backup); BackgroundMusicPlayer::getInstance().setVolume(bgm_volume_backup); SyncRealTimeWidgetstoConfig(); @@ -210,7 +234,7 @@ SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, { connect(ui->horizontalVolumeSlider, &QSlider::valueChanged, this, [this](int value) { VolumeSliderChange(value); - Config::setVolumeSlider(value); + Config::setVolumeSlider(value, game_specific); Libraries::AudioOut::AdjustVol(); }); @@ -278,9 +302,6 @@ SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, connect(ui->BGMVolumeSlider, &QSlider::valueChanged, this, [](int value) { BackgroundMusicPlayer::getInstance().setVolume(value); }); - connect(ui->chooseHomeTabComboBox, &QComboBox::currentTextChanged, this, - [](const QString& hometab) { Config::setChooseHomeTab(hometab.toStdString()); }); - #if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0)) connect(ui->showBackgroundImageCheckBox, &QCheckBox::stateChanged, this, [this](int state) { #else @@ -508,64 +529,125 @@ void SettingsDialog::closeEvent(QCloseEvent* event) { if (!is_saving) { ui->backgroundImageOpacitySlider->setValue(backgroundImageOpacitySlider_backup); emit BackgroundOpacityChanged(backgroundImageOpacitySlider_backup); - ui->horizontalVolumeSlider->setValue(volume_slider_backup); - Config::setVolumeSlider(volume_slider_backup); ui->BGMVolumeSlider->setValue(bgm_volume_backup); BackgroundMusicPlayer::getInstance().setVolume(bgm_volume_backup); + SyncRealTimeWidgetstoConfig(); } QDialog::closeEvent(event); } void SettingsDialog::LoadValuesFromConfig() { + std::filesystem::path config_file; + config_file = + game_specific + ? Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (gs_serial + ".toml") + : Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"; - std::filesystem::path userdir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); std::error_code error; - if (!std::filesystem::exists(userdir / "config.toml", error)) { - Config::load(userdir / "config.toml"); - return; + bool is_newly_created = false; + if (!std::filesystem::exists(config_file, error)) { + Config::save(config_file, game_specific); + is_newly_created = true; } try { std::ifstream ifs; ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); - const toml::value data = toml::parse(userdir / "config.toml"); + const toml::value data = toml::parse(config_file); } catch (std::exception& ex) { fmt::print("Got exception trying to load config file. Exception: {}\n", ex.what()); return; } - const toml::value data = toml::parse(userdir / "config.toml"); + const toml::value data = toml::parse(config_file); const QVector languageIndexes = {21, 23, 14, 6, 18, 1, 12, 22, 2, 4, 25, 24, 29, 5, 0, 9, 15, 16, 17, 7, 26, 8, 11, 20, 3, 13, 27, 10, 19, 30, 28}; - const auto save_data_path = Config::GetSaveDataPath(); - QString save_data_path_string; - Common::FS::PathToQString(save_data_path_string, save_data_path); - ui->currentSaveDataPath->setText(save_data_path_string); + // Entries with no game-specific settings + if (!game_specific) { + const auto save_data_path = Config::GetSaveDataPath(); + QString save_data_path_string; + Common::FS::PathToQString(save_data_path_string, save_data_path); + ui->currentSaveDataPath->setText(save_data_path_string); - const auto dlc_folder_path = Config::getAddonInstallDir(); - QString dlc_folder_path_string; - Common::FS::PathToQString(dlc_folder_path_string, dlc_folder_path); - ui->currentDLCFolder->setText(dlc_folder_path_string); + const auto dlc_folder_path = Config::getAddonInstallDir(); + QString dlc_folder_path_string; + Common::FS::PathToQString(dlc_folder_path_string, dlc_folder_path); + ui->currentDLCFolder->setText(dlc_folder_path_string); + ui->emulatorLanguageComboBox->setCurrentIndex( + languages[m_gui_settings->GetValue(gui::gen_guiLanguage).toString().toStdString()]); + + ui->playBGMCheckBox->setChecked( + m_gui_settings->GetValue(gui::gl_playBackgroundMusic).toBool()); + + ui->RCASSlider->setValue(toml::find_or(data, "GPU", "rcasAttenuation", 250)); + ui->RCASValue->setText(QString::number(ui->RCASSlider->value() / 1000.0, 'f', 3)); + + ui->BGMVolumeSlider->setValue( + m_gui_settings->GetValue(gui::gl_backgroundMusicVolume).toInt()); + ui->discordRPCCheckbox->setChecked( + toml::find_or(data, "General", "enableDiscordRPC", true)); + + ui->gameSizeCheckBox->setChecked( + toml::find_or(data, "GUI", "loadGameSizeEnabled", true)); + ui->trophyKeyLineEdit->setText( + QString::fromStdString(toml::find_or(data, "Keys", "TrophyKey", ""))); + ui->trophyKeyLineEdit->setEchoMode(QLineEdit::Password); + ui->enableCompatibilityCheckBox->setChecked( + toml::find_or(data, "General", "compatibilityEnabled", false)); + ui->checkCompatibilityOnStartupCheckBox->setChecked( + toml::find_or(data, "General", "checkCompatibilityOnStartup", false)); + + ui->removeFolderButton->setEnabled(!ui->gameFoldersListWidget->selectedItems().isEmpty()); + ui->backgroundImageOpacitySlider->setValue( + m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt()); + ui->showBackgroundImageCheckBox->setChecked( + m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool()); + + backgroundImageOpacitySlider_backup = + m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt(); + bgm_volume_backup = m_gui_settings->GetValue(gui::gl_backgroundMusicVolume).toInt(); + +#ifdef ENABLE_UPDATER + ui->updateCheckBox->setChecked(m_gui_settings->GetValue(gui::gen_checkForUpdates).toBool()); + ui->changelogCheckBox->setChecked( + m_gui_settings->GetValue(gui::gen_showChangeLog).toBool()); + + QString updateChannel = m_gui_settings->GetValue(gui::gen_updateChannel).toString(); + ui->updateComboBox->setCurrentText( + channelMap.key(updateChannel != "Release" && updateChannel != "Nightly" + ? (Common::g_is_release ? "Release" : "Nightly") + : updateChannel)); +#endif + + SyncRealTimeWidgetstoConfig(); + } + + // Entries with game-specific settings, *load these from toml file, not from Config::get* ui->consoleLanguageComboBox->setCurrentIndex( std::distance(languageIndexes.begin(), std::find(languageIndexes.begin(), languageIndexes.end(), toml::find_or(data, "Settings", "consoleLanguage", 6))) % languageIndexes.size()); - ui->emulatorLanguageComboBox->setCurrentIndex( - languages[m_gui_settings->GetValue(gui::gen_guiLanguage).toString().toStdString()]); - ui->hideCursorComboBox->setCurrentIndex(toml::find_or(data, "Input", "cursorState", 1)); - OnCursorStateChanged(toml::find_or(data, "Input", "cursorState", 1)); - ui->idleTimeoutSpinBox->setValue(toml::find_or(data, "Input", "cursorHideTimeout", 5)); - QString micValue = QString::fromStdString(Config::getMicDevice()); + std::string micDevice = + toml::find_or(data, "Input", "micDevice", "Default Device"); + QString micValue = QString::fromStdString(micDevice); int micIndex = ui->micComboBox->findData(micValue); if (micIndex != -1) { ui->micComboBox->setCurrentIndex(micIndex); } else { ui->micComboBox->setCurrentIndex(0); } + + ui->dmaCheckBox->setChecked(toml::find_or(data, "GPU", "directMemoryAccess", false)); + ui->neoCheckBox->setChecked(toml::find_or(data, "General", "isPS4Pro", false)); + ui->devkitCheckBox->setChecked(toml::find_or(data, "General", "isDevKit", false)); + ui->networkConnectedCheckBox->setChecked( + toml::find_or(data, "General", "isConnectedToNetwork", false)); + ui->psnSignInCheckBox->setChecked(toml::find_or(data, "General", "isPSNSignedIn", false)); + // First options is auto selection -1, so gpuId on the GUI will always have to subtract 1 // when setting and add 1 when getting to select the correct gpu in Qt ui->graphicsAdapterBox->setCurrentIndex(toml::find_or(data, "Vulkan", "gpuId", -1) + 1); @@ -577,35 +659,43 @@ void SettingsDialog::LoadValuesFromConfig() { ui->enableHDRCheckBox->setChecked(toml::find_or(data, "GPU", "allowHDR", false)); ui->FSRCheckBox->setChecked(toml::find_or(data, "GPU", "fsrEnabled", true)); ui->RCASCheckBox->setChecked(toml::find_or(data, "GPU", "rcasEnabled", true)); - ui->RCASSlider->setValue(toml::find_or(data, "GPU", "rcasAttenuation", 500)); + ui->RCASSlider->setValue(toml::find_or(data, "GPU", "rcasAttenuation", 250)); ui->RCASValue->setText(QString::number(ui->RCASSlider->value() / 1000.0, 'f', 3)); - - ui->playBGMCheckBox->setChecked(m_gui_settings->GetValue(gui::gl_playBackgroundMusic).toBool()); ui->disableTrophycheckBox->setChecked( toml::find_or(data, "General", "isTrophyPopupDisabled", false)); - ui->popUpDurationSpinBox->setValue(Config::getTrophyNotificationDuration()); - - QString side = QString::fromStdString(Config::sideTrophy()); + ui->popUpDurationSpinBox->setValue( + toml::find_or(data, "General", "trophyNotificationDuration", 6.0)); + ui->showSplashCheckBox->setChecked(toml::find_or(data, "General", "showSplash", false)); + ui->hideCursorComboBox->setCurrentIndex(toml::find_or(data, "Input", "cursorState", 1)); + OnCursorStateChanged(toml::find_or(data, "Input", "cursorState", 1)); + ui->idleTimeoutSpinBox->setValue(toml::find_or(data, "Input", "cursorHideTimeout", 5)); + ui->motionControlsCheckBox->setChecked( + toml::find_or(data, "Input", "isMotionControlsEnabled", true)); + ui->backgroundControllerCheckBox->setChecked( + toml::find_or(data, "Input", "backgroundControllerInput", false)); + std::string sideTrophy = toml::find_or(data, "General", "sideTrophy", "right"); + QString side = QString::fromStdString(sideTrophy); ui->radioButton_Left->setChecked(side == "left"); ui->radioButton_Right->setChecked(side == "right"); ui->radioButton_Top->setChecked(side == "top"); ui->radioButton_Bottom->setChecked(side == "bottom"); - ui->BGMVolumeSlider->setValue(m_gui_settings->GetValue(gui::gl_backgroundMusicVolume).toInt()); - ui->horizontalVolumeSlider->setValue(m_gui_settings->GetValue(gui::gl_VolumeSlider).toInt()); + ui->horizontalVolumeSlider->setValue(toml::find_or(data, "General", "volumeSlider", 100)); ui->volumeText->setText(QString::number(ui->horizontalVolumeSlider->sliderPosition()) + "%"); - ui->discordRPCCheckbox->setChecked( - toml::find_or(data, "General", "enableDiscordRPC", true)); + + std::string fullScreenMode = + toml::find_or(data, "General", "FullscreenMode", "Windowed"); QString translatedText_FullscreenMode = - screenModeMap.key(QString::fromStdString(Config::getFullscreenMode())); + screenModeMap.key(QString::fromStdString(fullScreenMode)); ui->displayModeComboBox->setCurrentText(translatedText_FullscreenMode); - QString translatedText_PresentMode = - presentModeMap.key(QString::fromStdString(Config::getPresentMode())); + + std::string presentMode = toml::find_or(data, "GPU", "presentMode", "Mailbox"); + QString translatedText_PresentMode = presentModeMap.key(QString::fromStdString(presentMode)); ui->presentModeComboBox->setCurrentText(translatedText_PresentMode); - ui->gameSizeCheckBox->setChecked(toml::find_or(data, "GUI", "loadGameSizeEnabled", true)); - ui->showSplashCheckBox->setChecked(toml::find_or(data, "General", "showSplash", false)); - QString translatedText_logType = logTypeMap.key(QString::fromStdString(Config::getLogType())); + + std::string logType = toml::find_or(data, "General", "logType", "sync"); + QString translatedText_logType = logTypeMap.key(QString::fromStdString(logType)); if (!translatedText_logType.isEmpty()) { ui->logTypeComboBox->setCurrentText(translatedText_logType); } @@ -613,9 +703,7 @@ void SettingsDialog::LoadValuesFromConfig() { QString::fromStdString(toml::find_or(data, "General", "logFilter", ""))); ui->userNameLineEdit->setText( QString::fromStdString(toml::find_or(data, "General", "userName", "shadPS4"))); - ui->trophyKeyLineEdit->setText( - QString::fromStdString(toml::find_or(data, "Keys", "TrophyKey", ""))); - ui->trophyKeyLineEdit->setEchoMode(QLineEdit::Password); + ui->debugDump->setChecked(toml::find_or(data, "Debug", "DebugDump", false)); ui->separateLogFilesCheckbox->setChecked( toml::find_or(data, "Debug", "isSeparateLogFilesEnabled", false)); @@ -636,21 +724,6 @@ void SettingsDialog::LoadValuesFromConfig() { ui->enableLoggingCheckBox->setChecked(toml::find_or(data, "Debug", "logEnabled", true)); ui->readbackLinearImagesCheckBox->setChecked( toml::find_or(data, "GPU", "readbackLinearImages", false)); - ui->enableCompatibilityCheckBox->setChecked( - toml::find_or(data, "General", "compatibilityEnabled", false)); - ui->checkCompatibilityOnStartupCheckBox->setChecked( - toml::find_or(data, "General", "checkCompatibilityOnStartup", false)); - -#ifdef ENABLE_UPDATER - ui->updateCheckBox->setChecked(m_gui_settings->GetValue(gui::gen_checkForUpdates).toBool()); - ui->changelogCheckBox->setChecked(m_gui_settings->GetValue(gui::gen_showChangeLog).toBool()); - - QString updateChannel = m_gui_settings->GetValue(gui::gen_updateChannel).toString(); - ui->updateComboBox->setCurrentText( - channelMap.key(updateChannel != "Release" && updateChannel != "Nightly" - ? (Common::g_is_release ? "Release" : "Nightly") - : updateChannel)); -#endif std::string chooseHomeTab = toml::find_or(data, "General", "chooseHomeTab", "General"); @@ -663,26 +736,9 @@ void SettingsDialog::LoadValuesFromConfig() { QStringList tabNames = {tr("General"), tr("GUI"), tr("Graphics"), tr("User"), tr("Input"), tr("Paths"), tr("Log"), tr("Debug")}; int indexTab = tabNames.indexOf(translatedText); - if (indexTab == -1) + if (indexTab == -1 || !ui->tabWidgetSettings->isTabVisible(indexTab) || is_newly_created) indexTab = 0; ui->tabWidgetSettings->setCurrentIndex(indexTab); - - ui->motionControlsCheckBox->setChecked( - toml::find_or(data, "Input", "isMotionControlsEnabled", true)); - ui->backgroundControllerCheckBox->setChecked( - toml::find_or(data, "Input", "backgroundControllerInput", false)); - - ui->removeFolderButton->setEnabled(!ui->gameFoldersListWidget->selectedItems().isEmpty()); - SyncRealTimeWidgetstoConfig(); - ui->backgroundImageOpacitySlider->setValue( - m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt()); - ui->showBackgroundImageCheckBox->setChecked( - m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool()); - - backgroundImageOpacitySlider_backup = - m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt(); - bgm_volume_backup = m_gui_settings->GetValue(gui::gl_backgroundMusicVolume).toInt(); - volume_slider_backup = m_gui_settings->GetValue(gui::gl_VolumeSlider).toInt(); } void SettingsDialog::InitializeEmulatorLanguages() { @@ -908,166 +964,203 @@ bool SettingsDialog::eventFilter(QObject* obj, QEvent* event) { return QDialog::eventFilter(obj, event); } -void SettingsDialog::UpdateSettings() { +void SettingsDialog::UpdateSettings(bool game_specific) { + // Entries that are only in the game-specific gui - Config::setIsFullscreen(screenModeMap.value(ui->displayModeComboBox->currentText()) != - "Windowed"); + if (game_specific) { + Config::setDirectMemoryAccess(ui->dmaCheckBox->isChecked(), true); + Config::setDevKitConsole(ui->devkitCheckBox->isChecked(), true); + Config::setNeoMode(ui->neoCheckBox->isChecked(), true); + Config::setConnectedToNetwork(ui->networkConnectedCheckBox->isChecked(), true); + Config::setPSNSignedIn(ui->psnSignInCheckBox->isChecked(), true); + } + + // Entries with game-specific settings, needs the game-specific arg + Config::setIsFullscreen( + screenModeMap.value(ui->displayModeComboBox->currentText()) != "Windowed", game_specific); Config::setFullscreenMode( - screenModeMap.value(ui->displayModeComboBox->currentText()).toStdString()); + screenModeMap.value(ui->displayModeComboBox->currentText()).toStdString(), game_specific); Config::setPresentMode( - presentModeMap.value(ui->presentModeComboBox->currentText()).toStdString()); - Config::setIsMotionControlsEnabled(ui->motionControlsCheckBox->isChecked()); - Config::setBackgroundControllerInput(ui->backgroundControllerCheckBox->isChecked()); - Config::setisTrophyPopupDisabled(ui->disableTrophycheckBox->isChecked()); - Config::setTrophyNotificationDuration(ui->popUpDurationSpinBox->value()); + presentModeMap.value(ui->presentModeComboBox->currentText()).toStdString(), game_specific); + Config::setIsMotionControlsEnabled(ui->motionControlsCheckBox->isChecked(), game_specific); + Config::setBackgroundControllerInput(ui->backgroundControllerCheckBox->isChecked(), + game_specific); + Config::setisTrophyPopupDisabled(ui->disableTrophycheckBox->isChecked(), game_specific); + Config::setTrophyNotificationDuration(ui->popUpDurationSpinBox->value(), game_specific); if (ui->radioButton_Top->isChecked()) { - Config::setSideTrophy("top"); + Config::setSideTrophy("top", game_specific); } else if (ui->radioButton_Left->isChecked()) { - Config::setSideTrophy("left"); + Config::setSideTrophy("left", game_specific); } else if (ui->radioButton_Right->isChecked()) { - Config::setSideTrophy("right"); + Config::setSideTrophy("right", game_specific); } else if (ui->radioButton_Bottom->isChecked()) { - Config::setSideTrophy("bottom"); + Config::setSideTrophy("bottom", game_specific); } - m_gui_settings->SetValue(gui::gl_playBackgroundMusic, ui->playBGMCheckBox->isChecked()); - Config::setLoggingEnabled(ui->enableLoggingCheckBox->isChecked()); - Config::setAllowHDR(ui->enableHDRCheckBox->isChecked()); - Config::setLogType(logTypeMap.value(ui->logTypeComboBox->currentText()).toStdString()); - Config::setMicDevice(ui->micComboBox->currentData().toString().toStdString()); - Config::setLogFilter(ui->logFilterLineEdit->text().toStdString()); - Config::setUserName(ui->userNameLineEdit->text().toStdString()); - Config::setTrophyKey(ui->trophyKeyLineEdit->text().toStdString()); - Config::setCursorState(ui->hideCursorComboBox->currentIndex()); - Config::setCursorHideTimeout(ui->idleTimeoutSpinBox->value()); - Config::setGpuId(ui->graphicsAdapterBox->currentIndex() - 1); - m_gui_settings->SetValue(gui::gl_VolumeSlider, ui->horizontalVolumeSlider->value()); - m_gui_settings->SetValue(gui::gl_backgroundMusicVolume, ui->BGMVolumeSlider->value()); + + Config::setLoggingEnabled(ui->enableLoggingCheckBox->isChecked(), game_specific); + Config::setAllowHDR(ui->enableHDRCheckBox->isChecked(), game_specific); + Config::setLogType(logTypeMap.value(ui->logTypeComboBox->currentText()).toStdString(), + game_specific); + Config::setMicDevice(ui->micComboBox->currentData().toString().toStdString(), game_specific); + Config::setLogFilter(ui->logFilterLineEdit->text().toStdString(), game_specific); + Config::setUserName(ui->userNameLineEdit->text().toStdString(), game_specific); + Config::setCursorState(ui->hideCursorComboBox->currentIndex(), game_specific); + Config::setCursorHideTimeout(ui->hideCursorComboBox->currentIndex(), game_specific); + Config::setGpuId(ui->graphicsAdapterBox->currentIndex() - 1, game_specific); + Config::setVolumeSlider(ui->horizontalVolumeSlider->value(), game_specific); Config::setLanguage(languageIndexes[ui->consoleLanguageComboBox->currentIndex()]); - Config::setEnableDiscordRPC(ui->discordRPCCheckbox->isChecked()); - Config::setWindowWidth(ui->widthSpinBox->value()); - Config::setWindowHeight(ui->heightSpinBox->value()); - Config::setVblankFreq(ui->vblankSpinBox->value()); - Config::setDumpShaders(ui->dumpShadersCheckBox->isChecked()); - Config::setNullGpu(ui->nullGpuCheckBox->isChecked()); - Config::setFsrEnabled(ui->FSRCheckBox->isChecked()); - Config::setRcasEnabled(ui->RCASCheckBox->isChecked()); - Config::setRcasAttenuation(ui->RCASSlider->value()); - Config::setLoadGameSizeEnabled(ui->gameSizeCheckBox->isChecked()); - Config::setShowSplash(ui->showSplashCheckBox->isChecked()); - Config::setDebugDump(ui->debugDump->isChecked()); - Config::setSeparateLogFilesEnabled(ui->separateLogFilesCheckbox->isChecked()); - Config::setVkValidation(ui->vkValidationCheckBox->isChecked()); - Config::setVkSyncValidation(ui->vkSyncValidationCheckBox->isChecked()); - Config::setRdocEnabled(ui->rdocCheckBox->isChecked()); - Config::setVkHostMarkersEnabled(ui->hostMarkersCheckBox->isChecked()); - Config::setVkGuestMarkersEnabled(ui->guestMarkersCheckBox->isChecked()); - Config::setVkCrashDiagnosticEnabled(ui->crashDiagnosticsCheckBox->isChecked()); - Config::setReadbacks(ui->readbacksCheckBox->isChecked()); - Config::setReadbackLinearImages(ui->readbackLinearImagesCheckBox->isChecked()); - Config::setCollectShaderForDebug(ui->collectShaderCheckBox->isChecked()); - Config::setCopyGPUCmdBuffers(ui->copyGPUBuffersCheckBox->isChecked()); - m_gui_settings->SetValue(gui::gen_checkForUpdates, ui->updateCheckBox->isChecked()); - m_gui_settings->SetValue(gui::gen_showChangeLog, ui->changelogCheckBox->isChecked()); - m_gui_settings->SetValue(gui::gen_updateChannel, - channelMap.value(ui->updateComboBox->currentText())); + Config::setWindowWidth(ui->widthSpinBox->value(), game_specific); + Config::setWindowHeight(ui->heightSpinBox->value(), game_specific); + Config::setVblankFreq(ui->vblankSpinBox->value(), game_specific); + Config::setDumpShaders(ui->dumpShadersCheckBox->isChecked(), game_specific); + Config::setNullGpu(ui->nullGpuCheckBox->isChecked(), game_specific); + Config::setFsrEnabled(ui->FSRCheckBox->isChecked(), game_specific); + Config::setRcasEnabled(ui->RCASCheckBox->isChecked(), game_specific); + Config::setRcasAttenuation(ui->RCASSlider->value(), game_specific); + Config::setShowSplash(ui->showSplashCheckBox->isChecked(), game_specific); + Config::setDebugDump(ui->debugDump->isChecked(), game_specific); + Config::setSeparateLogFilesEnabled(ui->separateLogFilesCheckbox->isChecked(), game_specific); + Config::setVkValidation(ui->vkValidationCheckBox->isChecked(), game_specific); + Config::setVkSyncValidation(ui->vkSyncValidationCheckBox->isChecked(), game_specific); + Config::setRdocEnabled(ui->rdocCheckBox->isChecked(), game_specific); + Config::setVkHostMarkersEnabled(ui->hostMarkersCheckBox->isChecked(), game_specific); + Config::setVkGuestMarkersEnabled(ui->guestMarkersCheckBox->isChecked(), game_specific); + Config::setVkCrashDiagnosticEnabled(ui->crashDiagnosticsCheckBox->isChecked(), game_specific); + Config::setReadbacks(ui->readbacksCheckBox->isChecked(), game_specific); + Config::setReadbackLinearImages(ui->readbackLinearImagesCheckBox->isChecked(), game_specific); + Config::setCollectShaderForDebug(ui->collectShaderCheckBox->isChecked(), game_specific); + Config::setCopyGPUCmdBuffers(ui->copyGPUBuffersCheckBox->isChecked(), game_specific); Config::setChooseHomeTab( - chooseHomeTabMap.value(ui->chooseHomeTabComboBox->currentText()).toStdString()); - Config::setCompatibilityEnabled(ui->enableCompatibilityCheckBox->isChecked()); - Config::setCheckCompatibilityOnStartup(ui->checkCompatibilityOnStartupCheckBox->isChecked()); - m_gui_settings->SetValue(gui::gl_backgroundImageOpacity, - std::clamp(ui->backgroundImageOpacitySlider->value(), 0, 100)); - emit BackgroundOpacityChanged(ui->backgroundImageOpacitySlider->value()); - m_gui_settings->SetValue(gui::gl_showBackgroundImage, - ui->showBackgroundImageCheckBox->isChecked()); + chooseHomeTabMap.value(ui->chooseHomeTabComboBox->currentText()).toStdString(), + game_specific); - std::vector dirs_with_states; - for (int i = 0; i < ui->gameFoldersListWidget->count(); i++) { - QListWidgetItem* item = ui->gameFoldersListWidget->item(i); - QString path_string = item->text(); - auto path = Common::FS::PathFromQString(path_string); - bool enabled = (item->checkState() == Qt::Checked); + // Entries with no game-specific settings + if (!game_specific) { + std::vector dirs_with_states; + for (int i = 0; i < ui->gameFoldersListWidget->count(); i++) { + QListWidgetItem* item = ui->gameFoldersListWidget->item(i); + QString path_string = item->text(); + auto path = Common::FS::PathFromQString(path_string); + bool enabled = (item->checkState() == Qt::Checked); - dirs_with_states.push_back({path, enabled}); - } - Config::setAllGameInstallDirs(dirs_with_states); + dirs_with_states.push_back({path, enabled}); + } + Config::setAllGameInstallDirs(dirs_with_states); + + BackgroundMusicPlayer::getInstance().setVolume(ui->BGMVolumeSlider->value()); #ifdef ENABLE_DISCORD_RPC - auto* rpc = Common::Singleton::Instance(); - if (Config::getEnableDiscordRPC()) { - rpc->init(); - rpc->setStatusIdling(); - } else { - rpc->shutdown(); - } + auto* rpc = Common::Singleton::Instance(); + if (Config::getEnableDiscordRPC()) { + rpc->init(); + rpc->setStatusIdling(); + } else { + rpc->shutdown(); + } #endif - BackgroundMusicPlayer::getInstance().setVolume(ui->BGMVolumeSlider->value()); - Config::setVolumeSlider(ui->horizontalVolumeSlider->value()); + Config::setLoadGameSizeEnabled(ui->gameSizeCheckBox->isChecked()); + Config::setTrophyKey(ui->trophyKeyLineEdit->text().toStdString()); + Config::setEnableDiscordRPC(ui->discordRPCCheckbox->isChecked()); + Config::setCompatibilityEnabled(ui->enableCompatibilityCheckBox->isChecked()); + Config::setCheckCompatibilityOnStartup( + ui->checkCompatibilityOnStartupCheckBox->isChecked()); + m_gui_settings->SetValue(gui::gl_playBackgroundMusic, ui->playBGMCheckBox->isChecked()); + m_gui_settings->SetValue(gui::gl_backgroundMusicVolume, ui->BGMVolumeSlider->value()); + m_gui_settings->SetValue(gui::gen_checkForUpdates, ui->updateCheckBox->isChecked()); + m_gui_settings->SetValue(gui::gen_showChangeLog, ui->changelogCheckBox->isChecked()); + m_gui_settings->SetValue(gui::gen_updateChannel, + channelMap.value(ui->updateComboBox->currentText())); + m_gui_settings->SetValue(gui::gl_showBackgroundImage, + ui->showBackgroundImageCheckBox->isChecked()); + m_gui_settings->SetValue(gui::gl_backgroundImageOpacity, + std::clamp(ui->backgroundImageOpacitySlider->value(), 0, 100)); + emit BackgroundOpacityChanged(ui->backgroundImageOpacitySlider->value()); + } } void SettingsDialog::SyncRealTimeWidgetstoConfig() { - ui->gameFoldersListWidget->clear(); - std::filesystem::path userdir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); const toml::value data = toml::parse(userdir / "config.toml"); - if (data.contains("GUI")) { - const toml::value& gui = data.at("GUI"); - const auto install_dir_array = - toml::find_or>(gui, "installDirs", {}); + if (!game_specific) { + ui->gameFoldersListWidget->clear(); + if (data.contains("GUI")) { + const toml::value& gui = data.at("GUI"); + const auto install_dir_array = + toml::find_or>(gui, "installDirs", {}); - std::vector install_dirs_enabled; - try { - install_dirs_enabled = Config::getGameInstallDirsEnabled(); - } catch (...) { - // If it does not exist, assume that all are enabled. - install_dirs_enabled.resize(install_dir_array.size(), true); + std::vector install_dirs_enabled; + try { + install_dirs_enabled = Config::getGameInstallDirsEnabled(); + } 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); + } + + std::vector settings_install_dirs_config; + + for (size_t i = 0; i < install_dir_array.size(); i++) { + std::filesystem::path dir = install_dir_array[i]; + bool enabled = install_dirs_enabled[i]; + + settings_install_dirs_config.push_back({dir, enabled}); + + QString path_string; + Common::FS::PathToQString(path_string, dir); + + QListWidgetItem* item = new QListWidgetItem(path_string); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(enabled ? Qt::Checked : Qt::Unchecked); + ui->gameFoldersListWidget->addItem(item); + } + + Config::setAllGameInstallDirs(settings_install_dirs_config); } - - if (install_dirs_enabled.size() < install_dir_array.size()) { - install_dirs_enabled.resize(install_dir_array.size(), true); - } - - std::vector settings_install_dirs_config; - - for (size_t i = 0; i < install_dir_array.size(); i++) { - std::filesystem::path dir = install_dir_array[i]; - bool enabled = install_dirs_enabled[i]; - - settings_install_dirs_config.push_back({dir, enabled}); - - QString path_string; - Common::FS::PathToQString(path_string, dir); - - QListWidgetItem* item = new QListWidgetItem(path_string); - item->setFlags(item->flags() | Qt::ItemIsUserCheckable); - item->setCheckState(enabled ? Qt::Checked : Qt::Unchecked); - ui->gameFoldersListWidget->addItem(item); - } - - Config::setAllGameInstallDirs(settings_install_dirs_config); } + toml::value gs_data; + game_specific + ? gs_data = toml::parse(Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / + (gs_serial + ".toml")) + : gs_data = data; + + int sliderValue = toml::find_or(gs_data, "General", "volumeSlider", 100); + ui->horizontalVolumeSlider->setValue(sliderValue); + + // Since config::set can be called for volume slider (connected to the widget) outside the save + // function, need to null it out if GS GUI is closed without saving + game_specific ? Config::resetGameSpecificValue("volumeSlider") + : Config::setVolumeSlider(sliderValue); + if (presenter) { - presenter->GetFsrSettingsRef().enable = Config::getFsrEnabled(); - presenter->GetFsrSettingsRef().use_rcas = Config::getRcasEnabled(); + presenter->GetFsrSettingsRef().enable = + toml::find_or(gs_data, "GPU", "fsrEnabled", true); + presenter->GetFsrSettingsRef().use_rcas = + toml::find_or(gs_data, "GPU", "rcasEnabled", true); presenter->GetFsrSettingsRef().rcas_attenuation = - static_cast(Config::getRcasAttenuation() / 1000.f); + static_cast(toml::find_or(gs_data, "GPU", "rcasAttenuation", 250) / 1000.f); } } + void SettingsDialog::setDefaultValues() { - m_gui_settings->SetValue(gui::gl_showBackgroundImage, true); - m_gui_settings->SetValue(gui::gl_backgroundImageOpacity, 50); - m_gui_settings->SetValue(gui::gl_playBackgroundMusic, false); - m_gui_settings->SetValue(gui::gl_backgroundMusicVolume, 50); - m_gui_settings->SetValue(gui::gl_VolumeSlider, 100); - m_gui_settings->SetValue(gui::gen_checkForUpdates, false); - m_gui_settings->SetValue(gui::gen_showChangeLog, false); - if (Common::g_is_release) { - m_gui_settings->SetValue(gui::gen_updateChannel, "Release"); - } else { - m_gui_settings->SetValue(gui::gen_updateChannel, "Nightly"); + if (!game_specific) { + m_gui_settings->SetValue(gui::gl_showBackgroundImage, true); + m_gui_settings->SetValue(gui::gl_backgroundImageOpacity, 50); + m_gui_settings->SetValue(gui::gl_playBackgroundMusic, false); + m_gui_settings->SetValue(gui::gl_backgroundMusicVolume, 50); + m_gui_settings->SetValue(gui::gen_checkForUpdates, false); + m_gui_settings->SetValue(gui::gen_showChangeLog, false); + if (Common::g_is_release) { + m_gui_settings->SetValue(gui::gen_updateChannel, "Release"); + } else { + m_gui_settings->SetValue(gui::gen_updateChannel, "Nightly"); + } + m_gui_settings->SetValue(gui::gen_guiLanguage, "en_US"); } - m_gui_settings->SetValue(gui::gen_guiLanguage, "en_US"); } diff --git a/src/qt_gui/settings_dialog.h b/src/qt_gui/settings_dialog.h index 38c4a7688..ace401258 100644 --- a/src/qt_gui/settings_dialog.h +++ b/src/qt_gui/settings_dialog.h @@ -23,7 +23,8 @@ class SettingsDialog : public QDialog { public: explicit SettingsDialog(std::shared_ptr gui_settings, std::shared_ptr m_compat_info, - QWidget* parent = nullptr); + QWidget* parent = nullptr, bool is_game_specific = false, + std::string gsc_serial = ""); ~SettingsDialog(); bool eventFilter(QObject* obj, QEvent* event) override; @@ -38,7 +39,7 @@ signals: private: void LoadValuesFromConfig(); - void UpdateSettings(); + void UpdateSettings(bool game_specific = false); void SyncRealTimeWidgetstoConfig(); void InitializeEmulatorLanguages(); void OnLanguageChanged(int index); @@ -55,5 +56,8 @@ private: int initialHeight; bool is_saving = false; + bool game_specific; + std::string gs_serial; + std::shared_ptr m_gui_settings; }; diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index c85230eee..64914597b 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -12,7 +12,7 @@ 0 0 970 - 750 + 765 @@ -59,7 +59,7 @@ - 6 + 7 @@ -74,7 +74,7 @@ 0 0 946 - 486 + 501 @@ -452,8 +452,8 @@ 0 0 - 400 - 60 + 402 + 68 @@ -539,7 +539,7 @@ 0 0 946 - 486 + 501 @@ -999,7 +999,7 @@ 0 0 946 - 486 + 501 @@ -1406,7 +1406,7 @@ 0 0 946 - 486 + 501 @@ -1648,7 +1648,7 @@ 0 0 946 - 486 + 501 @@ -1920,7 +1920,7 @@ 0 0 946 - 486 + 501 @@ -2235,7 +2235,7 @@ 0 0 946 - 382 + 495 @@ -2244,17 +2244,11 @@ 0 - + - - - 0 - - - 0 - + - + @@ -2318,10 +2312,6 @@ - - - - @@ -2389,94 +2379,136 @@ - - - - - - 0 - - + Experimental Features + + 6 + + + 9 + + + 9 + + + 9 + + + 9 + - - - 0 + + + Enable Readbacks - - - - 0 - - - - - Enable Readbacks - - - - - - - Enable Readback Linear Images - - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 11 - true - - - - WARNING: These features are experimental and should not be enabled unless you were told to, or a game requires it. Please ask in the shadPS4 Discord server if you have any questions. - - - false - - - Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop - - - true - - - 0 - - - -1 - - - - + + + + + + Enable Readback Linear Images + + + + + + + Enable Direct Memory Access + + + + + + + Enable Devkit Console Mode + + + + + + + Enable PS4 Pro Mode + + + + + + + Set "PSN signed-in" to True + + + + + + + Set "Network Connected" to True + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 11 + true + + + + WARNING: These features are experimental and should not be enabled unless you were told to, or a game requires it. Please ask in the shadPS4 Discord server if you have any questions. + + + false + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + true + + + 20 + + + -1 + + + + + + + Qt::Orientation::Vertical + + + + 20 + 5 + + + + diff --git a/src/shadps4.qrc b/src/shadps4.qrc index 2fd9c786a..d2cb14750 100644 --- a/src/shadps4.qrc +++ b/src/shadps4.qrc @@ -38,5 +38,6 @@ images/favorite_icon.png images/trophy_icon.png images/hotkey.png + images/game_settings.png