#include "prx/libSceSaveData/SaveDataFile.hpp" #include #include #include #include #include #include #include #include #include static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "Save-data write failure check failed at line %d\n", line); std::abort(); } } #define Require(value) Check((value), __LINE__) static std::vector ReadFile(const std::filesystem::path& path) { std::ifstream file(path, std::ios::binary); Require(file.is_open()); return {std::istreambuf_iterator(file), std::istreambuf_iterator()}; } static void WriteFile(const std::filesystem::path& path, const std::vector& data) { std::ofstream file(path, std::ios::binary); Require(file.is_open()); file.write(data.data(), static_cast(data.size())); Require(static_cast(file)); } static bool ReplaceFile(const std::filesystem::path& temporary, const std::filesystem::path& destination) { #ifdef _WIN32 return MoveFileExW(temporary.c_str(), destination.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) != 0; #else std::error_code error; std::filesystem::rename(temporary, destination, error); return !error; #endif } int main() { const auto root = std::filesystem::temp_directory_path() / ("anyps5-savedata-write-failure-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); Require(std::filesystem::create_directory(root)); const auto savePath = root / std::filesystem::path(u8"m\u00e9moire-\u65e5\u672c.dat"); const std::vector oldData{'o', 'l', 'd'}; const std::vector replacement{'n', 'e', 'w'}; WriteFile(savePath, oldData); int replaceAttempts = 0; std::filesystem::path failedTemporary; const bool replaced = savedata::replace_file_with( savePath, replacement.data(), replacement.size(), [&](const std::filesystem::path& temporary, const std::filesystem::path& destination) { ++replaceAttempts; Require(destination == savePath); Require(temporary != destination && temporary.parent_path() == destination.parent_path()); Require(ReadFile(temporary) == replacement); failedTemporary = temporary; return false; }); Require(!replaced); Require(replaceAttempts == 1); Require(ReadFile(savePath) == oldData); Require(!std::filesystem::exists(failedTemporary)); auto temporary = savePath; temporary += ".tmp"; WriteFile(temporary, oldData); Require(savedata::replace_file(savePath, replacement.data(), replacement.size())); Require(ReadFile(savePath) == replacement); Require(std::filesystem::exists(temporary)); Require(ReadFile(temporary) == oldData); std::filesystem::remove(temporary); const auto unrelated = root / "unrelated.dat"; WriteFile(unrelated, oldData); std::filesystem::create_hard_link(unrelated, temporary); Require(savedata::replace_file(savePath, replacement.data(), replacement.size())); Require(ReadFile(unrelated) == oldData); Require(std::filesystem::equivalent(unrelated, temporary)); Require(!std::filesystem::equivalent(unrelated, savePath)); std::filesystem::remove(temporary); std::error_code symlinkError; #ifdef _WIN32 if (!CreateSymbolicLinkW(temporary.c_str(), unrelated.c_str(), SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) { symlinkError = std::error_code(GetLastError(), std::system_category()); } #else std::filesystem::create_symlink(unrelated, temporary, symlinkError); #endif #ifdef _WIN32 if (symlinkError == std::errc::permission_denied || symlinkError == std::error_code(ERROR_PRIVILEGE_NOT_HELD, std::system_category())) { std::printf("Symlink case skipped: creating Windows symlinks requires developer mode or privilege\n"); } else #endif { Require(!symlinkError); Require(savedata::replace_file(savePath, replacement.data(), replacement.size())); Require(ReadFile(unrelated) == oldData); #ifdef _WIN32 const auto linkAttributes = GetFileAttributesW(temporary.c_str()); const auto saveAttributes = GetFileAttributesW(savePath.c_str()); Require(linkAttributes != INVALID_FILE_ATTRIBUTES && (linkAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0); Require(saveAttributes != INVALID_FILE_ATTRIBUTES && (saveAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0); #else Require(std::filesystem::is_symlink(temporary)); Require(!std::filesystem::is_symlink(savePath)); #endif Require(ReadFile(savePath) == replacement); std::filesystem::remove(temporary); } Require(std::filesystem::create_directory(temporary)); Require(savedata::replace_file(savePath, replacement.data(), replacement.size())); Require(std::filesystem::is_directory(temporary)); std::filesystem::remove(temporary); std::filesystem::path outerTemporary; Require(savedata::replace_file_with(savePath, replacement.data(), replacement.size(), [&](const auto& outer, const auto& destination) { outerTemporary = outer; Require(savedata::replace_file_with(savePath, oldData.data(), oldData.size(), [&](const auto& inner, const auto& innerDestination) { Require(inner != outer); Require(ReadFile(outer) == replacement); Require(ReadFile(inner) == oldData); return ReplaceFile(inner, innerDestination); })); Require(ReadFile(destination) == oldData); Require(ReadFile(outer) == replacement); return ReplaceFile(outer, destination); })); Require(ReadFile(savePath) == replacement); Require(!std::filesystem::exists(outerTemporary)); Require(savedata::replace_file_with(savePath, replacement.data(), replacement.size(), [&](const auto& created, const auto& destination) { Require(ReplaceFile(created, destination)); outerTemporary = created; WriteFile(created, oldData); return true; })); Require(ReadFile(outerTemporary) == oldData); std::filesystem::remove(outerTemporary); std::filesystem::path thrownTemporary; bool threw = false; try { savedata::replace_file_with(savePath, oldData.data(), oldData.size(), [&](const auto& created, const auto&) -> bool { thrownTemporary = created; throw std::runtime_error("injected replacement failure"); }); } catch (const std::runtime_error&) { threw = true; } Require(threw && !thrownTemporary.empty()); Require(!std::filesystem::exists(thrownTemporary)); Require(ReadFile(savePath) == replacement); Require(!savedata::replace_file(savePath, nullptr, 1)); Require(ReadFile(savePath) == replacement); Require(savedata::replace_file(savePath, nullptr, 0)); Require(ReadFile(savePath).empty()); Require(std::distance(std::filesystem::directory_iterator(root), std::filesystem::directory_iterator{}) == 2); std::filesystem::remove_all(root); }