Merge pull request #5133 from SandstromPL/fix/savedata-search-other-title

fix(savedata): return no saves for a search with another title's ID
This commit is contained in:
Dean 2026-10-14 15:41:58 +13:00 committed by GitHub
commit a21d73733c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 43 additions and 0 deletions

View File

@ -18,6 +18,7 @@
#include <string>
#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)) {

View File

@ -1,6 +1,7 @@
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <algorithm>
#include <array>
#include <cstdio>
#include <cstring>
@ -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<SceSaveDataDirName, 1> 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<std::uint32_t>(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());

View File

@ -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`