Savefixes vii (#2279)

* savedata: rewrite save memory functions to handle slot id & better backup management

* savedata: auto-restore backup if needed

* safe save backup shutdown

replaced exit by quick_exit
This commit is contained in:
Vinicius Rangel 2025-01-30 04:45:48 -03:00 committed by GitHub
parent 929e15260d
commit 0358271b93
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 285 additions and 309 deletions

View file

@ -80,6 +80,7 @@ public:
static constexpr u32 FW_40 = 0x4000000; static constexpr u32 FW_40 = 0x4000000;
static constexpr u32 FW_45 = 0x4500000; static constexpr u32 FW_45 = 0x4500000;
static constexpr u32 FW_50 = 0x5000000; static constexpr u32 FW_50 = 0x5000000;
static constexpr u32 FW_55 = 0x5500000;
static constexpr u32 FW_80 = 0x8000000; static constexpr u32 FW_80 = 0x8000000;
static ElfInfo& Instance() { static ElfInfo& Instance() {

View file

@ -121,16 +121,18 @@ static void BackupThreadBody() {
std::scoped_lock lk{g_backup_queue_mutex}; std::scoped_lock lk{g_backup_queue_mutex};
g_backup_queue.front().done = true; g_backup_queue.front().done = true;
} }
std::this_thread::sleep_for(std::chrono::seconds(5)); // Don't backup too often
{ {
std::scoped_lock lk{g_backup_queue_mutex}; std::scoped_lock lk{g_backup_queue_mutex};
g_backup_queue.pop_front(); g_backup_queue.pop_front();
if (req.origin != OrbisSaveDataEventType::__DO_NOT_SAVE) {
g_result_queue.push_back(std::move(req)); g_result_queue.push_back(std::move(req));
if (g_result_queue.size() > 20) { if (g_result_queue.size() > 20) {
g_result_queue.pop_front(); g_result_queue.pop_front();
} }
} }
} }
std::this_thread::sleep_for(std::chrono::seconds(5)); // Don't backup too often
}
g_backup_status = WorkerStatus::NotStarted; g_backup_status = WorkerStatus::NotStarted;
} }
@ -141,6 +143,15 @@ void StartThread() {
LOG_DEBUG(Lib_SaveData, "Starting backup thread"); LOG_DEBUG(Lib_SaveData, "Starting backup thread");
g_backup_status = WorkerStatus::Waiting; g_backup_status = WorkerStatus::Waiting;
g_backup_thread = std::jthread{BackupThreadBody}; g_backup_thread = std::jthread{BackupThreadBody};
static std::once_flag flag;
std::call_once(flag, [] {
std::at_quick_exit([] {
StopThread();
while (GetWorkerStatus() != WorkerStatus::NotStarted) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
});
});
} }
void StopThread() { void StopThread() {
@ -148,12 +159,12 @@ void StopThread() {
return; return;
} }
LOG_DEBUG(Lib_SaveData, "Stopping backup thread"); LOG_DEBUG(Lib_SaveData, "Stopping backup thread");
g_backup_status = WorkerStatus::Stopping;
{ {
std::scoped_lock lk{g_backup_queue_mutex}; std::scoped_lock lk{g_backup_queue_mutex};
g_backup_queue.emplace_back(BackupRequest{}); g_backup_queue.emplace_back(BackupRequest{});
} }
g_backup_thread_semaphore.release(); g_backup_thread_semaphore.release();
g_backup_status = WorkerStatus::Stopping;
} }
bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id, bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id,

View file

