SavaData: backup features

This commit is contained in:
Vinicius Rangel 2024-09-11 04:58:37 -03:00
commit de0a38db0a
No known key found for this signature in database
GPG key ID: A5B154D904B761D9
9 changed files with 589 additions and 142 deletions

View file

@ -232,6 +232,8 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp
src/core/libraries/system/msgdialog_ui.cpp
src/core/libraries/system/posix.cpp
src/core/libraries/system/posix.h
src/core/libraries/save_data/save_backup.cpp
src/core/libraries/save_data/save_backup.h
src/core/libraries/save_data/save_instance.cpp
src/core/libraries/save_data/save_instance.h
src/core/libraries/save_data/savedata.cpp

View file

@ -51,6 +51,8 @@ PSF& PSF::operator=(PSF&& other) noexcept {
}
bool PSF::Open(const std::filesystem::path& filepath) {
last_write = std::filesystem::last_write_time(filepath);
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
return false;
@ -125,6 +127,8 @@ bool PSF::Encode(const std::filesystem::path& filepath) const {
return false;
}
last_write = std::filesystem::file_time_type::clock::now();
const auto psf_buffer = Encode();
return file.Write(psf_buffer) == psf_buffer.size();
}

View file

@ -70,11 +70,17 @@ public:
void AddString(std::string key, std::string value, bool update = false);
void AddInteger(std::string key, s32 value, bool update = false);
[[nodiscard]] std::filesystem::file_time_type GetLastWrite() const {
return last_write;
}
[[nodiscard]] const std::vector<Entry>& GetEntries() const {
return entry_list;
}
private:
mutable std::filesystem::file_time_type last_write;
std::vector<Entry> entry_list;
std::unordered_map<size_t, std::vector<u8>> map_binaries;

View file

