diff --git a/core/libs/prx/libSceSaveData.native/Export.cpp b/core/libs/prx/libSceSaveData.native/Export.cpp index 59b91db41..e8e2635d9 100644 --- a/core/libs/prx/libSceSaveData.native/Export.cpp +++ b/core/libs/prx/libSceSaveData.native/Export.cpp @@ -18,6 +18,7 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/AppMetadata/include/AppMetadata.hpp" #include "SaveData.hpp" #include "prx/libSceSaveData/SaveDataFile.hpp" @@ -278,6 +279,9 @@ static int dirNameSearch(const SaveDataDirNameSearchCond* cond, SaveDataDirNameS if ((key > SAVE_DATA_SORT_KEY_MTIME && key != SAVE_DATA_SORT_KEY_FREE_BLOCKS) || cond->order > SAVE_DATA_SORT_ORDER_DESCENT) { return SAVE_DATA_ERROR_PARAMETER; } + if (cond->title_id != nullptr && std::strncmp(cond->title_id->data, GetAppTitleId_nid_postfix().value, sizeof(cond->title_id->data)) != 0) { + return SAVE_DATA_OK; + } const char* pattern = (cond->dir_name != nullptr) ? cond->dir_name->data : nullptr; const std::string root = save_root(); if (!std::filesystem::is_directory(root)) { diff --git a/core/libs/tests/GuestSaveDataSearch.cpp b/core/libs/tests/GuestSaveDataSearch.cpp index 53d0cc506..fb8e61c70 100644 --- a/core/libs/tests/GuestSaveDataSearch.cpp +++ b/core/libs/tests/GuestSaveDataSearch.cpp @@ -1,6 +1,7 @@ #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include #include #include #include @@ -123,6 +124,42 @@ static void TrailingWildcards() { Require(SearchPattern("TRAIL_%") == Names{"TRAIL1"}, "_ must still match exactly one character"); } +static std::uint32_t SearchTitle(const char* titleId, bool second) { + SceSaveDataTitleId title{}; + std::snprintf(title.data, sizeof(title.data), "%s", titleId != nullptr ? titleId : ""); + SceSaveDataDirName pattern{}; + std::snprintf(pattern.data, sizeof(pattern.data), "OTHER_%%"); + SaveDataDirNameSearchCond cond{}; + cond.user_id = 1; + cond.title_id = titleId != nullptr ? &title : nullptr; + cond.dir_name = &pattern; + std::array names{}; + names[0].data[0] = '!'; + SaveDataDirNameSearchResult result{}; + result.dir_names = names.data(); + result.dir_names_num = 1; + result.hit_num = 7; + result.set_num = 7; + Require((second ? sceSaveDataDirNameSearch2(&cond, &result) : sceSaveDataDirNameSearch(&cond, &result)) == 0, "title search failed"); + Require(result.set_num == std::min(result.hit_num, 1), "title search must set the names it found"); + Require(result.set_num == 1 ? std::string(names[0].data) == "OTHER_A" : names[0].data[0] == '!', "title search returned the wrong name"); + return result.hit_num; +} + +static void OtherTitle() { + std::filesystem::create_directories("app0/sce_sys"); + std::FILE* param = std::fopen("app0/sce_sys/param.json", "wb"); + Require(param != nullptr, "param.json was not created"); + std::fputs("{\"titleId\":\"PPSA00000\",\"localizedParameters\":{\"en-US\":{\"titleName\":\"Example\"}}}", param); + std::fclose(param); + std::filesystem::create_directories(std::filesystem::path("_sd") / "OTHER_A"); + for (const bool second : {false, true}) { + Require(SearchTitle(nullptr, second) == 1, "a search without a title id must find the running title's save"); + Require(SearchTitle("PPSA00000", second) == 1, "a search for the running title must find its save"); + Require(SearchTitle("PPSA17221", second) == 0, "a search for another title must find none of the running title's saves"); + } +} + static void SearchAndMount() { const std::array names{std::string(31, 'A'), std::string(31, 'B')}; for (const auto& name : names) std::filesystem::create_directories(std::filesystem::path("_sd") / name); @@ -181,6 +218,7 @@ int main() { try { SearchAndMount(); SortByKey(); + OtherTitle(); TrailingWildcards(); } catch (const std::exception& error) { std::fprintf(stderr, "%s\n", error.what()); diff --git a/docs/dev/debt/libSceSaveData.native.md b/docs/dev/debt/libSceSaveData.native.md index c6a5c1397..4b928d083 100644 --- a/docs/dev/debt/libSceSaveData.native.md +++ b/docs/dev/debt/libSceSaveData.native.md @@ -12,6 +12,7 @@ ### Functional +- [sceSaveDataDirNameSearch, sceSaveDataDirNameSearch2](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - only the running title's saves are kept, so a search with another title's `title_id` reports zero hits, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/ec334904604ba41f5d4753f4a2498ec82540e5d6/src/core/libraries/save_data/savedata.cpp#L791-L802) and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/569ec04d1a77127c601eed522ad5bba187173f9d/src/libs/libSaveData.cpp#L592-L596) do when that title has no save directory; what a console returns for another title's saves is not verified. The running title's ID is the `titleId` in its `param.json` - [sceSaveDataDirNameSearch2](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) runs the same search as `sceSaveDataDirNameSearch` with the same condition and result structures, as [SharpEmu](https://github.com/sharpemu/sharpemu/blob/main/src/SharpEmu.Libs/SaveData/SaveDataExports.cs) does, its only source; what distinguishes it on the console is unknown. - [sceSaveDataMount3](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - a failed mount (BUSY, EXISTS, NOT_FOUND, MOUNT_FULL) leaves the result as it was, as shadPS4 does; not checked on hardware. Animal Well (PPSA22520) depends on it: it checks for its save with a mount it never unmounts, then saves with a second mount of the same directory, ignores its BUSY and keeps using the first mount point for `sceSaveDataPrepare`, its file and `sceSaveDataUmount2`