From 90eeee345ca838d056183b42d8d74b0acf0a8715 Mon Sep 17 00:00:00 2001 From: Zephyron Date: Fri, 28 Nov 2025 16:11:07 +1000 Subject: [PATCH] feat(filesystem): add custom save path support per game - Add custom_save_paths map to Settings::Values - Implement ReadCustomSavePathValues and SaveCustomSavePathValues in Config - Update CreateSaveDataFactory to check for custom save paths - Support per-game title ID save path overrides Signed-off-by: Zephyron --- src/common/settings.h | 3 ++ .../hle/service/filesystem/filesystem.cpp | 17 ++++++++- src/frontend_common/config.cpp | 37 +++++++++++++++++++ src/frontend_common/config.h | 3 ++ 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/common/settings.h b/src/common/settings.h index 8c769c5ff..567ee6b3e 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -683,6 +683,9 @@ struct Values { // Cheats // Key: build_id (hex string), Value: set of disabled cheat names std::map> disabled_cheats; + + // Custom Save Paths + std::map custom_save_paths; }; extern Values values; diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 2ed584393..1a3637fda 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-FileCopyrightText: 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -419,8 +420,22 @@ std::shared_ptr FileSystemController::CreateSaveDataFa ProgramId program_id) { using CitronPath = Common::FS::CitronPath; const auto rw_mode = FileSys::OpenMode::ReadWrite; - auto vfs = system.GetFilesystem(); + + // Check for a custom save path for the current game. + if (Settings::values.custom_save_paths.count(program_id)) { + const std::string& custom_path_str = Settings::values.custom_save_paths.at(program_id); + const std::filesystem::path custom_path = custom_path_str; + + // If the custom path is valid and points to a directory, use it. + if (!custom_path_str.empty() && Common::FS::IsDir(custom_path)) { + LOG_INFO(Service_FS, "Using custom save path for program_id={:016X}: {}", program_id, custom_path_str); + auto custom_save_directory = vfs->OpenDirectory(custom_path_str, rw_mode); + return std::make_shared(system, program_id, std::move(custom_save_directory)); + } + } + + // If no valid custom path was found, use the default NAND directory. const auto nand_directory = vfs->OpenDirectory(Common::FS::GetCitronPathString(CitronPath::NANDDir), rw_mode); return std::make_shared(system, program_id, diff --git a/src/frontend_common/config.cpp b/src/frontend_common/config.cpp index 71768eecb..6148d3310 100644 --- a/src/frontend_common/config.cpp +++ b/src/frontend_common/config.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-FileCopyrightText: 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -299,6 +300,24 @@ void Config::ReadDebuggingValues() { EndGroup(); } +void Config::ReadCustomSavePathValues() { + BeginGroup(std::string("CustomSavePaths")); + + const int size = BeginArray(std::string("")); + for (int i = 0; i < size; ++i) { + SetArrayIndex(i); + const auto title_id = ReadUnsignedIntegerSetting(std::string("title_id"), 0); + const auto path = ReadStringSetting(std::string("path"), std::string("")); + + if (title_id != 0 && !path.empty()) { + Settings::values.custom_save_paths.insert_or_assign(title_id, path); + } + } + EndArray(); + + EndGroup(); +} + #ifdef __unix__ void Config::ReadLinuxValues() { BeginGroup(Settings::TranslateCategory(Settings::Category::Linux)); @@ -438,6 +457,7 @@ void Config::ReadValues() { if (global) { ReadDataStorageValues(); ReadDebuggingValues(); + ReadCustomSavePathValues(); ReadDisabledAddOnValues(); ReadDisabledCheatValues(); ReadNetworkValues(); @@ -542,6 +562,7 @@ void Config::SaveValues() { LOG_DEBUG(Config, "Saving global generic configuration values"); SaveDataStorageValues(); SaveDebuggingValues(); + SaveCustomSavePathValues(); SaveDisabledAddOnValues(); SaveDisabledCheatValues(); SaveNetworkValues(); @@ -631,6 +652,22 @@ void Config::SaveDebuggingValues() { EndGroup(); } +void Config::SaveCustomSavePathValues() { + BeginGroup(std::string("CustomSavePaths")); + + int i = 0; + BeginArray(std::string("")); + for (const auto& elem : Settings::values.custom_save_paths) { + SetArrayIndex(i); + WriteIntegerSetting(std::string("title_id"), elem.first, std::make_optional(static_cast(0))); + WriteStringSetting(std::string("path"), elem.second, std::make_optional(std::string(""))); + ++i; + } + EndArray(); + + EndGroup(); +} + #ifdef __unix__ void Config::SaveLinuxValues() { BeginGroup(Settings::TranslateCategory(Settings::Category::Linux)); diff --git a/src/frontend_common/config.h b/src/frontend_common/config.h index 5315102f7..35c3273e2 100644 --- a/src/frontend_common/config.h +++ b/src/frontend_common/config.h @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-FileCopyrightText: 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -76,6 +77,7 @@ protected: void ReadCoreValues(); void ReadDataStorageValues(); void ReadDebuggingValues(); + void ReadCustomSavePathValues(); #ifdef __unix__ void ReadLinuxValues(); #endif @@ -112,6 +114,7 @@ protected: void SaveCoreValues(); void SaveDataStorageValues(); void SaveDebuggingValues(); + void SaveCustomSavePathValues(); #ifdef __unix__ void SaveLinuxValues(); #endif