mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-04 13:07:32 +00:00
295 lines
12 KiB
C++
295 lines
12 KiB
C++
#include "prx/libSceSaveData/include/Api.hpp"
|
|
#include "prx/libSceSaveData.native/include/SaveDataHostSpace.hpp"
|
|
#include "prx/libSceSaveData.native/include/Api.hpp"
|
|
#include "SceTypes.hpp"
|
|
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <random>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
constexpr std::uint32_t MountReadWrite = 2;
|
|
constexpr std::uint32_t MountCreate2 = 32;
|
|
constexpr int SaveDataErrorParameter = -2137063424;
|
|
constexpr int SaveDataErrorNoSpaceFs = -2137063414;
|
|
constexpr std::uint64_t BlockBytes = 65536;
|
|
|
|
int failures = 0;
|
|
|
|
std::uint64_t fakeAvailable = 0;
|
|
int fakeCalls = 0;
|
|
bool fakeFails = false;
|
|
|
|
std::uint64_t FakeFreeSpace(const std::string&) {
|
|
++fakeCalls;
|
|
if (fakeFails) throw std::runtime_error("fake volume query failed");
|
|
return fakeAvailable;
|
|
}
|
|
|
|
void Check(bool condition, const std::string& what) {
|
|
if (condition) return;
|
|
std::fprintf(stderr, "savedata host space check failed: %s\n", what.c_str());
|
|
++failures;
|
|
}
|
|
|
|
void SetFreeSpaceVariable(const char* value) {
|
|
#ifdef _WIN32
|
|
_putenv_s("ANYPS5_FREE_SPACE", value != nullptr ? value : "");
|
|
#else
|
|
if (value == nullptr) {
|
|
unsetenv("ANYPS5_FREE_SPACE");
|
|
} else {
|
|
setenv("ANYPS5_FREE_SPACE", value, 1);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int Mount(const char* name, std::uint32_t mode, std::uint64_t blocks, SaveDataMountResult* result) {
|
|
SceSaveDataDirName dirName{};
|
|
std::snprintf(dirName.data, sizeof(dirName.data), "%s", name);
|
|
SaveDataMount3 mount{};
|
|
mount.user_id = 1;
|
|
mount.dir_name = &dirName;
|
|
mount.mount_mode = mode;
|
|
mount.blocks = blocks;
|
|
return sceSaveDataMount3(&mount, result);
|
|
}
|
|
|
|
void CheckValidValuesMount() {
|
|
const char* values[] = {nullptr, "fixed", "host"};
|
|
int index = 0;
|
|
for (const char* value : values) {
|
|
SetFreeSpaceVariable(value);
|
|
const std::string name = "HS_VALID_" + std::to_string(index++);
|
|
SaveDataMountResult result{};
|
|
const int code = Mount(name.c_str(), MountReadWrite | MountCreate2, 96, &result);
|
|
Check(code == 0, "a valid ANYPS5_FREE_SPACE value lets a save be created (" + name + ")");
|
|
if (code == 0) Check(sceSaveDataUmount2(0, &result.mount_point) == 0, "the created save unmounts (" + name + ")");
|
|
}
|
|
SetFreeSpaceVariable(nullptr);
|
|
}
|
|
|
|
void CheckInvalidValueStopsMount() {
|
|
SetFreeSpaceVariable("bogus");
|
|
SaveDataMountResult result{};
|
|
std::string message;
|
|
try {
|
|
Mount("HS_BOGUS", MountReadWrite | MountCreate2, 96, &result);
|
|
} catch (const std::runtime_error& error) {
|
|
message = error.what();
|
|
}
|
|
Check(message.find("ANYPS5_FREE_SPACE") != std::string::npos, "an invalid ANYPS5_FREE_SPACE throws an error naming the variable");
|
|
Check(!std::filesystem::exists("_sd/HS_BOGUS"), "an invalid ANYPS5_FREE_SPACE stops the mount before a save is created");
|
|
|
|
message.clear();
|
|
try {
|
|
Mount("HS_BOGUS", MountReadWrite, 0, &result);
|
|
} catch (const std::runtime_error& error) {
|
|
message = error.what();
|
|
}
|
|
Check(message.find("ANYPS5_FREE_SPACE") != std::string::npos, "an open mount with an invalid ANYPS5_FREE_SPACE also throws");
|
|
SetFreeSpaceVariable(nullptr);
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
void CheckEmptyValueStopsMount() {
|
|
SetFreeSpaceVariable("");
|
|
SaveDataMountResult result{};
|
|
std::string message;
|
|
try {
|
|
Mount("HS_EMPTY", MountReadWrite | MountCreate2, 96, &result);
|
|
} catch (const std::runtime_error& error) {
|
|
message = error.what();
|
|
}
|
|
Check(message.find("ANYPS5_FREE_SPACE") != std::string::npos, "an empty ANYPS5_FREE_SPACE throws an error naming the variable");
|
|
Check(!std::filesystem::exists("_sd/HS_EMPTY"), "an empty ANYPS5_FREE_SPACE stops the mount before a save is created");
|
|
SetFreeSpaceVariable(nullptr);
|
|
}
|
|
#endif
|
|
|
|
void CheckHostRoom() {
|
|
SetFreeSpaceVariable("host");
|
|
SetSaveDataFreeSpaceSourceForTest_nid_no_patch(FakeFreeSpace);
|
|
SaveDataMountResult result{};
|
|
|
|
fakeAvailable = 0;
|
|
fakeCalls = 0;
|
|
result = SaveDataMountResult{};
|
|
int code = Mount("HS_FULL", MountReadWrite | MountCreate2, 96, &result);
|
|
Check(code == SaveDataErrorNoSpaceFs, "a save the full volume cannot hold is NO_SPACE_FS");
|
|
Check(result.required_blocks == 96, "a full volume reports every requested block as required");
|
|
Check(!std::filesystem::exists("_sd/HS_FULL"), "a refused save creates no directory");
|
|
Check(fakeCalls == 1, "the volume is queried once per create");
|
|
|
|
fakeAvailable = 96 * BlockBytes - 1;
|
|
result = SaveDataMountResult{};
|
|
code = Mount("HS_TINY", MountReadWrite | MountCreate2, 96, &result);
|
|
Check(code == SaveDataErrorNoSpaceFs && result.required_blocks == 1, "one byte short reports one block required");
|
|
Check(!std::filesystem::exists("_sd/HS_TINY"), "a one-byte-short save creates no directory");
|
|
|
|
fakeAvailable = 96 * BlockBytes;
|
|
code = Mount("HS_EXACT", MountReadWrite | MountCreate2, 96, &result);
|
|
Check(code == 0, "a volume with exactly the requested room creates the save");
|
|
if (code == 0) Check(sceSaveDataUmount2(0, &result.mount_point) == 0, "the exact-room save unmounts");
|
|
|
|
fakeAvailable = UINT64_MAX;
|
|
code = Mount("HS_HUGE", MountReadWrite | MountCreate2, 0, &result);
|
|
Check(code == 0, "a volume with unbounded room creates a default-size save");
|
|
if (code == 0) Check(sceSaveDataUmount2(0, &result.mount_point) == 0, "the huge-room save unmounts");
|
|
|
|
fakeAvailable = 16384 * BlockBytes - 1;
|
|
result = SaveDataMountResult{};
|
|
code = Mount("HS_DEFAULT", MountReadWrite | MountCreate2, 0, &result);
|
|
Check(code == SaveDataErrorNoSpaceFs && result.required_blocks == 1, "a default-size save needs the full 16384 blocks");
|
|
|
|
fakeFails = true;
|
|
code = Mount("HS_ERROR", MountReadWrite | MountCreate2, 96, &result);
|
|
Check(code == 0, "a failed volume query keeps today's behaviour and creates the save");
|
|
if (code == 0) Check(sceSaveDataUmount2(0, &result.mount_point) == 0, "the save created after a failed query unmounts");
|
|
fakeFails = false;
|
|
|
|
fakeAvailable = UINT64_MAX;
|
|
code = Mount("HS_OPEN", MountReadWrite | MountCreate2, 96, &result);
|
|
Check(code == 0, "the save to reopen is created while the volume has room");
|
|
if (code == 0) Check(sceSaveDataUmount2(0, &result.mount_point) == 0, "the save to reopen unmounts");
|
|
fakeAvailable = 0;
|
|
code = Mount("HS_OPEN", MountReadWrite, 0, &result);
|
|
Check(code == 0, "a full volume still opens an existing save");
|
|
if (code == 0) Check(sceSaveDataUmount2(0, &result.mount_point) == 0, "the opened save unmounts");
|
|
|
|
SetFreeSpaceVariable(nullptr);
|
|
fakeAvailable = 0;
|
|
fakeCalls = 0;
|
|
code = Mount("HS_FIXED", MountReadWrite | MountCreate2, 96, &result);
|
|
Check(code == 0, "the default fixed mode creates a save on a full volume");
|
|
Check(fakeCalls == 0, "the default fixed mode never queries the volume");
|
|
if (code == 0) Check(sceSaveDataUmount2(0, &result.mount_point) == 0, "the fixed-mode save unmounts");
|
|
|
|
SetSaveDataFreeSpaceSourceForTest_nid_no_patch(nullptr);
|
|
}
|
|
|
|
std::uint64_t MountInfoFreeBlocks(const char* name, std::uint64_t* blocks) {
|
|
SceSaveDataDirName dirName{};
|
|
std::snprintf(dirName.data, sizeof(dirName.data), "%s", name);
|
|
SaveDataMount3 mount{};
|
|
mount.user_id = 1;
|
|
mount.dir_name = &dirName;
|
|
mount.mount_mode = MountReadWrite;
|
|
SaveDataMountResult result{};
|
|
Check(sceSaveDataMount3(&mount, &result) == 0, std::string("open for mount info: ") + name);
|
|
SaveDataMountInfo info{};
|
|
Check(sceSaveDataGetMountInfo(&result.mount_point, &info) == 0, std::string("mount info of ") + name);
|
|
Check(sceSaveDataUmount2(0, &result.mount_point) == 0, std::string("unmount after mount info: ") + name);
|
|
*blocks = info.blocks;
|
|
return info.free_blocks;
|
|
}
|
|
|
|
std::uint64_t SearchFreeBlocks(const char* pattern, std::uint64_t* blocks) {
|
|
SceSaveDataDirName patternName{};
|
|
std::snprintf(patternName.data, sizeof(patternName.data), "%s", pattern);
|
|
SaveDataDirNameSearchCond cond{};
|
|
cond.user_id = 1;
|
|
cond.dir_name = &patternName;
|
|
cond.key = 5;
|
|
cond.order = 0;
|
|
SceSaveDataDirName names[1]{};
|
|
SaveDataSearchInfo infos[1]{};
|
|
SaveDataDirNameSearchResult result{};
|
|
result.dir_names = names;
|
|
result.dir_names_num = 1;
|
|
result.infos = infos;
|
|
Check(sceSaveDataDirNameSearch(&cond, &result) == 0, "free-blocks search succeeds");
|
|
Check(result.set_num == 1, "free-blocks search finds the save");
|
|
*blocks = infos[0].blocks;
|
|
return infos[0].free_blocks;
|
|
}
|
|
|
|
void CheckHostFreeBlocks() {
|
|
SetFreeSpaceVariable("host");
|
|
SetSaveDataFreeSpaceSourceForTest_nid_no_patch(FakeFreeSpace);
|
|
SaveDataMountResult result{};
|
|
fakeAvailable = UINT64_MAX;
|
|
const int created = Mount("HS_INFO", MountReadWrite | MountCreate2, 200, &result);
|
|
Check(created == 0, "a save with 200 blocks is created");
|
|
if (created == 0) Check(sceSaveDataUmount2(0, &result.mount_point) == 0, "the new save unmounts");
|
|
{
|
|
std::ofstream data("_sd/HS_INFO/data.bin", std::ios::binary);
|
|
const std::string bytes(40000, 'x');
|
|
data.write(bytes.data(), static_cast<std::streamsize>(bytes.size()));
|
|
}
|
|
|
|
std::uint64_t blocks = 0;
|
|
fakeAvailable = UINT64_MAX;
|
|
Check(MountInfoFreeBlocks("HS_INFO", &blocks) == 199 && blocks == 200, "an unbounded volume reports the save's own free blocks");
|
|
fakeAvailable = 10 * BlockBytes + 5;
|
|
Check(MountInfoFreeBlocks("HS_INFO", &blocks) == 10 && blocks == 200, "mount info is bounded by the blocks the volume has");
|
|
Check(SearchFreeBlocks("HS_INF%", &blocks) == 10 && blocks == 200, "the search reports the same bound");
|
|
fakeAvailable = 0;
|
|
Check(MountInfoFreeBlocks("HS_INFO", &blocks) == 0, "a full volume reports no free blocks");
|
|
fakeFails = true;
|
|
Check(MountInfoFreeBlocks("HS_INFO", &blocks) == 199, "a failed volume query reports the save's own free blocks");
|
|
fakeFails = false;
|
|
|
|
SetFreeSpaceVariable(nullptr);
|
|
fakeAvailable = 0;
|
|
fakeCalls = 0;
|
|
Check(MountInfoFreeBlocks("HS_INFO", &blocks) == 199, "the default fixed mode reports the save's own free blocks");
|
|
Check(fakeCalls == 0, "the default fixed mode never bounds free blocks by the volume");
|
|
SetSaveDataFreeSpaceSourceForTest_nid_no_patch(nullptr);
|
|
}
|
|
|
|
void CheckDefaultVolumeQuery() {
|
|
SetFreeSpaceVariable("host");
|
|
SetSaveDataFreeSpaceSourceForTest_nid_no_patch(nullptr);
|
|
SaveDataMountResult result{};
|
|
const int created = Mount("HS_REAL", MountReadWrite | MountCreate2, 1, &result);
|
|
Check(created == 0, "a 1-block save is created with the default volume query");
|
|
if (created == 0) Check(sceSaveDataUmount2(0, &result.mount_point) == 0, "the default-query save unmounts");
|
|
|
|
std::uint64_t blocks = 0;
|
|
Check(MountInfoFreeBlocks("HS_REAL", &blocks) > 0, "the default volume query reports room for a 1-block save");
|
|
SetFreeSpaceVariable(nullptr);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main() {
|
|
const auto previous = std::filesystem::current_path();
|
|
std::mt19937_64 random(std::random_device{}());
|
|
const auto root = std::filesystem::temp_directory_path() / ("anyps5-savedata-host-space-" + std::to_string(random()));
|
|
std::filesystem::create_directories(root);
|
|
std::filesystem::current_path(root);
|
|
sceSaveDataInitialize3(nullptr);
|
|
|
|
CheckValidValuesMount();
|
|
CheckInvalidValueStopsMount();
|
|
CheckHostRoom();
|
|
CheckHostFreeBlocks();
|
|
CheckDefaultVolumeQuery();
|
|
#ifndef _WIN32
|
|
CheckEmptyValueStopsMount();
|
|
#endif
|
|
|
|
sceSaveDataTerminate();
|
|
std::filesystem::current_path(previous);
|
|
std::error_code ignored;
|
|
std::filesystem::remove_all(root, ignored);
|
|
if (failures != 0) {
|
|
std::fprintf(stderr, "%d savedata host space check(s) failed\n", failures);
|
|
return 1;
|
|
}
|
|
std::printf("savedata host space tests passed\n");
|
|
return 0;
|
|
}
|