Merge pull request #3771 from yunusshaikh7/fix/savedata-dir-name-search-sort
fix(savedata): sort sceSaveDataDirNameSearch results by the requested key and order
This commit is contained in:
commit
dd1cf5cb9d
|
|
@ -270,13 +270,22 @@ static int dirNameSearch(const SaveDataDirNameSearchCond* cond, SaveDataDirNameS
|
|||
}
|
||||
result->hit_num = 0;
|
||||
result->set_num = 0;
|
||||
const std::uint32_t key = cond->key;
|
||||
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;
|
||||
}
|
||||
const char* pattern = (cond->dir_name != nullptr) ? cond->dir_name->data : nullptr;
|
||||
const std::string root = save_root();
|
||||
if (!std::filesystem::is_directory(root)) {
|
||||
return SAVE_DATA_OK;
|
||||
}
|
||||
std::uint32_t hit = 0;
|
||||
std::uint32_t set = 0;
|
||||
struct Match {
|
||||
std::string name;
|
||||
std::string path;
|
||||
SaveDataParam param;
|
||||
};
|
||||
const bool sortByParam = key == SAVE_DATA_SORT_KEY_USER_PARAM || key == SAVE_DATA_SORT_KEY_MTIME;
|
||||
std::vector<Match> matches;
|
||||
for (const auto& entry : std::filesystem::directory_iterator(root)) {
|
||||
if (!entry.is_directory()) {
|
||||
continue;
|
||||
|
|
@ -285,16 +294,29 @@ static int dirNameSearch(const SaveDataDirNameSearchCond* cond, SaveDataDirNameS
|
|||
if (!dir_name_match(name.c_str(), pattern)) {
|
||||
continue;
|
||||
}
|
||||
hit++;
|
||||
if (result->dir_names != nullptr && set < result->dir_names_num) {
|
||||
std::snprintf(result->dir_names[set].data, sizeof(result->dir_names[set].data), "%s", name.c_str());
|
||||
if (result->params != nullptr) {
|
||||
result->params[set] = load_param(entry.path().string());
|
||||
}
|
||||
set++;
|
||||
Match match{name, entry.path().string(), {}};
|
||||
if (sortByParam) {
|
||||
match.param = load_param(match.path);
|
||||
}
|
||||
matches.push_back(std::move(match));
|
||||
}
|
||||
std::sort(matches.begin(), matches.end(), [](const Match& a, const Match& b) { return a.name < b.name; });
|
||||
if (key == SAVE_DATA_SORT_KEY_USER_PARAM) {
|
||||
std::stable_sort(matches.begin(), matches.end(), [](const Match& a, const Match& b) { return a.param.user_param < b.param.user_param; });
|
||||
} else if (key == SAVE_DATA_SORT_KEY_MTIME) {
|
||||
std::stable_sort(matches.begin(), matches.end(), [](const Match& a, const Match& b) { return a.param.mtime < b.param.mtime; });
|
||||
}
|
||||
if (cond->order == SAVE_DATA_SORT_ORDER_DESCENT) {
|
||||
std::reverse(matches.begin(), matches.end());
|
||||
}
|
||||
const std::uint32_t set = result->dir_names == nullptr ? 0 : static_cast<std::uint32_t>(std::min<std::size_t>(result->dir_names_num, matches.size()));
|
||||
for (std::uint32_t i = 0; i < set; i++) {
|
||||
std::snprintf(result->dir_names[i].data, sizeof(result->dir_names[i].data), "%s", matches[i].name.c_str());
|
||||
if (result->params != nullptr) {
|
||||
result->params[i] = sortByParam ? matches[i].param : load_param(matches[i].path);
|
||||
}
|
||||
}
|
||||
result->hit_num = hit;
|
||||
result->hit_num = static_cast<std::uint32_t>(matches.size());
|
||||
result->set_num = set;
|
||||
return SAVE_DATA_OK;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,14 @@ constexpr std::uint32_t SAVE_DATA_PARAM_TYPE_DETAIL = 3;
|
|||
constexpr std::uint32_t SAVE_DATA_PARAM_TYPE_USER_PARAM = 4;
|
||||
constexpr std::uint32_t SAVE_DATA_PARAM_TYPE_MTIME = 5;
|
||||
|
||||
constexpr std::uint32_t SAVE_DATA_SORT_KEY_DIRNAME = 0;
|
||||
constexpr std::uint32_t SAVE_DATA_SORT_KEY_USER_PARAM = 1;
|
||||
constexpr std::uint32_t SAVE_DATA_SORT_KEY_BLOCKS = 2;
|
||||
constexpr std::uint32_t SAVE_DATA_SORT_KEY_MTIME = 3;
|
||||
constexpr std::uint32_t SAVE_DATA_SORT_KEY_FREE_BLOCKS = 5;
|
||||
constexpr std::uint32_t SAVE_DATA_SORT_ORDER_ASCENT = 0;
|
||||
constexpr std::uint32_t SAVE_DATA_SORT_ORDER_DESCENT = 1;
|
||||
|
||||
constexpr std::size_t SAVE_DATA_MOUNT_SLOTS = 16;
|
||||
|
||||
struct MountSlot {
|
||||
|
|
|
|||
|
|
@ -8,17 +8,79 @@
|
|||
#include <random>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI sceSaveDataDirNameSearch(const SaveDataDirNameSearchCond*, SaveDataDirNameSearchResult*);
|
||||
int APS5_VABI sceSaveDataMount3(const SaveDataMount3*, SaveDataMountResult*);
|
||||
int APS5_VABI sceSaveDataUmount2(std::uint32_t, const SaveDataMountPoint*);
|
||||
int APS5_VABI sceSaveDataSetParam(const SaveDataMountPoint*, std::uint32_t, const void*, std::size_t);
|
||||
}
|
||||
|
||||
static constexpr int SaveDataErrorParameter = -2137063424;
|
||||
|
||||
static void Require(bool value, const char* message) {
|
||||
if (!value) throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
static std::vector<std::string> Search(std::uint32_t key, std::uint32_t order, std::uint32_t capacity, std::uint32_t* hits) {
|
||||
SceSaveDataDirName pattern{};
|
||||
std::snprintf(pattern.data, sizeof(pattern.data), "SORT_%%");
|
||||
SaveDataDirNameSearchCond cond{};
|
||||
cond.user_id = 1;
|
||||
cond.dir_name = &pattern;
|
||||
cond.key = key;
|
||||
cond.order = order;
|
||||
std::array<SceSaveDataDirName, 3> names{};
|
||||
SaveDataDirNameSearchResult result{};
|
||||
result.dir_names = names.data();
|
||||
result.dir_names_num = capacity;
|
||||
Require(sceSaveDataDirNameSearch(&cond, &result) == 0, "sorted search failed");
|
||||
*hits = result.hit_num;
|
||||
std::vector<std::string> found;
|
||||
for (std::uint32_t i = 0; i < result.set_num; ++i) found.emplace_back(names[i].data);
|
||||
return found;
|
||||
}
|
||||
|
||||
static void SortByKey() {
|
||||
struct Save {
|
||||
const char* name;
|
||||
std::uint32_t userParam;
|
||||
std::int64_t mtime;
|
||||
};
|
||||
const std::array<Save, 3> saves{{{"SORT_B", 1, 1000}, {"SORT_A", 3, 2000}, {"SORT_C", 2, 3000}}};
|
||||
for (const auto& save : saves) {
|
||||
std::filesystem::create_directories(std::filesystem::path("_sd") / save.name);
|
||||
SceSaveDataDirName dirName{};
|
||||
std::snprintf(dirName.data, sizeof(dirName.data), "%s", save.name);
|
||||
SaveDataMount3 mount{};
|
||||
mount.user_id = 1;
|
||||
mount.dir_name = &dirName;
|
||||
mount.mount_mode = 2;
|
||||
SaveDataMountResult mounted{};
|
||||
Require(sceSaveDataMount3(&mount, &mounted) == 0, "mount for parameters failed");
|
||||
Require(sceSaveDataSetParam(&mounted.mount_point, 4, &save.userParam, sizeof(save.userParam)) == 0, "user parameter was not set");
|
||||
Require(sceSaveDataSetParam(&mounted.mount_point, 5, &save.mtime, sizeof(save.mtime)) == 0, "mtime was not set");
|
||||
Require(sceSaveDataUmount2(0, &mounted.mount_point) == 0, "unmount failed");
|
||||
}
|
||||
using Names = std::vector<std::string>;
|
||||
std::uint32_t hits = 0;
|
||||
Require(Search(0, 0, 3, &hits) == Names{"SORT_A", "SORT_B", "SORT_C"} && hits == 3, "directory names must sort ascending");
|
||||
Require(Search(0, 1, 3, &hits) == Names{"SORT_C", "SORT_B", "SORT_A"}, "directory names must sort descending");
|
||||
Require(Search(1, 0, 3, &hits) == Names{"SORT_B", "SORT_C", "SORT_A"}, "user parameters must sort ascending");
|
||||
Require(Search(1, 1, 3, &hits) == Names{"SORT_A", "SORT_C", "SORT_B"}, "user parameters must sort descending");
|
||||
Require(Search(3, 0, 3, &hits) == Names{"SORT_B", "SORT_A", "SORT_C"}, "modification times must sort ascending");
|
||||
Require(Search(3, 1, 1, &hits) == Names{"SORT_C"} && hits == 3, "the newest save must come first in descending modification time");
|
||||
SaveDataDirNameSearchCond cond{};
|
||||
cond.user_id = 1;
|
||||
cond.key = 6;
|
||||
SaveDataDirNameSearchResult result{};
|
||||
Require(sceSaveDataDirNameSearch(&cond, &result) == SaveDataErrorParameter, "an unknown sort key must be rejected");
|
||||
cond.key = 0;
|
||||
cond.order = 2;
|
||||
Require(sceSaveDataDirNameSearch(&cond, &result) == SaveDataErrorParameter, "an unknown sort order must be rejected");
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
@ -64,6 +126,7 @@ int main() {
|
|||
int status = 0;
|
||||
try {
|
||||
SearchAndMount();
|
||||
SortByKey();
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "%s\n", error.what());
|
||||
status = 1;
|
||||
|
|
|
|||
|
|
@ -302,6 +302,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [sceNgs2VoiceQueryInfo](../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - only info id 0x4001 is known: PPSA22520 passes an 8-byte buffer and branches on the first word being 1 or 2, so it is assumed to be the voice channel count (0 before the voice is set up); the second word is zeroed and other ids throw
|
||||
- [sceNgs2VoiceGetPortInfo](../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - layout from shadPS4; the delay and destination input are zero because ports have neither yet
|
||||
- [sceNgs2ParseWaveformFile](../../core/libs/prx/libSceNgs2.native/src/Atrac9.cpp) (libSceNgs2.native) - the signature `(path, offset, info)` is taken from the call site of PPSA21402, which sets only three argument registers; the file is read from `offset` to its end, the data and block offsets are taken to be relative to the start of the file, not of `offset`, and an unreadable file throws. The audio unit fields of a 16-bit PCM waveform are assumed to be one sample frame per unit and per frame; `INVALID_WAVEFORM_DATA` for a `data` chunk that runs past the buffer or ends inside a frame is assumed, and looped PCM and other PCM formats throw
|
||||
- [sceSaveDataDirNameSearch](../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - the sort keys (0 directory name, 1 user parameter, 2 blocks, 3 modification time, 5 free blocks) and orders (0 ascending, 1 descending) follow shadPS4, not a hardware run; saves with equal keys keep directory-name order before a descending reverse. Every native save reports `SAVE_DATA_BLOCKS_MAX` blocks, as `sceSaveDataGetMountInfo` does, so the two block keys keep directory-name order. Keys other than 0, 1, 2, 3 and 5 (including 4) and orders above 1 return `SCE_SAVE_DATA_ERROR_PARAMETER`; shadPS4 and KytyPS5 only reject keys above 5, so key 4 is rejected here because no source names it
|
||||
- [sceSaveDataInitialize3](../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - a repeated initialize succeeds: PPSA12544's executable and its Unity SaveData plugin both initialize, and the plugin fails on any error. Initializations are counted and `sceSaveDataTerminate` ends the session at the last one; how the console pairs them is unknown
|
||||
- [sceTextToSpeech2GetSystemStatus](../../core/libs/prx/libSceTextToSpeech2/Export.cpp) (libSceTextToSpeech2) - unknown signature; returns `EOPNOTSUPP` like `sceTextToSpeech2Initialize`
|
||||
- [sceCoredumpAttachUserMemoryFile](../../core/libs/prx/libkernel/System/src/Coredump.cpp) (libkernel) - unknown signature
|
||||
|
|
|
|||
Loading…
Reference in New Issue