@ -25,6 +25,8 @@ enum class OrbisSaveDataEventType : u32 {
UMOUNT_BACKUP = 1, UMOUNT_BACKUP = 1,
BACKUP = 2, BACKUP = 2,
SAVE_DATA_MEMORY_SYNC = 3, SAVE_DATA_MEMORY_SYNC = 3,
__DO_NOT_SAVE = 1000000, // This value is only for the backup thread
}; };
struct BackupRequest { struct BackupRequest {

View file

@ -10,6 +10,7 @@
#include "common/path_util.h" #include "common/path_util.h"
#include "common/singleton.h" #include "common/singleton.h"
#include "core/file_sys/fs.h" #include "core/file_sys/fs.h"
#include "save_backup.h"
#include "save_instance.h" #include "save_instance.h"
constexpr auto OrbisSaveDataBlocksMin2 = 96; // 3MiB constexpr auto OrbisSaveDataBlocksMin2 = 96; // 3MiB
@ -45,13 +46,12 @@ static const std::unordered_map<std::string, std::string> default_title = {
namespace Libraries::SaveData { namespace Libraries::SaveData {
std::filesystem::path SaveInstance::MakeTitleSavePath(OrbisUserServiceUserId user_id, fs::path SaveInstance::MakeTitleSavePath(OrbisUserServiceUserId user_id,
std::string_view game_serial) { std::string_view game_serial) {
return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial; return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial;
} }
std::filesystem::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_id, fs::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_id, std::string_view game_serial,
std::string_view game_serial,
std::string_view dir_name) { std::string_view dir_name) {
return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial / dir_name; return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial / dir_name;
} }
@ -65,7 +65,7 @@ uint64_t SaveInstance::GetMaxBlockFromSFO(const PSF& psf) {
return *(uint64_t*)value.data(); return *(uint64_t*)value.data();
} }
std::filesystem::path SaveInstance::GetParamSFOPath(const std::filesystem::path& dir_path) { fs::path SaveInstance::GetParamSFOPath(const fs::path& dir_path) {
return dir_path / sce_sys / "param.sfo"; return dir_path / sce_sys / "param.sfo";
} }
@ -129,7 +129,6 @@ SaveInstance& SaveInstance::operator=(SaveInstance&& other) noexcept {
save_path = std::move(other.save_path); save_path = std::move(other.save_path);
param_sfo_path = std::move(other.param_sfo_path); param_sfo_path = std::move(other.param_sfo_path);
corrupt_file_path = std::move(other.corrupt_file_path); corrupt_file_path = std::move(other.corrupt_file_path);
corrupt_file = std::move(other.corrupt_file);
param_sfo = std::move(other.param_sfo); param_sfo = std::move(other.param_sfo);
mount_point = std::move(other.mount_point); mount_point = std::move(other.mount_point);
max_blocks = other.max_blocks; max_blocks = other.max_blocks;
@ -142,7 +141,8 @@ SaveInstance& SaveInstance::operator=(SaveInstance&& other) noexcept {
return *this; return *this;
} }
void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_corrupt) { void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_corrupt,
bool dont_restore_backup) {
if (mounted) { if (mounted) {
UNREACHABLE_MSG("Save instance is already mounted"); UNREACHABLE_MSG("Save instance is already mounted");
} }
@ -161,25 +161,27 @@ void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_cor
} }
exists = true; exists = true;
} else { } else {
std::optional<fs::filesystem_error> err;
if (!ignore_corrupt && fs::exists(corrupt_file_path)) { if (!ignore_corrupt && fs::exists(corrupt_file_path)) {
throw std::filesystem::filesystem_error( err = fs::filesystem_error("Corrupted save data", corrupt_file_path,
"Corrupted save data", corrupt_file_path, std::make_error_code(std::errc::illegal_byte_sequence));
} else if (!param_sfo.Open(param_sfo_path)) {
err = fs::filesystem_error("Failed to read param.sfo", param_sfo_path,
std::make_error_code(std::errc::illegal_byte_sequence)); std::make_error_code(std::errc::illegal_byte_sequence));
} }
if (!param_sfo.Open(param_sfo_path)) { if (err.has_value()) {
throw std::filesystem::filesystem_error( if (dont_restore_backup) {
"Failed to read param.sfo", param_sfo_path, throw err.value();
std::make_error_code(std::errc::illegal_byte_sequence)); }
if (Backup::Restore(save_path)) {
return SetupAndMount(read_only, copy_icon, ignore_corrupt, true);
}
} }
} }
if (!ignore_corrupt && !read_only) { if (!ignore_corrupt && !read_only) {
int err = corrupt_file.Open(corrupt_file_path, Common::FS::FileAccessMode::Write); Common::FS::IOFile f(corrupt_file_path, Common::FS::FileAccessMode::Write);
if (err != 0) { f.Close();
throw std::filesystem::filesystem_error(
"Failed to open corrupted file", corrupt_file_path,
std::make_error_code(std::errc::illegal_byte_sequence));
}
} }
max_blocks = static_cast<int>(GetMaxBlockFromSFO(param_sfo)); max_blocks = static_cast<int>(GetMaxBlockFromSFO(param_sfo));
@ -197,12 +199,11 @@ void SaveInstance::Umount() {
mounted = false; mounted = false;
const bool ok = param_sfo.Encode(param_sfo_path); const bool ok = param_sfo.Encode(param_sfo_path);
if (!ok) { if (!ok) {
throw std::filesystem::filesystem_error("Failed to write param.sfo", param_sfo_path, throw fs::filesystem_error("Failed to write param.sfo", param_sfo_path,
std::make_error_code(std::errc::permission_denied)); std::make_error_code(std::errc::permission_denied));
} }
param_sfo = PSF(); param_sfo = PSF();
corrupt_file.Close();
fs::remove(corrupt_file_path); fs::remove(corrupt_file_path);
g_mnt->Unmount(save_path, mount_point); g_mnt->Unmount(save_path, mount_point);
} }
@ -216,7 +217,7 @@ void SaveInstance::CreateFiles() {
const bool ok = param_sfo.Encode(param_sfo_path); const bool ok = param_sfo.Encode(param_sfo_path);
if (!ok) { if (!ok) {
throw std::filesystem::filesystem_error("Failed to write param.sfo", param_sfo_path, throw fs::filesystem_error("Failed to write param.sfo", param_sfo_path,
std::make_error_code(std::errc::permission_denied)); std::make_error_code(std::errc::permission_denied));
} }
} }

View file

