Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/libs/prx/libSceSaveData.native/Export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ int APS5_VABI sceSaveDataDeleteTransactionResource(int32_t resource) {
return SAVE_DATA_OK;
}

static constexpr std::uint64_t SAVE_DATA_BLOCK_BYTES = 32768;
static constexpr std::uint64_t SAVE_DATA_BLOCK_BYTES = 65536;

static std::uint64_t save_blocks(const std::string& real_path) {
std::ifstream file(blocks_path(real_path));
Expand Down
2 changes: 1 addition & 1 deletion core/libs/prx/libSceSaveData.native/SaveData.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ constexpr std::uint32_t SAVE_DATA_MOUNT_MODE_RDWR = 2;
constexpr std::uint32_t SAVE_DATA_MOUNT_MODE_CREATE = 4;
constexpr std::uint32_t SAVE_DATA_MOUNT_MODE_CREATE2 = 32;

constexpr std::uint64_t SAVE_DATA_BLOCKS_MAX = 32768;
constexpr std::uint64_t SAVE_DATA_BLOCKS_MAX = 16384;
constexpr std::uint32_t SAVE_DATA_PARAM_TYPE_ALL = 0;
constexpr std::uint32_t SAVE_DATA_PARAM_TYPE_TITLE = 1;
constexpr std::uint32_t SAVE_DATA_PARAM_TYPE_SUB_TITLE = 2;
Expand Down
43 changes: 29 additions & 14 deletions core/libs/tests/GuestSaveDataSearch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ static void SearchAndMount() {
for (std::size_t i = 0; i < 2; ++i) {
SaveDataSearchInfo info{};
std::memcpy(&info, infos.data() + i * 48, sizeof(info));
Require(info.blocks == 32768 && info.free_blocks == 32768, "search info must report the save's blocks and free blocks");
Require(info.blocks == 16384 && info.free_blocks == 16384, "search info must report the save's blocks and free blocks");
for (const auto byte : info.reserved) Require(byte == 0, "search info reserved bytes must be zero");
}
std::array<bool, 2> found{};
Expand Down Expand Up @@ -246,30 +246,44 @@ static std::vector<SaveDataSearchInfo> SizeSearch(std::uint32_t key, std::vector
return {infos.begin(), infos.end()};
}

static SaveDataMountInfo MountInfo(const char* name) {
SceSaveDataDirName dirName{};
std::snprintf(dirName.data, sizeof(dirName.data), "%s", name);
SaveDataMount3 mount{};
mount.user_id = 1;
mount.dir_name = &dirName;
mount.mount_mode = 1;
SaveDataMountResult mounted{};
Require(sceSaveDataMount3(&mount, &mounted) == 0, "mounting the sized save failed");
SaveDataMountInfo info{};
Require(sceSaveDataGetMountInfo(&mounted.mount_point, &info) == 0, "mount info failed");
Require(sceSaveDataUmount2(0, &mounted.mount_point) == 0, "unmount failed");
return info;
}

static void SaveSize() {
CreateSized("SIZE_A", 200, 0);
CreateSized("SIZE_B", 96, 40000);
std::vector<std::string> found;
auto infos = SizeSearch(0, found);
Require(found == std::vector<std::string>{"SIZE_A", "SIZE_B"}, "the sized saves must sort by name");
Require(infos[0].blocks == 200 && infos[0].free_blocks == 200, "an empty save must report all its blocks free");
Require(infos[1].blocks == 96 && infos[1].free_blocks == 94, "a save must report the blocks it was created with, less those its files take");
Require(infos[1].blocks == 96 && infos[1].free_blocks == 95, "a save must report the blocks it was created with, less those its files take");
SizeSearch(2, found);
Require(found == std::vector<std::string>{"SIZE_B", "SIZE_A"}, "saves must sort by blocks");
infos = SizeSearch(5, found);
Require(found == std::vector<std::string>{"SIZE_B", "SIZE_A"} && infos[0].free_blocks < infos[1].free_blocks, "saves must sort by free blocks");
SceSaveDataDirName dirName{};
std::snprintf(dirName.data, sizeof(dirName.data), "SIZE_B");
SaveDataMount3 mount{};
mount.user_id = 1;
mount.dir_name = &dirName;
mount.mount_mode = 1;
SaveDataMountResult mounted{};
Require(sceSaveDataMount3(&mount, &mounted) == 0, "mounting the sized save failed");
SaveDataMountInfo info{};
Require(sceSaveDataGetMountInfo(&mounted.mount_point, &info) == 0, "mount info failed");
Require(info.blocks == 96 && info.free_blocks == 94, "mount info must report the save's blocks and free blocks");
Require(sceSaveDataUmount2(0, &mounted.mount_point) == 0, "unmount failed");
const auto info = MountInfo("SIZE_B");
Require(info.blocks == 96 && info.free_blocks == 95, "mount info must report the save's blocks and free blocks");
}

static void BlockSize() {
CreateSized("BLOCK_A", 48, 70000);
auto info = MountInfo("BLOCK_A");
Require(info.blocks == 48 && info.free_blocks == 46, "a file must take whole 64 KiB blocks");
CreateSized("BLOCK_B", 32768, 0);
info = MountInfo("BLOCK_B");
Require(info.blocks == 16384 && info.free_blocks == 16384, "a save must hold at most 16384 blocks");
}

int main() {
Expand All @@ -285,6 +299,7 @@ int main() {
OtherTitle();
TrailingWildcards();
SaveSize();
BlockSize();
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
status = 1;
Expand Down
3 changes: 2 additions & 1 deletion docs/dev/debt/libSceSaveData.native.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
- [sceSaveDataConvert](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - the parameter block (user id at `+0x00`, `SceSaveDataDirName` pointer at `+0x08`, 48 opaque bytes after it) is read from PPSA28416's single call, not confirmed by KytyPS5, shadPS4, fpPS4 or SharpProspero, which have no body for it. Host saves have no size limit, so an existing save returns `SCE_OK` without touching its files and no event is queued; what the console returns for a save that is mounted, already converted or needs no conversion is unknown. The directory name is validated as `sceSaveDataDelete` does
- [sceSaveDataGetConvertProgress](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - imported by PPSA16064; signature and error codes follow the [SDK 12.000 reference](https://github.com/peterpuppy/bm/blob/3f9d1ba6b896a53080460c903f5c15af8affb80d/features/ps5/document/output/psn_12/System/Save_Data/SaveData-Reference/sce-save-data-get-convert-progress.md) (`float* progress`). Save data conversion on host filesystems completes synchronously, so the progress query returns `SAVE_DATA_OK` with the last conversion's progress (`0.0f` before any conversion or when conversion fails, `1.0f` after successful conversion), `SAVE_DATA_ERROR_PARAMETER` if `progress` is null, or `SAVE_DATA_ERROR_NOT_INITIALIZED` when the library is uninitialized; intermediate asynchronous conversion progress on console is unmeasured
- [sceSaveDataCancel](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - imported by PPSA23126, which fails to load without it; added without an implementation because its signature is unknown. Every save data call here finishes before it returns, so nothing is ever in flight to cancel, but what the console returns then is unknown
- [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. A save's size is the `blocks` of the `sceSaveDataMount3` call that created it, kept beside the save in `<dir>.blocks`; a save without a kept size takes that of its next mount that names one, and otherwise reports `SAVE_DATA_BLOCKS_MAX`. The search infos (blocks, free blocks and 32 reserved bytes per returned name, following shadPS4's `OrbisSaveDataSearchInfo`), `sceSaveDataGetMountInfo` and the two block keys use that size and, as free, what the save's files leave of it, each file rounded up to 32 KiB blocks; the console's file system overhead is not known and not counted. 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. In the directory name pattern `%` matches any run of characters, none included, and `_` exactly one, as the comments of shadPS4's matcher describe; also not confirmed on hardware
- [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. The search infos (blocks, free blocks and 32 reserved bytes per returned name) follow shadPS4's `OrbisSaveDataSearchInfo`. 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. In the directory name pattern `%` matches any run of characters, none included, and `_` exactly one, as the comments of shadPS4's matcher describe; also not confirmed on hardware
- [sceSaveDataDirNameSearch, sceSaveDataGetMountInfo, sceSaveDataMount3](../../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - the 64 KiB block of [SharpEmu](https://github.com/sharpemu/sharpemu/blob/c6d304cc3328bad62cb671327ace181110cf2ddb/src/SharpEmu.Libs/SaveData/SaveDataExports.cs#L82-L84) and the 48 to 16384-block range of SharpEmu and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/569ec04d1a77127c601eed522ad5bba187173f9d/src/libs/libSaveData.cpp#L30-L31) are not measured on a console, and file system overhead is not counted. What the console does with a `blocks` outside that range is unknown: KytyPS5 rejects the creating mount and SharpEmu clamps it; here a larger one is kept as 16384 and a smaller non-zero one as given. A save's size is the `blocks` of the mount that created it, kept in `<dir>.blocks`, else that of its next mount that names one, else 16384; its free blocks are that size less its files, each rounded up to 64 KiB blocks
- [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

### Functional
Expand Down
Loading