@ -0,0 +1,199 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <deque>
#include <mutex>
#include <semaphore>
#include <magic_enum.hpp>
#include "save_backup.h"
#include "save_instance.h"
#include "common/logging/log.h"
#include "common/logging/log_entry.h"
#include "common/polyfill_thread.h"
#include "common/thread.h"
constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save
constexpr std::string_view backup_dir = "sce_backup"; // backup folder
constexpr std::string_view backup_dir_tmp = "sce_backup_tmp"; // in-progress backup folder
namespace fs = std::filesystem;
namespace Libraries::SaveData::Backup {
static std::jthread g_backup_thread;
static std::counting_semaphore g_backup_thread_semaphore{0};
static std::mutex g_backup_queue_mutex;
static std::deque<BackupRequest> g_backup_queue;
static std::deque<BackupRequest> g_result_queue;
static std::atomic_int g_backup_progress = 0;
static std::atomic g_backup_status = WorkerStatus::NotStarted;
static void backup(const std::filesystem::path& dir_name) {
if (!fs::exists(dir_name)) {
return;
}
std::vector<std::filesystem::path> backup_files;
for (const auto& entry : fs::directory_iterator(dir_name)) {
const auto filename = entry.path().filename();
if (filename != backup_dir && filename != backup_dir_tmp) {
backup_files.push_back(entry.path());
}
}
const auto backup_dir = dir_name / ::backup_dir;
const auto backup_dir_tmp = dir_name / ::backup_dir_tmp;
g_backup_progress = 0;
int total_count = static_cast<int>(backup_files.size());
int current_count = 0;
fs::remove_all(backup_dir_tmp);
fs::create_directory(backup_dir_tmp);
for (const auto& file : backup_files) {
fs::copy(file, backup_dir_tmp / file.filename(), fs::copy_options::recursive);
current_count++;
g_backup_progress = current_count * 100 / total_count;
}
bool has_existing = fs::exists(backup_dir);
if (has_existing) {
fs::rename(backup_dir, dir_name / "sce_backup_old");
}
fs::rename(backup_dir_tmp, backup_dir);
if (has_existing) {
fs::remove_all(dir_name / "sce_backup_old");
}
}
static void BackupThreadBody() {
Common::SetCurrentThreadName("SaveData_BackupThread");
while (true) {
g_backup_status = WorkerStatus::Waiting;
g_backup_thread_semaphore.acquire();
BackupRequest req;
{
std::scoped_lock lk{g_backup_queue_mutex};
req = g_backup_queue.front();
}
if (req.save_path.empty()) {
break;
}
g_backup_status = WorkerStatus::Running;
LOG_INFO(Lib_SaveData, "Backing up the following directory: {}", req.save_path.string());
backup(req.save_path);
LOG_DEBUG(Lib_SaveData, "Backing up the following directory: {} finished",
req.save_path.string());
{
std::scoped_lock lk{g_backup_queue_mutex};
g_backup_queue.pop_front();
g_result_queue.push_back(std::move(req));
if (g_result_queue.size() > 20) {
g_result_queue.pop_front();
}
}
}
g_backup_status = WorkerStatus::NotStarted;
}
void StartThread() {
if (g_backup_status != WorkerStatus::NotStarted) {
return;
}
LOG_DEBUG(Lib_SaveData, "Starting backup thread");
g_backup_thread = std::jthread{BackupThreadBody};
g_backup_status = WorkerStatus::Waiting;
}
void StopThread() {
if (g_backup_status == WorkerStatus::NotStarted || g_backup_status == WorkerStatus::Stopping) {
return;
}
LOG_DEBUG(Lib_SaveData, "Stopping backup thread");
{
std::scoped_lock lk{g_backup_queue_mutex};
g_backup_queue.emplace_back(BackupRequest{});
}
g_backup_thread_semaphore.release();
g_backup_status = WorkerStatus::Stopping;
}
bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id,
std::string_view dir_name, OrbisSaveDataEventType origin) {
auto save_path = SaveInstance::MakeDirSavePath(user_id, title_id, dir_name);
if (g_backup_status != WorkerStatus::Waiting && g_backup_status != WorkerStatus::Running) {
LOG_ERROR(Lib_SaveData, "Called backup while status is {}. Backup request to {} ignored",
magic_enum::enum_name(g_backup_status.load()), save_path.string());
return false;
}
{
std::scoped_lock lk{g_backup_queue_mutex};
g_backup_queue.push_back(BackupRequest{
.user_id = user_id,
.title_id = std::string{title_id},
.dir_name = std::string{dir_name},
.origin = origin,
.save_path = std::move(save_path),
});
}
g_backup_thread_semaphore.release();
return true;
}
bool Restore(const std::filesystem::path& save_path) {
LOG_INFO(Lib_SaveData, "Restoring backup for {}", save_path.string());
if (!fs::exists(save_path) || !fs::exists(save_path / backup_dir)) {
return false;
}
for (const auto& entry : fs::directory_iterator(save_path)) {
const auto filename = entry.path().filename();
if (filename != backup_dir) {
fs::remove_all(entry.path());
}
}
for (const auto& entry : fs::directory_iterator(save_path / backup_dir)) {
const auto filename = entry.path().filename();
fs::copy(entry.path(), save_path / filename, fs::copy_options::recursive);
}
return true;
}
WorkerStatus GetWorkerStatus() {
return g_backup_status;
}
bool IsBackupExecutingFor(const std::filesystem::path& save_path) {
std::scoped_lock lk{g_backup_queue_mutex};
return std::ranges::find(g_backup_queue, save_path,
[](const auto& v) { return v.save_path; }) != g_backup_queue.end();
}
std::filesystem::path MakeBackupPath(const std::filesystem::path& save_path) {
return save_path / backup_dir;
}
std::optional<BackupRequest> PopLastEvent() {
std::scoped_lock lk{g_backup_queue_mutex};
if (g_result_queue.empty()) {
return std::nullopt;
}
auto req = std::move(g_result_queue.front());
g_result_queue.pop_front();
return req;
}
float GetProgress() {
return static_cast<float>(g_backup_progress) / 100.0f;
}
void ClearProgress() {
g_backup_progress = 0;
}
} // namespace Libraries::SaveData::Backup

View file

@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include "common/types.h"
namespace Libraries::SaveData {
using OrbisUserServiceUserId = s32;
namespace Backup {
enum class WorkerStatus {
NotStarted,
Waiting,
Running,
Stopping,
};
enum class OrbisSaveDataEventType : u32 {
UMOUNT_BACKUP = 1,
BACKUP = 2,
SAVE_DATA_MEMORY_SYNC = 3,
};
struct BackupRequest {
OrbisUserServiceUserId user_id{};
std::string title_id{};
std::string dir_name{};
OrbisSaveDataEventType origin{};
std::filesystem::path save_path{};
};
// No problem calling this function if the backup thread is already running
void StartThread();
void StopThread();
bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id,
std::string_view dir_name, OrbisSaveDataEventType origin);
bool Restore(const std::filesystem::path& save_path);
WorkerStatus GetWorkerStatus();
bool IsBackupExecutingFor(const std::filesystem::path& save_path);
std::filesystem::path MakeBackupPath(const std::filesystem::path& save_path);
std::optional<BackupRequest> PopLastEvent();
float GetProgress();
void ClearProgress();
} // namespace Backup
} // namespace Libraries::SaveData

View file