@ -42,8 +42,6 @@ class SaveInstance {
std::filesystem::path param_sfo_path; std::filesystem::path param_sfo_path;
std::filesystem::path corrupt_file_path; std::filesystem::path corrupt_file_path;
Common::FS::IOFile corrupt_file;
PSF param_sfo; PSF param_sfo;
std::string mount_point; std::string mount_point;
@ -80,7 +78,8 @@ public:
SaveInstance& operator=(const SaveInstance& other) = delete; SaveInstance& operator=(const SaveInstance& other) = delete;
SaveInstance& operator=(SaveInstance&& other) noexcept; SaveInstance& operator=(SaveInstance&& other) noexcept;
void SetupAndMount(bool read_only = false, bool copy_icon = false, bool ignore_corrupt = false); void SetupAndMount(bool read_only = false, bool copy_icon = false, bool ignore_corrupt = false,
bool dont_restore_backup = false);
void Umount(); void Umount();

View file

@ -6,14 +6,16 @@
#include <condition_variable> #include <condition_variable>
#include <filesystem> #include <filesystem>
#include <mutex> #include <mutex>
#include <thread>
#include <utility> #include <utility>
#include <fmt/format.h> #include <fmt/format.h>
#include <core/libraries/system/msgdialog_ui.h> #include <core/libraries/system/msgdialog_ui.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/elf_info.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/polyfill_thread.h" #include "common/path_util.h"
#include "common/singleton.h" #include "common/singleton.h"
#include "common/thread.h" #include "common/thread.h"
#include "core/file_sys/fs.h" #include "core/file_sys/fs.h"
@ -23,265 +25,202 @@ using Common::FS::IOFile;
namespace fs = std::filesystem; namespace fs = std::filesystem;
constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save
constexpr std::string_view DirnameSaveDataMemory = "sce_sdmemory"; constexpr std::string_view StandardDirnameSaveDataMemory = "sce_sdmemory";
constexpr std::string_view FilenameSaveDataMemory = "memory.dat"; constexpr std::string_view FilenameSaveDataMemory = "memory.dat";
constexpr std::string_view IconName = "icon0.png";
constexpr std::string_view CorruptFileName = "corrupted";
namespace Libraries::SaveData::SaveMemory { namespace Libraries::SaveData::SaveMemory {
static Core::FileSys::MntPoints* g_mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance(); static Core::FileSys::MntPoints* g_mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
static OrbisUserServiceUserId g_user_id{}; struct SlotData {
static std::string g_game_serial{}; OrbisUserServiceUserId user_id;
static std::filesystem::path g_save_path{}; std::string game_serial;
static std::filesystem::path g_param_sfo_path{}; std::filesystem::path folder_path;
static PSF g_param_sfo; PSF sfo;
std::vector<u8> memory_cache;
};
static bool g_save_memory_initialized = false; static std::mutex g_slot_mtx;
static std::mutex g_saving_memory_mutex; static std::unordered_map<u32, SlotData> g_attached_slots;
static std::vector<u8> g_save_memory;
static std::filesystem::path g_icon_path; void PersistMemory(u32 slot_id, bool lock) {
static std::vector<u8> g_icon_memory; std::unique_lock lck{g_slot_mtx, std::defer_lock};
if (lock) {
static std::condition_variable g_trigger_save_memory; lck.lock();
static std::atomic_bool g_saving_memory = false;
static std::atomic_bool g_save_event = false;
static std::jthread g_save_memory_thread;
static std::atomic_bool g_memory_dirty = false;
static std::atomic_bool g_param_dirty = false;
static std::atomic_bool g_icon_dirty = false;
static void SaveFileSafe(void* buf, size_t count, const std::filesystem::path& path) {
const auto& dir = path.parent_path();
const auto& name = path.filename();
const auto tmp_path = dir / (name.string() + ".tmp");
IOFile file(tmp_path, Common::FS::FileAccessMode::Write);
file.WriteRaw<u8>(buf, count);
file.Close();
fs::remove(path);
fs::rename(tmp_path, path);
}
[[noreturn]] void SaveThreadLoop() {
Common::SetCurrentThreadName("shadPS4:SaveData:SaveDataMemoryThread");
std::mutex mtx;
while (true) {
{
std::unique_lock lk{mtx};
g_trigger_save_memory.wait(lk);
} }
// Save the memory auto& data = g_attached_slots[slot_id];
g_saving_memory = true; auto memoryPath = data.folder_path / FilenameSaveDataMemory;
std::scoped_lock lk{g_saving_memory_mutex}; fs::create_directories(memoryPath.parent_path());
int n = 0;
std::string errMsg;
while (n++ < 10) {
try { try {
LOG_DEBUG(Lib_SaveData, "Saving save data memory {}", fmt::UTF(g_save_path.u8string())); IOFile f;
int r = f.Open(memoryPath, Common::FS::FileAccessMode::Write);
if (g_memory_dirty) { if (f.IsOpen()) {
g_memory_dirty = false; f.WriteRaw<u8>(data.memory_cache.data(), data.memory_cache.size());
SaveFileSafe(g_save_memory.data(), g_save_memory.size(), f.Close();
g_save_path / FilenameSaveDataMemory);
}
if (g_param_dirty) {
g_param_dirty = false;
static std::vector<u8> buf;
g_param_sfo.Encode(buf);
SaveFileSafe(buf.data(), buf.size(), g_param_sfo_path);
}
if (g_icon_dirty) {
g_icon_dirty = false;
SaveFileSafe(g_icon_memory.data(), g_icon_memory.size(), g_icon_path);
}
if (g_save_event) {
Backup::PushBackupEvent(Backup::BackupRequest{
.user_id = g_user_id,
.title_id = g_game_serial,
.dir_name = std::string{DirnameSaveDataMemory},
.origin = Backup::OrbisSaveDataEventType::SAVE_DATA_MEMORY_SYNC,
.save_path = g_save_path,
});
g_save_event = false;
}
} catch (const fs::filesystem_error& e) {
LOG_ERROR(Lib_SaveData, "Failed to save save data memory: {}", e.what());
MsgDialog::ShowMsgDialog(MsgDialog::MsgDialogState{
MsgDialog::MsgDialogState::UserState{
.type = MsgDialog::ButtonType::OK,
.msg = fmt::format("Failed to save save data memory.\nCode: <{}>\n{}",
e.code().message(), e.what()),
},
});
}
g_saving_memory = false;
}
}
void SetDirectories(OrbisUserServiceUserId user_id, std::string _game_serial) {
g_user_id = user_id;
g_game_serial = std::move(_game_serial);
g_save_path = SaveInstance::MakeDirSavePath(user_id, g_game_serial, DirnameSaveDataMemory);
g_param_sfo_path = SaveInstance::GetParamSFOPath(g_save_path);
g_param_sfo = PSF();
g_icon_path = g_save_path / sce_sys / "icon0.png";
}
const std::filesystem::path& GetSavePath() {
return g_save_path;
}
size_t CreateSaveMemory(size_t memory_size) {
size_t existed_size = 0;
static std::once_flag init_save_thread_flag;
std::call_once(init_save_thread_flag,
[] { g_save_memory_thread = std::jthread{SaveThreadLoop}; });
g_save_memory.resize(memory_size);
SaveInstance::SetupDefaultParamSFO(g_param_sfo, std::string{DirnameSaveDataMemory},
g_game_serial);
g_save_memory_initialized = true;
if (!fs::exists(g_param_sfo_path)) {
// Create save memory
fs::create_directories(g_save_path / sce_sys);
IOFile memory_file{g_save_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Write};
bool ok = memory_file.SetSize(memory_size);
if (!ok) {
LOG_ERROR(Lib_SaveData, "Failed to set memory size");
throw std::filesystem::filesystem_error(
"Failed to set save memory size", g_save_path / FilenameSaveDataMemory,
std::make_error_code(std::errc::no_space_on_device));
}
memory_file.Close();
} else {
// Load save memory
bool ok = g_param_sfo.Open(g_param_sfo_path);
if (!ok) {
LOG_ERROR(Lib_SaveData, "Failed to open SFO at {}",
fmt::UTF(g_param_sfo_path.u8string()));
throw std::filesystem::filesystem_error(
"failed to open SFO", g_param_sfo_path,
std::make_error_code(std::errc::illegal_byte_sequence));
}
IOFile memory_file{g_save_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Read};
if (!memory_file.IsOpen()) {
LOG_ERROR(Lib_SaveData, "Failed to open save memory");
throw std::filesystem::filesystem_error(
"failed to open save memory", g_save_path / FilenameSaveDataMemory,
std::make_error_code(std::errc::permission_denied));
}
size_t save_size = memory_file.GetSize();
existed_size = save_size;
memory_file.Seek(0);
memory_file.ReadRaw<u8>(g_save_memory.data(), std::min(save_size, memory_size));
memory_file.Close();
}
return existed_size;
}
void SetIcon(void* buf, size_t buf_size) {
if (buf == nullptr) {
const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png");
if (fs::exists(src_icon)) {
if (fs::exists(g_icon_path)) {
fs::remove(g_icon_path);
}
fs::copy_file(src_icon, g_icon_path);
}
if (fs::exists(g_icon_path)) {
IOFile file(g_icon_path, Common::FS::FileAccessMode::Read);
size_t size = file.GetSize();
file.Seek(0);
g_icon_memory.resize(size);
file.ReadRaw<u8>(g_icon_memory.data(), size);
file.Close();
}
} else {
g_icon_memory.resize(buf_size);
std::memcpy(g_icon_memory.data(), buf, buf_size);
IOFile file(g_icon_path, Common::FS::FileAccessMode::Write);
file.Seek(0);
file.WriteRaw<u8>(g_icon_memory.data(), buf_size);
file.Close();
}
}
void WriteIcon(void* buf, size_t buf_size) {
if (buf_size != g_icon_memory.size()) {
g_icon_memory.resize(buf_size);
}
std::memcpy(g_icon_memory.data(), buf, buf_size);
g_icon_dirty = true;
}
bool IsSaveMemoryInitialized() {
return g_save_memory_initialized;
}
PSF& GetParamSFO() {
return g_param_sfo;
}
std::span<u8> GetIcon() {
return {g_icon_memory};
}
void SaveSFO(bool sync) {
if (!sync) {
g_param_dirty = true;
return; return;
} }
const bool ok = g_param_sfo.Encode(g_param_sfo_path); const auto err = std::error_code{r, std::iostream_category()};
throw std::filesystem::filesystem_error{err.message(), err};
} catch (const std::filesystem::filesystem_error& e) {
errMsg = std::string{e.what()};
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
const MsgDialog::MsgDialogState dialog{MsgDialog::MsgDialogState::UserState{
.type = MsgDialog::ButtonType::OK,
.msg = "Failed to persist save memory:\n" + errMsg + "\nat " +
Common::FS::PathToUTF8String(memoryPath),
}};
MsgDialog::ShowMsgDialog(dialog);
}
std::string GetSaveDir(u32 slot_id) {
std::string dir(StandardDirnameSaveDataMemory);
if (slot_id > 0) {
dir += std::to_string(slot_id);
}
return dir;
}
std::filesystem::path GetSavePath(OrbisUserServiceUserId user_id, u32 slot_id,
std::string_view game_serial) {
std::string dir(StandardDirnameSaveDataMemory);
if (slot_id > 0) {
dir += std::to_string(slot_id);
}
return SaveInstance::MakeDirSavePath(user_id, Common::ElfInfo::Instance().GameSerial(), dir);
}
size_t SetupSaveMemory(OrbisUserServiceUserId user_id, u32 slot_id, std::string_view game_serial) {
std::lock_guard lck{g_slot_mtx};
const auto save_dir = GetSavePath(user_id, slot_id, game_serial);
auto& data = g_attached_slots[slot_id];
data = SlotData{
.user_id = user_id,
.game_serial = std::string{game_serial},
.folder_path = save_dir,
.sfo = {},
.memory_cache = {},
};
SaveInstance::SetupDefaultParamSFO(data.sfo, GetSaveDir(slot_id), std::string{game_serial});
auto param_sfo_path = SaveInstance::GetParamSFOPath(save_dir);
if (!fs::exists(param_sfo_path)) {
return 0;
}
if (!data.sfo.Open(param_sfo_path) || fs::exists(save_dir / CorruptFileName)) {
if (!Backup::Restore(save_dir)) { // Could not restore the backup
return 0;
}
}
const auto memory = save_dir / FilenameSaveDataMemory;
if (fs::exists(memory)) {
return fs::file_size(memory);
}
return 0;
}
void SetIcon(u32 slot_id, void* buf, size_t buf_size) {
std::lock_guard lck{g_slot_mtx};
const auto& data = g_attached_slots[slot_id];
const auto icon_path = data.folder_path / sce_sys / "icon0.png";
if (buf == nullptr) {
const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png");
if (fs::exists(icon_path)) {
fs::remove(icon_path);
}
if (fs::exists(src_icon)) {
fs::create_directories(icon_path.parent_path());
fs::copy_file(src_icon, icon_path);
}
} else {
IOFile file(icon_path, Common::FS::FileAccessMode::Write);
file.WriteRaw<u8>(buf, buf_size);
file.Close();
}
}
bool IsSaveMemoryInitialized(u32 slot_id) {
std::lock_guard lck{g_slot_mtx};
return g_attached_slots.contains(slot_id);
}
PSF& GetParamSFO(u32 slot_id) {
std::lock_guard lck{g_slot_mtx};
auto& data = g_attached_slots[slot_id];
return data.sfo;
}
std::vector<u8> GetIcon(u32 slot_id) {
std::lock_guard lck{g_slot_mtx};
auto& data = g_attached_slots[slot_id];
const auto icon_path = data.folder_path / sce_sys / "icon0.png";
IOFile f{icon_path, Common::FS::FileAccessMode::Read};
if (!f.IsOpen()) {
return {};
}
const u64 size = f.GetSize();
std::vector<u8> ret;
ret.resize(size);
f.ReadSpan(std::span{ret});
return ret;
}
void SaveSFO(u32 slot_id) {
std::lock_guard lck{g_slot_mtx};
const auto& data = g_attached_slots[slot_id];
const auto sfo_path = SaveInstance::GetParamSFOPath(data.folder_path);
fs::create_directories(sfo_path.parent_path());
const bool ok = data.sfo.Encode(sfo_path);
if (!ok) { if (!ok) {
LOG_ERROR(Lib_SaveData, "Failed to encode param.sfo"); LOG_ERROR(Lib_SaveData, "Failed to encode param.sfo");
throw std::filesystem::filesystem_error("Failed to write param.sfo", g_param_sfo_path, throw std::filesystem::filesystem_error("Failed to write param.sfo", sfo_path,
std::make_error_code(std::errc::permission_denied)); std::make_error_code(std::errc::permission_denied));
} }
} }
bool IsSaving() {
return g_saving_memory; void ReadMemory(u32 slot_id, void* buf, size_t buf_size, int64_t offset) {
std::lock_guard lk{g_slot_mtx};
auto& data = g_attached_slots[slot_id];
auto& memory = data.memory_cache;
if (memory.empty()) { // Load file
IOFile f{data.folder_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Read};
if (f.IsOpen()) {
memory.resize(f.GetSize());
f.Seek(0);
f.ReadSpan(std::span{memory});
}
}
s64 read_size = buf_size;
if (read_size + offset > memory.size()) {
read_size = memory.size() - offset;
}
std::memcpy(buf, memory.data() + offset, read_size);
} }
bool TriggerSaveWithoutEvent() { void WriteMemory(u32 slot_id, void* buf, size_t buf_size, int64_t offset) {
if (g_saving_memory) { std::lock_guard lk{g_slot_mtx};
return false; auto& data = g_attached_slots[slot_id];
auto& memory = data.memory_cache;
if (offset + buf_size > memory.size()) {
memory.resize(offset + buf_size);
} }
g_trigger_save_memory.notify_one(); std::memcpy(memory.data() + offset, buf, buf_size);
return true; PersistMemory(slot_id, false);
} Backup::NewRequest(data.user_id, data.game_serial, GetSaveDir(slot_id),
Backup::OrbisSaveDataEventType::__DO_NOT_SAVE);
bool TriggerSave() {
if (g_saving_memory) {
return false;
}
g_save_event = true;
g_trigger_save_memory.notify_one();
return true;
}
void ReadMemory(void* buf, size_t buf_size, int64_t offset) {
std::scoped_lock lk{g_saving_memory_mutex};
if (offset + buf_size > g_save_memory.size()) {
UNREACHABLE_MSG("ReadMemory out of bounds");
}
std::memcpy(buf, g_save_memory.data() + offset, buf_size);
}
void WriteMemory(void* buf, size_t buf_size, int64_t offset) {
std::scoped_lock lk{g_saving_memory_mutex};
if (offset + buf_size > g_save_memory.size()) {
g_save_memory.resize(offset + buf_size);
}
std::memcpy(g_save_memory.data() + offset, buf, buf_size);
g_memory_dirty = true;
} }
} // namespace Libraries::SaveData::SaveMemory } // namespace Libraries::SaveData::SaveMemory

View file

@ -3,7 +3,7 @@
#pragma once #pragma once
#include <span> #include <vector>
#include "save_backup.h" #include "save_backup.h"
class PSF; class PSF;
@ -14,36 +14,30 @@ using OrbisUserServiceUserId = s32;
namespace Libraries::SaveData::SaveMemory { namespace Libraries::SaveData::SaveMemory {
void SetDirectories(OrbisUserServiceUserId user_id, std::string game_serial); void PersistMemory(u32 slot_id, bool lock = true);
[[nodiscard]] const std::filesystem::path& GetSavePath(); [[nodiscard]] std::string GetSaveDir(u32 slot_id);
// returns the size of the existed save memory [[nodiscard]] std::filesystem::path GetSavePath(OrbisUserServiceUserId user_id, u32 slot_id,
size_t CreateSaveMemory(size_t memory_size); std::string_view game_serial);
// Initialize the icon. Set buf to null to read the standard icon. // returns the size of the save memory if exists
void SetIcon(void* buf, size_t buf_size); size_t SetupSaveMemory(OrbisUserServiceUserId user_id, u32 slot_id, std::string_view game_serial);
// Update the icon // Write the icon. Set buf to null to read the standard icon.
void WriteIcon(void* buf, size_t buf_size); void SetIcon(u32 slot_id, void* buf = nullptr, size_t buf_size = 0);
[[nodiscard]] bool IsSaveMemoryInitialized(); [[nodiscard]] bool IsSaveMemoryInitialized(u32 slot_id);
[[nodiscard]] PSF& GetParamSFO(); [[nodiscard]] PSF& GetParamSFO(u32 slot_id);
[[nodiscard]] std::span<u8> GetIcon(); [[nodiscard]] std::vector<u8> GetIcon(u32 slot_id);
// Save now or wait for the background thread to save // Save now or wait for the background thread to save
void SaveSFO(bool sync = false); void SaveSFO(u32 slot_id);
[[nodiscard]] bool IsSaving(); void ReadMemory(u32 slot_id, void* buf, size_t buf_size, int64_t offset);
bool TriggerSaveWithoutEvent(); void WriteMemory(u32 slot_id, void* buf, size_t buf_size, int64_t offset);
bool TriggerSave();
void ReadMemory(void* buf, size_t buf_size, int64_t offset);
void WriteMemory(void* buf, size_t buf_size, int64_t offset);
} // namespace Libraries::SaveData::SaveMemory } // namespace Libraries::SaveData::SaveMemory

View file

@ -177,7 +177,8 @@ struct OrbisSaveDataMemoryGet2 {
OrbisSaveDataMemoryData* data; OrbisSaveDataMemoryData* data;
OrbisSaveDataParam* param; OrbisSaveDataParam* param;
OrbisSaveDataIcon* icon; OrbisSaveDataIcon* icon;
std::array<u8, 32> _reserved; u32 slotId;
std::array<u8, 28> _reserved;
}; };
struct OrbisSaveDataMemorySet2 { struct OrbisSaveDataMemorySet2 {
@ -186,6 +187,8 @@ struct OrbisSaveDataMemorySet2 {
const OrbisSaveDataMemoryData* data; const OrbisSaveDataMemoryData* data;
const OrbisSaveDataParam* param; const OrbisSaveDataParam* param;
const OrbisSaveDataIcon* icon; const OrbisSaveDataIcon* icon;
u32 dataNum;
u32 slotId;
std::array<u8, 32> _reserved; std::array<u8, 32> _reserved;
}; };
@ -198,7 +201,8 @@ struct OrbisSaveDataMemorySetup2 {
const OrbisSaveDataParam* initParam; const OrbisSaveDataParam* initParam;
// +4.5 // +4.5
const OrbisSaveDataIcon* initIcon; const OrbisSaveDataIcon* initIcon;
std::array<u8, 24> _reserved; u32 slotId;
std::array<u8, 20> _reserved;
}; };
struct OrbisSaveDataMemorySetupResult { struct OrbisSaveDataMemorySetupResult {
@ -206,9 +210,16 @@ struct OrbisSaveDataMemorySetupResult {
std::array<u8, 16> _reserved; std::array<u8, 16> _reserved;
}; };
enum OrbisSaveDataMemorySyncOption : u32 {
NONE = 0,
BLOCKING = 1,
};
struct OrbisSaveDataMemorySync { struct OrbisSaveDataMemorySync {
OrbisUserServiceUserId userId; OrbisUserServiceUserId userId;
std::array<u8, 36> _reserved; u32 slotId;
OrbisSaveDataMemorySyncOption option;
std::array<u8, 28> _reserved;
}; };
struct OrbisSaveDataMount2 { struct OrbisSaveDataMount2 {
@ -327,6 +338,7 @@ static void initialize() {
g_initialized = true; g_initialized = true;
g_game_serial = ElfInfo::Instance().GameSerial(); g_game_serial = ElfInfo::Instance().GameSerial();
g_fw_ver = ElfInfo::Instance().FirmwareVer(); g_fw_ver = ElfInfo::Instance().FirmwareVer();
Backup::StartThread();
} }
// game_00other | game*other // game_00other | game*other
@ -558,7 +570,6 @@ Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup) {
} }
} }
Backup::StartThread();
Backup::NewRequest(backup->userId, title, dir_name, OrbisSaveDataEventType::BACKUP); Backup::NewRequest(backup->userId, title, dir_name, OrbisSaveDataEventType::BACKUP);
return Error::OK; return Error::OK;
@ -1136,22 +1147,27 @@ Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getPar
LOG_INFO(Lib_SaveData, "called with invalid parameter"); LOG_INFO(Lib_SaveData, "called with invalid parameter");
return Error::PARAMETER; return Error::PARAMETER;
} }
if (!SaveMemory::IsSaveMemoryInitialized()) {
u32 slot_id = 0;
if (g_fw_ver > ElfInfo::FW_50) {
slot_id = getParam->slotId;
}
if (!SaveMemory::IsSaveMemoryInitialized(slot_id)) {
LOG_INFO(Lib_SaveData, "called without save memory initialized"); LOG_INFO(Lib_SaveData, "called without save memory initialized");
return Error::MEMORY_NOT_READY; return Error::MEMORY_NOT_READY;
} }
LOG_DEBUG(Lib_SaveData, "called"); LOG_DEBUG(Lib_SaveData, "called");
auto data = getParam->data; auto data = getParam->data;
if (data != nullptr) { if (data != nullptr) {
SaveMemory::ReadMemory(data->buf, data->bufSize, data->offset); SaveMemory::ReadMemory(slot_id, data->buf, data->bufSize, data->offset);
} }
auto param = getParam->param; auto param = getParam->param;
if (param != nullptr) { if (param != nullptr) {
param->FromSFO(SaveMemory::GetParamSFO()); param->FromSFO(SaveMemory::GetParamSFO(slot_id));
} }
auto icon = getParam->icon; auto icon = getParam->icon;
if (icon != nullptr) { if (icon != nullptr) {
auto icon_mem = SaveMemory::GetIcon(); auto icon_mem = SaveMemory::GetIcon(slot_id);
size_t total = std::min(icon->bufSize, icon_mem.size()); size_t total = std::min(icon->bufSize, icon_mem.size());
std::memcpy(icon->buf, icon_mem.data(), total); std::memcpy(icon->buf, icon_mem.data(), total);
icon->dataSize = total; icon->dataSize = total;
@ -1494,36 +1510,37 @@ Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2*
LOG_INFO(Lib_SaveData, "called with invalid parameter"); LOG_INFO(Lib_SaveData, "called with invalid parameter");
return Error::PARAMETER; return Error::PARAMETER;
} }
if (!SaveMemory::IsSaveMemoryInitialized()) { u32 slot_id = 0;
u32 data_num = 1;
if (g_fw_ver > ElfInfo::FW_50) {
slot_id = setParam->slotId;
if (setParam->dataNum > 1) {
data_num = setParam->dataNum;
}
}
if (!SaveMemory::IsSaveMemoryInitialized(slot_id)) {
LOG_INFO(Lib_SaveData, "called without save memory initialized"); LOG_INFO(Lib_SaveData, "called without save memory initialized");
return Error::MEMORY_NOT_READY; return Error::MEMORY_NOT_READY;
} }
if (SaveMemory::IsSaving()) {
int count = 0;
while (++count < 100 && SaveMemory::IsSaving()) { // try for more 10 seconds
std::this_thread::sleep_for(chrono::milliseconds(100));
}
if (SaveMemory::IsSaving()) {
LOG_TRACE(Lib_SaveData, "called while saving");
return Error::BUSY_FOR_SAVING;
}
}
LOG_DEBUG(Lib_SaveData, "called"); LOG_DEBUG(Lib_SaveData, "called");
auto data = setParam->data; auto data = setParam->data;
if (data != nullptr) { if (data != nullptr) {
SaveMemory::WriteMemory(data->buf, data->bufSize, data->offset); for (int i = 0; i < data_num; i++) {
SaveMemory::WriteMemory(slot_id, data[i].buf, data[i].bufSize, data[i].offset);
}
} }
auto param = setParam->param; auto param = setParam->param;
if (param != nullptr) { if (param != nullptr) {
param->ToSFO(SaveMemory::GetParamSFO()); param->ToSFO(SaveMemory::GetParamSFO(slot_id));
SaveMemory::SaveSFO(); SaveMemory::SaveSFO(slot_id);
} }
auto icon = setParam->icon;
if (icon != nullptr) { auto icon = setParam->icon;
SaveMemory::WriteIcon(icon->buf, icon->bufSize); if (icon != nullptr) {
SaveMemory::SetIcon(slot_id, icon->buf, icon->bufSize);
} }
SaveMemory::TriggerSaveWithoutEvent();
return Error::OK; return Error::OK;
} }
@ -1563,9 +1580,12 @@ Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetu
} }
LOG_DEBUG(Lib_SaveData, "called"); LOG_DEBUG(Lib_SaveData, "called");
SaveMemory::SetDirectories(setupParam->userId, g_game_serial); u32 slot_id = 0;
if (g_fw_ver > ElfInfo::FW_50) {
slot_id = setupParam->slotId;
}
const auto& save_path = SaveMemory::GetSavePath(); const auto& save_path = SaveMemory::GetSavePath(setupParam->userId, slot_id, g_game_serial);
for (const auto& instance : g_mount_slots) { for (const auto& instance : g_mount_slots) {
if (instance.has_value() && instance->GetSavePath() == save_path) { if (instance.has_value() && instance->GetSavePath() == save_path) {
return Error::BUSY; return Error::BUSY;
@ -1573,21 +1593,21 @@ Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetu
} }
try { try {
size_t existed_size = SaveMemory::CreateSaveMemory(setupParam->memorySize); size_t existed_size =
SaveMemory::SetupSaveMemory(setupParam->userId, slot_id, g_game_serial);
if (existed_size == 0) { // Just created if (existed_size == 0) { // Just created
if (g_fw_ver >= ElfInfo::FW_45 && setupParam->initParam != nullptr) { if (g_fw_ver >= ElfInfo::FW_45 && setupParam->initParam != nullptr) {
auto& sfo = SaveMemory::GetParamSFO(); auto& sfo = SaveMemory::GetParamSFO(slot_id);
setupParam->initParam->ToSFO(sfo); setupParam->initParam->ToSFO(sfo);
} }
SaveMemory::SaveSFO(); SaveMemory::SaveSFO(slot_id);
auto init_icon = setupParam->initIcon; auto init_icon = setupParam->initIcon;
if (g_fw_ver >= ElfInfo::FW_45 && init_icon != nullptr) { if (g_fw_ver >= ElfInfo::FW_45 && init_icon != nullptr) {
SaveMemory::SetIcon(init_icon->buf, init_icon->bufSize); SaveMemory::SetIcon(slot_id, init_icon->buf, init_icon->bufSize);
} else { } else {
SaveMemory::SetIcon(nullptr, 0); SaveMemory::SetIcon(slot_id);
} }
SaveMemory::TriggerSaveWithoutEvent();
} }
if (g_fw_ver >= ElfInfo::FW_45 && result != nullptr) { if (g_fw_ver >= ElfInfo::FW_45 && result != nullptr) {
result->existedMemorySize = existed_size; result->existedMemorySize = existed_size;
@ -1631,15 +1651,23 @@ Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncPa
LOG_INFO(Lib_SaveData, "called with invalid parameter"); LOG_INFO(Lib_SaveData, "called with invalid parameter");
return Error::PARAMETER; return Error::PARAMETER;
} }
if (!SaveMemory::IsSaveMemoryInitialized()) {
u32 slot_id = 0;
if (g_fw_ver > ElfInfo::FW_50) {
slot_id = syncParam->slotId;
}
if (!SaveMemory::IsSaveMemoryInitialized(slot_id)) {
LOG_INFO(Lib_SaveData, "called without save memory initialized"); LOG_INFO(Lib_SaveData, "called without save memory initialized");
return Error::MEMORY_NOT_READY; return Error::MEMORY_NOT_READY;
} }
LOG_DEBUG(Lib_SaveData, "called"); LOG_DEBUG(Lib_SaveData, "called");
bool ok = SaveMemory::TriggerSave();
if (!ok) { SaveMemory::PersistMemory(slot_id);
return Error::BUSY_FOR_SAVING; const auto& save_path = SaveMemory::GetSaveDir(slot_id);
} Backup::NewRequest(syncParam->userId, g_game_serial, save_path,
OrbisSaveDataEventType::SAVE_DATA_MEMORY_SYNC);
return Error::OK; return Error::OK;
} }

View file

@ -33,6 +33,7 @@
#include "core/libraries/ngs2/ngs2.h" #include "core/libraries/ngs2/ngs2.h"
#include "core/libraries/np_trophy/np_trophy.h" #include "core/libraries/np_trophy/np_trophy.h"
#include "core/libraries/rtc/rtc.h" #include "core/libraries/rtc/rtc.h"
#include "core/libraries/save_data/save_backup.h"
#include "core/linker.h" #include "core/linker.h"
#include "core/memory.h" #include "core/memory.h"
#include "emulator.h" #include "emulator.h"
@ -271,7 +272,7 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector<std::str
UpdatePlayTime(id); UpdatePlayTime(id);
#endif #endif
std::exit(0); std::quick_exit(0);
} }
void Emulator::LoadSystemModules(const std::string& game_serial) { void Emulator::LoadSystemModules(const std::string& game_serial) {