mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 16:43:05 -05:00
The 36-byte SceSaveDataDirName declaration placed every search result after the first at the wrong address and could overrun guest arrays. Skyrim read an empty directory name for its second save and aborted when mounting it. Use the 32-byte layout also used by the native dialog, KytyPS5 src/libs/libSaveData.cpp and fpPS4 ps4_libscesavedata.pas. Add a regression using two maximum-length names, a guest buffer guard, and mounts of both returned records. It detects the previous overrun. Fedora 44 Release search, dialog, delete and PS4-save tests pass. Skyrim now mounts and reads the second save instead of rejecting its name. Written with AI assistance.
75 lines
2.9 KiB
C++
75 lines
2.9 KiB
C++
#include "SceTypes.hpp"
|
|
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
|
|
#include <array>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <random>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
|
|
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*);
|
|
}
|
|
|
|
static void Require(bool value, const char* message) {
|
|
if (!value) throw std::runtime_error(message);
|
|
}
|
|
|
|
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);
|
|
|
|
alignas(SceSaveDataDirName) std::array<char, 80> buffer;
|
|
buffer.fill('!');
|
|
SaveDataDirNameSearchCond cond{};
|
|
cond.user_id = 1;
|
|
SaveDataDirNameSearchResult result{};
|
|
result.dir_names = reinterpret_cast<SceSaveDataDirName*>(buffer.data());
|
|
result.dir_names_num = 2;
|
|
Require(sceSaveDataDirNameSearch(&cond, &result) == 0, "search failed");
|
|
Require(result.hit_num == 2 && result.set_num == 2, "search must return both saves");
|
|
for (std::size_t i = 64; i < buffer.size(); ++i) {
|
|
Require(buffer[i] == '!', "directory search overran two 32-byte guest records");
|
|
}
|
|
std::array<bool, 2> found{};
|
|
for (int i = 0; i < 2; ++i) {
|
|
const char* record = buffer.data() + i * 32;
|
|
Require(record[31] == '\0', "directory record must terminate within 32 bytes");
|
|
const bool first = std::strcmp(record, names[0].c_str()) == 0;
|
|
Require(first || std::strcmp(record, names[1].c_str()) == 0, "returned directory name is corrupted");
|
|
const int index = first ? 0 : 1;
|
|
Require(!found[index], "search returned a duplicate directory");
|
|
found[index] = true;
|
|
|
|
SaveDataMount3 mount{};
|
|
mount.user_id = cond.user_id;
|
|
mount.dir_name = reinterpret_cast<const SceSaveDataDirName*>(record);
|
|
mount.mount_mode = 1;
|
|
SaveDataMountResult mounted{};
|
|
Require(sceSaveDataMount3(&mount, &mounted) == 0, "returned directory must be mountable");
|
|
Require(sceSaveDataUmount2(0, &mounted.mount_point) == 0, "unmount failed");
|
|
}
|
|
}
|
|
|
|
int main() {
|
|
const auto previous = std::filesystem::current_path();
|
|
const auto root = std::filesystem::temp_directory_path() /
|
|
("anyps5-savedata-search-" + std::to_string(std::random_device{}()));
|
|
std::filesystem::create_directories(root);
|
|
std::filesystem::current_path(root);
|
|
int status = 0;
|
|
try {
|
|
SearchAndMount();
|
|
} catch (const std::exception& error) {
|
|
std::fprintf(stderr, "%s\n", error.what());
|
|
status = 1;
|
|
}
|
|
std::filesystem::current_path(previous);
|
|
std::filesystem::remove_all(root);
|
|
return status;
|
|
}
|