@ -113,7 +113,6 @@ SaveInstance& SaveInstance::operator=(SaveInstance&& other) noexcept {
param_sfo_path = std::move(other.param_sfo_path);
corrupt_file_path = std::move(other.corrupt_file_path);
corrupt_file = std::move(other.corrupt_file);
last_write = other.last_write;
param_sfo = std::move(other.param_sfo);
mount_point = std::move(other.mount_point);
max_blocks = other.max_blocks;
@ -140,14 +139,12 @@ void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_cor
}
}
exists = true;
last_write = std::filesystem::file_time_type::clock::now();
} else {
if (!ignore_corrupt && fs::exists(corrupt_file_path)) {
throw std::filesystem::filesystem_error(
"Corrupted save data", corrupt_file_path,
std::make_error_code(std::errc::illegal_byte_sequence));
}
last_write = fs::last_write_time(param_sfo_path);
if (!param_sfo.Open(param_sfo_path)) {
throw std::filesystem::filesystem_error(
"Failed to read param.sfo", param_sfo_path,
@ -155,7 +152,7 @@ void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_cor
}
}
if (!ignore_corrupt) {
if (!ignore_corrupt && !read_only) {
int err = corrupt_file.Open(corrupt_file_path, Common::FS::FileAccessMode::Write);
if (err != 0) {
throw std::filesystem::filesystem_error(
@ -183,7 +180,6 @@ void SaveInstance::Umount() {
throw std::filesystem::filesystem_error("Failed to write param.sfo", param_sfo_path,
std::make_error_code(std::errc::permission_denied));
}
last_write = std::filesystem::file_time_type::clock::now();
param_sfo = PSF();
corrupt_file.Close();

View file

@ -5,6 +5,7 @@
#include <filesystem>
#include "common/io_file.h"
#include "core/file_format/psf.h"
namespace Core::FileSys {
@ -43,7 +44,6 @@ class SaveInstance {
Common::FS::IOFile corrupt_file;
std::filesystem::file_time_type last_write;
PSF param_sfo;
std::string mount_point;
@ -90,6 +90,14 @@ public:
return exists;
}
[[nodiscard]] OrbisUserServiceUserId GetUserId() const noexcept {
return user_id;
}
[[nodiscard]] std::string_view GetTitleId() const noexcept {
return game_serial;
}
[[nodiscard]] const std::string& GetDirName() const noexcept {
return dir_name;
}
@ -98,10 +106,6 @@ public:
return save_path;
}
[[nodiscard]] const std::filesystem::file_time_type& GetLastWrite() const noexcept {
return last_write;
}
[[nodiscard]] const PSF& GetParamSFO() const noexcept {
return param_sfo;
}

View file

@ -18,6 +18,7 @@
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/save_data/savedata.h"
#include "save_backup.h"
#include "save_instance.h"
namespace fs = std::filesystem;
@ -56,12 +57,12 @@ constexpr u32 OrbisSaveDataBlocksMax = 32768; // 1 GiB
constexpr size_t OrbisSaveDataMountPointDataMaxsize = 16;
// Maximum size for a title ID (4 uppercase letters + 5 digits)
constexpr int OrbisSaveDataTitleIdDataSize = 10;
// Maximum size for a save data directory name
constexpr int OrbisSaveDataDirnameDataMaxsize = 32;
constexpr size_t OrbisSaveDataTitleMaxsize = 128;
constexpr size_t OrbisSaveDataSubtitleMaxsize = 128;
constexpr size_t OrbisSaveDataDetailMaxsize = 1024;
constexpr int OrbisSaveDataDirnameDataMaxsize = 32; // Maximum save directory name size
constexpr size_t OrbisSaveDataTitleMaxsize = 128; // Maximum title name size
constexpr size_t OrbisSaveDataSubtitleMaxsize = 128; // Maximum subtitle name size
constexpr size_t OrbisSaveDataDetailMaxsize = 1024; // Maximum detail name size
constexpr size_t OrbisSaveDataFingerprintDataSize = 65; // Maximum fingerprint size
constexpr std::string_view OrbisSaveDataDirnameSaveDataMemory = "sce_sdmemory";
constexpr std::string_view OrbisSaveDataFilenameSaveDataMemory = "memory.dat";
@ -120,6 +121,40 @@ struct OrbisSaveDataParam {
int : 32;
time_t mtime;
std::array<u8, 32> _reserved;
void FromSFO(const PSF& sfo) {
memset(this, 0, sizeof(OrbisSaveDataParam));
title.FromString(*sfo.GetString(SaveParams::MAINTITLE));
subTitle.FromString(*sfo.GetString(SaveParams::SUBTITLE));
detail.FromString(*sfo.GetString(SaveParams::DETAIL));
userParam = sfo.GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0);
const auto time_since_epoch = sfo.GetLastWrite().time_since_epoch();
mtime = chrono::duration_cast<chrono::seconds>(time_since_epoch).count();
}
};
struct OrbisSaveDataFingerprint {
CString<OrbisSaveDataFingerprintDataSize> data;
std::array<u8, 15> _pad;
};
struct OrbisSaveDataBackup {
OrbisUserServiceUserId userId;
s32 : 32;
const OrbisSaveDataTitleId* titleId;
const OrbisSaveDataDirName* dirName;
const OrbisSaveDataFingerprint* param;
std::array<u8, 32> _reserved;
};
struct OrbisSaveDataCheckBackupData {
OrbisUserServiceUserId userId;
s32 : 32;
const OrbisSaveDataTitleId* titleId;
const OrbisSaveDataDirName* dirName;
OrbisSaveDataParam* param;
OrbisSaveDataIcon* icon;
std::array<u8, 32> _reserved;
};
struct OrbisSaveDataDelete {
@ -137,6 +172,19 @@ struct OrbisSaveDataIcon {
size_t bufSize;
size_t dataSize;
std::array<u8, 32> _reserved;
Error LoadIcon(const std::filesystem::path& icon_path) {
try {
const Common::FS::IOFile file(icon_path, Common::FS::FileAccessMode::Read);
dataSize = file.GetSize();
file.Seek(0);
file.ReadRaw<u8>(buf, std::min(bufSize, dataSize));
} catch (const fs::filesystem_error& e) {
LOG_ERROR(Lib_SaveData, "Failed to load icon: {}", e.what());
return Error::INTERNAL;
}
return Error::OK;
}
};
struct OrbisSaveDataMount2 {
@ -179,6 +227,17 @@ struct OrbisSaveDataMountResult {
s32 : 32;
};
struct OrbisSaveDataRestoreBackupData {
OrbisUserServiceUserId userId;
s32 : 32;
const OrbisSaveDataTitleId* titleId;
const OrbisSaveDataDirName* dirName;
const OrbisSaveDataFingerprint* fingerprint;
u32 _unused;
std::array<u8, 32> _reserved;
s32 : 32;
};
struct OrbisSaveDataDirNameSearchCond {
OrbisUserServiceUserId userId;
int : 32;
@ -207,6 +266,22 @@ struct OrbisSaveDataDirNameSearchResult {
int : 32;
};
struct OrbisSaveDataEventParam { // dummy structure
OrbisSaveDataEventParam() = delete;
};
using OrbisSaveDataEventType = Backup::OrbisSaveDataEventType;
struct OrbisSaveDataEvent {
OrbisSaveDataEventType type;
s32 errorCode;
OrbisUserServiceUserId userId;
std::array<u8, 4> _pad;
OrbisSaveDataTitleId titleId;
OrbisSaveDataDirName dirName;
std::array<u8, 40> _reserved;
};
bool is_rw_mode = false;
static bool g_initialized = false;
@ -252,16 +327,23 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
return Error::INVALID_LOGIN_USER;
}
if (mount_info->blocks < OrbisSaveDataBlocksMin2 ||
mount_info->blocks > OrbisSaveDataBlocksMax) {
mount_info->blocks > OrbisSaveDataBlocksMax || mount_info->dirName == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
return Error::PARAMETER;
}
// check backup status
{
const auto save_path = SaveInstance::MakeDirSavePath(mount_info->userId, g_game_serial,
mount_info->dirName->data);
if (Backup::IsBackupExecutingFor(save_path)) {
return Error::BACKUP_BUSY;
}
}
auto mount_mode = mount_info->mountMode;
const bool is_ro = True(mount_mode & OrbisSaveDataMountMode::RDONLY);
if (!is_ro) {
ASSERT(True(mount_mode & OrbisSaveDataMountMode::RDWR));
}
const bool create = True(mount_mode & OrbisSaveDataMountMode::CREATE);
const bool create_if_not_exist = True(mount_mode & OrbisSaveDataMountMode::CREATE2);
ASSERT(!create || !create_if_not_exist); // Can't have both
@ -335,14 +417,69 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
return Error::OK;
}
static Error Umount(const OrbisSaveDataMountPoint* mountPoint, bool call_backup = false) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
}
if (mountPoint == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
return Error::PARAMETER;
}
LOG_DEBUG(Lib_SaveData, "Umount mountPoint:{}", mountPoint->data.to_view());
const std::string_view mount_point_str{mountPoint->data};
for (auto& instance : g_mount_slots) {
if (instance.has_value()) {
const auto& slot_name = instance->GetMountPoint();
if (slot_name == mount_point_str) {
if (call_backup) {
Backup::StartThread();
Backup::NewRequest(instance->GetUserId(), instance->GetTitleId(),
instance->GetDirName(),
OrbisSaveDataEventType::UMOUNT_BACKUP);
}
// TODO: check if is busy
instance->Umount();
instance.reset();
return Error::OK;
}
}
}
return Error::NOT_FOUND;
}
int PS4_SYSV_ABI sceSaveDataAbort() {
LOG_ERROR(Lib_SaveData, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataBackup() {
LOG_ERROR(Lib_SaveData, "(STUBBED) called");
return ORBIS_OK;
Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
}
if (backup == nullptr || backup->dirName == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
return Error::PARAMETER;
}
const std::string_view dir_name{backup->dirName->data};
LOG_DEBUG(Lib_SaveData, "called dirName: {}", dir_name);
std::string_view title{backup->titleId != nullptr ? std::string_view{backup->titleId->data}
: std::string_view{g_game_serial}};
for (const auto& instance : g_mount_slots) {
if (instance.has_value() && instance->GetUserId() == backup->userId &&
instance->GetTitleId() == title && instance->GetDirName() == dir_name) {
return Error::BUSY;
}
}
Backup::StartThread();
Backup::NewRequest(backup->userId, title, dir_name, OrbisSaveDataEventType::BACKUP);
return Error::OK;
}
int PS4_SYSV_ABI sceSaveDataBindPsnAccount() {
@ -365,9 +502,54 @@ int PS4_SYSV_ABI sceSaveDataChangeInternal() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataCheckBackupData(/*const OrbisSaveDataCheckBackupData* check*/) {
LOG_ERROR(Lib_SaveData, "(STUBBED) called");
return ORBIS_OK;
Error PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
}
if (check == nullptr || check->dirName == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
return Error::PARAMETER;
}
const std::string_view title{check->titleId != nullptr ? std::string_view{check->titleId->data}
: std::string_view{g_game_serial}};
const auto save_path =
SaveInstance::MakeDirSavePath(check->userId, title, check->dirName->data);
for (const auto& instance : g_mount_slots) {
if (instance.has_value() && instance->GetSavePath() == save_path) {
return Error::BUSY;
}
}
if (Backup::IsBackupExecutingFor(save_path)) {
return Error::BACKUP_BUSY;
}
const auto backup_path = Backup::MakeBackupPath(save_path);
if (!fs::exists(backup_path)) {
return Error::NOT_FOUND;
}
if (check->param != nullptr) {
PSF sfo;
if (!sfo.Open(backup_path / "sce_sys" / "param.sfo")) {
LOG_ERROR(Lib_SaveData, "Failed to read SFO at {}", backup_path.string());
return Error::INTERNAL;
}
check->param->FromSFO(sfo);
}
if (check->icon != nullptr) {
const auto icon_path = backup_path / "sce_sys" / "icon0.png";
if (fs::exists(icon_path) && check->icon->LoadIcon(icon_path) != Error::OK) {
return Error::INTERNAL;
}
}
return Error::OK;
}
int PS4_SYSV_ABI sceSaveDataCheckBackupDataForCdlg() {
@ -405,9 +587,14 @@ int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersionLatest() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataClearProgress() {
LOG_ERROR(Lib_SaveData, "(STUBBED) called");
return ORBIS_OK;
Error PS4_SYSV_ABI sceSaveDataClearProgress() {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
}
LOG_DEBUG(Lib_SaveData, "called");
Backup::ClearProgress();
return Error::OK;
}
int PS4_SYSV_ABI sceSaveDataCopy5() {
@ -546,7 +733,6 @@ Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond
std::unordered_map<std::string, PSF> map_dir_sfo;
std::unordered_map<std::string, int> map_max_blocks;
std::unordered_map<std::string, time_t> map_mtime;
std::unordered_map<std::string, OrbisSaveDataBlocks> map_free_size;
for (const auto& dir_name : dir_list) {
@ -563,10 +749,6 @@ Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond
size_t total = SaveInstance::GetMaxBlocks(dir_path);
map_free_size.emplace(dir_name, total - size / OrbisSaveDataBlockSize);
map_max_blocks.emplace(dir_name, total);
const auto last_write = fs::last_write_time(sfo_path).time_since_epoch();
size_t mtime = chrono::duration_cast<chrono::seconds>(last_write).count();
map_mtime.emplace(dir_name, mtime);
}
#define PROJ(x) [&](const auto& v) { return x; }
@ -586,7 +768,7 @@ Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond
std::ranges::stable_sort(dir_list, {}, PROJ(map_free_size.at(v)));
break;
case OrbisSaveDataSortKey::MTIME:
std::ranges::stable_sort(dir_list, {}, PROJ(map_mtime.at(v)));
std::ranges::stable_sort(dir_list, {}, PROJ(map_dir_sfo.at(v).GetLastWrite()));
break;
}
#undef PROJ
@ -605,14 +787,8 @@ Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond
if (result->params != nullptr) {
auto& sfo = map_dir_sfo.at(dir_list[i]);
auto& last_write = map_mtime.at(dir_list[i]);
auto& param_data = result->params[i];
memset(&param_data, 0, sizeof(OrbisSaveDataParam));
param_data.title.FromString(*sfo.GetString(SaveParams::MAINTITLE));
param_data.subTitle.FromString(*sfo.GetString(SaveParams::SUBTITLE));
param_data.detail.FromString(*sfo.GetString(SaveParams::DETAIL));
param_data.userParam = sfo.GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0);
param_data.mtime = last_write;
param_data.FromSFO(sfo);
}
if (result->infos != nullptr) {
@ -685,10 +861,30 @@ int PS4_SYSV_ABI sceSaveDataGetEventInfo() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataGetEventResult(/*const OrbisSaveDataEventParam* eventParam,
OrbisSaveDataEvent* event*/) {
LOG_ERROR(Lib_SaveData, "(STUBBED) called");
return ORBIS_OK;
Error PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam*,
OrbisSaveDataEvent* event) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
}
if (event == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
return Error::PARAMETER;
}
LOG_DEBUG(Lib_SaveData, "called");
auto last_event = Backup::PopLastEvent();
if (!last_event.has_value()) {
return Error::NOT_FOUND;
}
event->type = last_event->origin;
event->errorCode = 0;
event->userId = last_event->user_id;
event->titleId.data.FromString(last_event->title_id);
event->dirName.data.FromString(last_event->dir_name);
return Error::OK;
}
int PS4_SYSV_ABI sceSaveDataGetFormat() {
@ -738,13 +934,11 @@ Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint
}
LOG_DEBUG(Lib_SaveData, "called: paramType = {}", magic_enum::enum_name(paramType));
const PSF* param_sfo = nullptr;
std::filesystem::file_time_type last_write{};
const std::string_view mount_point_str{mountPoint->data};
for (const auto& instance : g_mount_slots) {
if (instance.has_value() && instance->GetMountPoint() == mount_point_str) {
param_sfo = &instance->GetParamSFO();
last_write = instance->GetLastWrite();
break;
}
}
@ -756,13 +950,7 @@ Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint
case OrbisSaveDataParamType::ALL: {
const auto param = static_cast<OrbisSaveDataParam*>(paramBuf);
ASSERT(paramBufSize == sizeof(OrbisSaveDataParam));
memset(param, 0, sizeof(OrbisSaveDataParam));
param->title.FromString(*param_sfo->GetString(SaveParams::MAINTITLE));
param->subTitle.FromString(*param_sfo->GetString(SaveParams::SUBTITLE));
param->detail.FromString(*param_sfo->GetString(SaveParams::DETAIL));
param->userParam = param_sfo->GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0);
param->mtime =
chrono::duration_cast<chrono::seconds>(last_write.time_since_epoch()).count();
param->FromSFO(*param_sfo);
if (gotSize != nullptr) {
*gotSize = sizeof(OrbisSaveDataParam);
}
@ -797,7 +985,8 @@ Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint
} break;
case OrbisSaveDataParamType::MTIME: {
const auto param = static_cast<time_t*>(paramBuf);
*param = chrono::duration_cast<chrono::seconds>(last_write.time_since_epoch()).count();
const auto last_write = param_sfo->GetLastWrite().time_since_epoch();
*param = chrono::duration_cast<chrono::seconds>(last_write).count();
if (gotSize != nullptr) {
*gotSize = sizeof(time_t);
}
@ -809,9 +998,18 @@ Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint
return Error::OK;
}
int PS4_SYSV_ABI sceSaveDataGetProgress() {
LOG_ERROR(Lib_SaveData, "(STUBBED) called");
return ORBIS_OK;
Error PS4_SYSV_ABI sceSaveDataGetProgress(float* progress) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
}
if (progress == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
return Error::PARAMETER;
}
LOG_DEBUG(Lib_SaveData, "called");
*progress = Backup::GetProgress();
return Error::OK;
}
int PS4_SYSV_ABI sceSaveDataGetSaveDataCount() {
@ -924,17 +1122,7 @@ Error PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint
return Error::NOT_FOUND;
}
try {
const Common::FS::IOFile file(path, Common::FS::FileAccessMode::Read);
icon->dataSize = file.GetSize();
file.Seek(0);
file.ReadRaw<u8>(icon->buf, std::min(icon->bufSize, icon->dataSize));
} catch (const fs::filesystem_error& e) {
LOG_ERROR(Lib_SaveData, "Failed to load icon: {}", e.what());
return Error::INTERNAL;
}
return Error::OK;
return icon->LoadIcon(path);
}
Error PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount,
@ -1003,9 +1191,44 @@ int PS4_SYSV_ABI sceSaveDataRegisterEventCallback() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataRestoreBackupData() {
LOG_ERROR(Lib_SaveData, "(STUBBED) called");
return ORBIS_OK;
Error PS4_SYSV_ABI sceSaveDataRestoreBackupData(const OrbisSaveDataRestoreBackupData* restore) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
}
if (restore == nullptr || restore->dirName == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
return Error::PARAMETER;
}
const std::string_view dir_name{restore->dirName->data};
LOG_DEBUG(Lib_SaveData, "called dirName: {}", dir_name);
std::string_view title{restore->titleId != nullptr ? std::string_view{restore->titleId->data}
: std::string_view{g_game_serial}};
const auto save_path = SaveInstance::MakeDirSavePath(restore->userId, title, dir_name);
for (const auto& instance : g_mount_slots) {
if (instance.has_value() && instance->GetSavePath() == save_path) {
return Error::BUSY;
}
}
if (Backup::IsBackupExecutingFor(save_path)) {
return Error::BACKUP_BUSY;
}
const auto backup_path = Backup::MakeBackupPath(save_path);
if (!fs::exists(backup_path)) {
return Error::NOT_FOUND;
}
const bool ok = Backup::Restore(save_path);
if (!ok) {
return Error::INTERNAL;
}
return Error::OK;
}
int PS4_SYSV_ABI sceSaveDataRestoreBackupDataForCdlg() {
@ -1185,6 +1408,7 @@ Error PS4_SYSV_ABI sceSaveDataTerminate() {
}
}
g_initialized = false;
Backup::StopThread();
return Error::OK;
}
@ -1194,28 +1418,8 @@ int PS4_SYSV_ABI sceSaveDataTransferringMount() {
}
Error PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
}
if (mountPoint == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
return Error::PARAMETER;
}
LOG_DEBUG(Lib_SaveData, "called mountPoint:{}", mountPoint->data.to_view());
const std::string_view mount_point_str{mountPoint->data};
for (auto& instance : g_mount_slots) {
if (instance.has_value()) {
const auto& slot_name = instance->GetMountPoint();
if (slot_name == mount_point_str) {
// TODO: check if is busy
instance->Umount();
instance.reset();
return Error::OK;
}
}
}
return Error::NOT_FOUND;
LOG_DEBUG(Lib_SaveData, "called");
return Umount(mountPoint);
}
int PS4_SYSV_ABI sceSaveDataUmountSys() {
@ -1223,9 +1427,9 @@ int PS4_SYSV_ABI sceSaveDataUmountSys() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint) {
LOG_ERROR(Lib_SaveData, "(STUBBED) called");
return ORBIS_OK;
Error PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint) {
LOG_DEBUG(Lib_SaveData, "called");
return Umount(mountPoint, true);
}
int PS4_SYSV_ABI sceSaveDataUnregisterEventCallback() {

View file

@ -10,25 +10,23 @@ class SymbolsResolver;
}
namespace Libraries::SaveData {
constexpr int ORBIS_SAVE_DATA_FINGERPRINT_DATA_SIZE = 65;
struct OrbisSaveDataFingerprint {
char data[ORBIS_SAVE_DATA_FINGERPRINT_DATA_SIZE];
char padding[15];
};
enum class Error : u32;
enum class OrbisSaveDataParamType : u32;
struct OrbisSaveDataBackup;
struct OrbisSaveDataCheckBackupData;
struct OrbisSaveDataDelete;
struct OrbisSaveDataDirNameSearchCond;
struct OrbisSaveDataDirNameSearchResult;
struct OrbisSaveDataEvent;
struct OrbisSaveDataEventParam;
struct OrbisSaveDataIcon;
struct OrbisSaveDataMount2;
struct OrbisSaveDataMount;
struct OrbisSaveDataMountInfo;
struct OrbisSaveDataMountPoint;
struct OrbisSaveDataMountResult;
struct OrbisSaveDataDirNameSearchCond;
struct OrbisSaveDataDirNameSearchResult;
struct OrbisSaveDataRestoreBackupData;
/*typedef u32 OrbisSaveDataSaveDataMemoryOption;
#define ORBIS_SAVE_DATA_MEMORY_OPTION_NONE (0x00000000)
@ -51,22 +49,6 @@ struct OrbisSaveDataMemorySetupResult {
u8 reserved[16];
};
typedef u32 OrbisSaveDataEventType;
#define SCE_SAVE_DATA_EVENT_TYPE_INVALID (0)
#define SCE_SAVE_DATA_EVENT_TYPE_UMOUNT_BACKUP_END (1)
#define SCE_SAVE_DATA_EVENT_TYPE_BACKUP_END (2)
#define SCE_SAVE_DATA_EVENT_TYPE_SAVE_DATA_MEMORY_SYNC_END (3)
struct OrbisSaveDataEvent {
OrbisSaveDataEventType type;
s32 errorCode;
s32 userId;
u8 padding[4];
OrbisSaveDataTitleId titleId;
OrbisSaveDataDirName dirName;
u8 reserved[40];
};
struct OrbisSaveDataMemoryData {
void* buf;
size_t bufSize;
@ -95,18 +77,6 @@ struct OrbisSaveDataMemorySet2 {
u8 reserved[24];
};
struct OrbisSaveDataCheckBackupData {
s32 userId;
int : 32;
const OrbisSaveDataTitleId* titleId;
const OrbisSaveDataDirName* dirName;
OrbisSaveDataParam* param;
OrbisSaveDataIcon* icon;
u8 reserved[32];
};
typedef struct _OrbisSaveDataEventParam OrbisSaveDataEventParam;
typedef u32 OrbisSaveDataMemorySyncOption;
#define SCE_SAVE_DATA_MEMORY_SYNC_OPTION_NONE (0x00000000)
@ -127,12 +97,12 @@ constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_USER_PARAM = 4;
constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_MTIME = 5;*/
int PS4_SYSV_ABI sceSaveDataAbort();
int PS4_SYSV_ABI sceSaveDataBackup();
Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup);
int PS4_SYSV_ABI sceSaveDataBindPsnAccount();
int PS4_SYSV_ABI sceSaveDataBindPsnAccountForSystemBackup();
int PS4_SYSV_ABI sceSaveDataChangeDatabase();
int PS4_SYSV_ABI sceSaveDataChangeInternal();
int PS4_SYSV_ABI sceSaveDataCheckBackupData(/*const OrbisSaveDataCheckBackupData* check*/);
Error PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check);
int PS4_SYSV_ABI sceSaveDataCheckBackupDataForCdlg();
int PS4_SYSV_ABI sceSaveDataCheckBackupDataInternal();
int PS4_SYSV_ABI sceSaveDataCheckCloudData();
@ -140,7 +110,7 @@ int PS4_SYSV_ABI sceSaveDataCheckIpmiIfSize();
int PS4_SYSV_ABI sceSaveDataCheckSaveDataBroken();
int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersion();
int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersionLatest();
int PS4_SYSV_ABI sceSaveDataClearProgress();
Error PS4_SYSV_ABI sceSaveDataClearProgress();
int PS4_SYSV_ABI sceSaveDataCopy5();
int PS4_SYSV_ABI sceSaveDataCreateUploadData();
int PS4_SYSV_ABI sceSaveDataDebug();
@ -169,8 +139,8 @@ int PS4_SYSV_ABI sceSaveDataGetClientThreadPriority();
int PS4_SYSV_ABI sceSaveDataGetCloudQuotaInfo();
int PS4_SYSV_ABI sceSaveDataGetDataBaseFilePath();
int PS4_SYSV_ABI sceSaveDataGetEventInfo();
int PS4_SYSV_ABI sceSaveDataGetEventResult(/*const OrbisSaveDataEventParam* eventParam,
OrbisSaveDataEvent* event*/);
Error PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam* eventParam,
OrbisSaveDataEvent* event);
int PS4_SYSV_ABI sceSaveDataGetFormat();
int PS4_SYSV_ABI sceSaveDataGetMountedSaveDataCount();
Error PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint,
@ -178,7 +148,7 @@ Error PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountP
Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint,
OrbisSaveDataParamType paramType, void* paramBuf,
size_t paramBufSize, size_t* gotSize);
int PS4_SYSV_ABI sceSaveDataGetProgress();
Error PS4_SYSV_ABI sceSaveDataGetProgress(float* progress);
int PS4_SYSV_ABI sceSaveDataGetSaveDataCount();
int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const u32 userId, void* buf, const size_t bufSize,
const int64_t offset);
@ -206,7 +176,7 @@ int PS4_SYSV_ABI sceSaveDataMountSys();
int PS4_SYSV_ABI sceSaveDataPromote5();
int PS4_SYSV_ABI sceSaveDataRebuildDatabase();
int PS4_SYSV_ABI sceSaveDataRegisterEventCallback();
int PS4_SYSV_ABI sceSaveDataRestoreBackupData();
Error PS4_SYSV_ABI sceSaveDataRestoreBackupData(const OrbisSaveDataRestoreBackupData* restore);
int PS4_SYSV_ABI sceSaveDataRestoreBackupDataForCdlg();
int PS4_SYSV_ABI sceSaveDataRestoreLoadSaveDataMemory();
Error PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint,
@ -232,7 +202,7 @@ Error PS4_SYSV_ABI sceSaveDataTerminate();
int PS4_SYSV_ABI sceSaveDataTransferringMount();
Error PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint);
int PS4_SYSV_ABI sceSaveDataUmountSys();
int PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint);
Error PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint);
int PS4_SYSV_ABI sceSaveDataUnregisterEventCallback();
int PS4_SYSV_ABI sceSaveDataUpload();
int PS4_SYSV_ABI Func_02E4C4D201716422();