mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
sceSaveDataDelete appended the title's directory name to the save root without any check and passed the result to std::filesystem::remove_all. "../victim" removed a directory beside the save root, ".." the directory holding the save root, and an empty name or "." every save. The name now has to be terminated within its field, non-empty, not "." or "..", and free of "/", "\" and ":", as sceSaveDataMount3 already requires; otherwise the call returns SAVE_DATA_ERROR_PARAMETER and deletes nothing. The native library gains its own CMakeLists.txt, with the same library definition as the generic one, to register guest_savedata_delete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R5WJS4ZdQynAJesCZKH5hb
69 lines
2.4 KiB
C++
69 lines
2.4 KiB
C++
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include "SceTypes.hpp"
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <random>
|
|
#include <string>
|
|
|
|
extern "C" int APS5_VABI sceSaveDataDelete(const SaveDataDelete*);
|
|
|
|
namespace {
|
|
|
|
constexpr int SaveDataErrorParameter = -2137063424;
|
|
|
|
int failures = 0;
|
|
|
|
void Check(bool condition, const std::string& what) {
|
|
if (condition) return;
|
|
std::fprintf(stderr, "savedata delete check failed: %s\n", what.c_str());
|
|
++failures;
|
|
}
|
|
|
|
int Delete(const char* data, std::size_t size) {
|
|
SceSaveDataDirName name{};
|
|
std::memcpy(name.data, data, size);
|
|
SaveDataDelete del{};
|
|
del.dir_name = &name;
|
|
return sceSaveDataDelete(&del);
|
|
}
|
|
|
|
void Write(const std::filesystem::path& path) {
|
|
std::filesystem::create_directories(path.parent_path());
|
|
std::ofstream(path) << "data";
|
|
}
|
|
|
|
}
|
|
|
|
int main() {
|
|
const auto root = std::filesystem::temp_directory_path() / ("anyps5-savedata-delete-" + std::to_string(std::random_device{}()));
|
|
const auto work = root / "work";
|
|
const auto kept = work / "_sd" / "kept" / "data.bin";
|
|
const auto victim = work / "victim" / "important.txt";
|
|
Write(kept);
|
|
Write(victim);
|
|
const auto previous = std::filesystem::current_path();
|
|
std::filesystem::current_path(work);
|
|
|
|
for (const char* invalid : {"../victim", "", ".", "..", "../..", "kept/..", "a\\b", "c:d"}) {
|
|
Check(Delete(invalid, std::strlen(invalid) + 1) == SaveDataErrorParameter, std::string("\"") + invalid + "\" is rejected");
|
|
Check(std::filesystem::exists(kept) && std::filesystem::exists(victim), std::string("\"") + invalid + "\" deletes nothing");
|
|
}
|
|
char unterminated[sizeof(SceSaveDataDirName::data)];
|
|
std::memset(unterminated, 'a', sizeof(unterminated));
|
|
Check(Delete(unterminated, sizeof(unterminated)) == SaveDataErrorParameter, "an unterminated name is rejected");
|
|
|
|
Check(Delete("kept", 5) == 0, "a valid name is deleted");
|
|
Check(!std::filesystem::exists(kept.parent_path()), "the valid save directory is gone");
|
|
Check(std::filesystem::exists(victim), "the directory beside the save root is untouched");
|
|
|
|
std::filesystem::current_path(previous);
|
|
std::error_code error;
|
|
std::filesystem::remove_all(root, error);
|
|
if (failures != 0) return 1;
|
|
std::printf("savedata delete tests passed\n");
|
|
return 0;
|
|
}
|