diff --git a/src/common/elf_info.h b/src/common/elf_info.h index cb32679bbec..d885709cdd1 100644 --- a/src/common/elf_info.h +++ b/src/common/elf_info.h @@ -80,6 +80,7 @@ class ElfInfo { static constexpr u32 FW_40 = 0x4000000; static constexpr u32 FW_45 = 0x4500000; static constexpr u32 FW_50 = 0x5000000; + static constexpr u32 FW_55 = 0x5500000; static constexpr u32 FW_80 = 0x8000000; static ElfInfo& Instance() { diff --git a/src/core/libraries/save_data/save_backup.cpp b/src/core/libraries/save_data/save_backup.cpp index 5261cdb11f2..f85845f70ca 100644 --- a/src/core/libraries/save_data/save_backup.cpp +++ b/src/core/libraries/save_data/save_backup.cpp @@ -121,15 +121,17 @@ static void BackupThreadBody() { std::scoped_lock lk{g_backup_queue_mutex}; 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}; g_backup_queue.pop_front(); - g_result_queue.push_back(std::move(req)); - if (g_result_queue.size() > 20) { - g_result_queue.pop_front(); + if (req.origin != OrbisSaveDataEventType::__DO_NOT_SAVE) { + g_result_queue.push_back(std::move(req)); + if (g_result_queue.size() > 20) { + g_result_queue.pop_front(); + } } } + std::this_thread::sleep_for(std::chrono::seconds(5)); // Don't backup too often } g_backup_status = WorkerStatus::NotStarted; } @@ -141,6 +143,15 @@ void StartThread() { LOG_DEBUG(Lib_SaveData, "Starting backup thread"); g_backup_status = WorkerStatus::Waiting; 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() { @@ -148,12 +159,12 @@ void StopThread() { return; } LOG_DEBUG(Lib_SaveData, "Stopping backup thread"); + g_backup_status = WorkerStatus::Stopping; { 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, diff --git a/src/core/libraries/save_data/save_backup.h b/src/core/libraries/save_data/save_backup.h index e49c69f6073..83a263c9b34 100644 --- a/src/core/libraries/save_data/save_backup.h +++ b/src/core/libraries/save_data/save_backup.h @@ -25,6 +25,8 @@ enum class OrbisSaveDataEventType : u32 { UMOUNT_BACKUP = 1, BACKUP = 2, SAVE_DATA_MEMORY_SYNC = 3, + + __DO_NOT_SAVE = 1000000, // This value is only for the backup thread }; struct BackupRequest { diff --git a/src/core/libraries/save_data/save_instance.cpp b/src/core/libraries/save_data/save_instance.cpp index c2b7dca3c00..26708d2d6f1 100644 --- a/src/core/libraries/save_data/save_instance.cpp +++ b/src/core/libraries/save_data/save_instance.cpp @@ -10,6 +10,7 @@ #include "common/path_util.h" #include "common/singleton.h" #include "core/file_sys/fs.h" +#include "save_backup.h" #include "save_instance.h" constexpr auto OrbisSaveDataBlocksMin2 = 96; // 3MiB @@ -45,14 +46,13 @@ static const std::unordered_map default_title = { namespace Libraries::SaveData { -std::filesystem::path SaveInstance::MakeTitleSavePath(OrbisUserServiceUserId user_id, - std::string_view game_serial) { +fs::path SaveInstance::MakeTitleSavePath(OrbisUserServiceUserId user_id, + std::string_view game_serial) { return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial; } -std::filesystem::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_id, - std::string_view game_serial, - std::string_view dir_name) { +fs::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_id, std::string_view game_serial, + std::string_view 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(); } -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"; } @@ -129,7 +129,6 @@ SaveInstance& SaveInstance::operator=(SaveInstance&& other) noexcept { save_path = std::move(other.save_path); 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); param_sfo = std::move(other.param_sfo); mount_point = std::move(other.mount_point); max_blocks = other.max_blocks; @@ -142,7 +141,8 @@ SaveInstance& SaveInstance::operator=(SaveInstance&& other) noexcept { 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) { 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; } else { + std::optional err; 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)); + err = fs::filesystem_error("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)); } - if (!param_sfo.Open(param_sfo_path)) { - throw std::filesystem::filesystem_error( - "Failed to read param.sfo", param_sfo_path, - std::make_error_code(std::errc::illegal_byte_sequence)); + if (err.has_value()) { + if (dont_restore_backup) { + throw err.value(); + } + if (Backup::Restore(save_path)) { + return SetupAndMount(read_only, copy_icon, ignore_corrupt, true); + } } } 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( - "Failed to open corrupted file", corrupt_file_path, - std::make_error_code(std::errc::illegal_byte_sequence)); - } + Common::FS::IOFile f(corrupt_file_path, Common::FS::FileAccessMode::Write); + f.Close(); } max_blocks = static_cast(GetMaxBlockFromSFO(param_sfo)); @@ -197,12 +199,11 @@ void SaveInstance::Umount() { mounted = false; const bool ok = param_sfo.Encode(param_sfo_path); if (!ok) { - throw std::filesystem::filesystem_error("Failed to write param.sfo", param_sfo_path, - std::make_error_code(std::errc::permission_denied)); + throw fs::filesystem_error("Failed to write param.sfo", param_sfo_path, + std::make_error_code(std::errc::permission_denied)); } param_sfo = PSF(); - corrupt_file.Close(); fs::remove(corrupt_file_path); g_mnt->Unmount(save_path, mount_point); } @@ -216,8 +217,8 @@ void SaveInstance::CreateFiles() { const bool ok = param_sfo.Encode(param_sfo_path); if (!ok) { - throw std::filesystem::filesystem_error("Failed to write param.sfo", param_sfo_path, - std::make_error_code(std::errc::permission_denied)); + throw fs::filesystem_error("Failed to write param.sfo", param_sfo_path, + std::make_error_code(std::errc::permission_denied)); } } diff --git a/src/core/libraries/save_data/save_instance.h b/src/core/libraries/save_data/save_instance.h index 3be5c45957b..6e7ac8f66ff 100644 --- a/src/core/libraries/save_data/save_instance.h +++ b/src/core/libraries/save_data/save_instance.h @@ -42,8 +42,6 @@ class SaveInstance { std::filesystem::path param_sfo_path; std::filesystem::path corrupt_file_path; - Common::FS::IOFile corrupt_file; - PSF param_sfo; std::string mount_point; @@ -80,7 +78,8 @@ class SaveInstance { SaveInstance& operator=(const SaveInstance& other) = delete; 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(); diff --git a/src/core/libraries/save_data/save_memory.cpp b/src/core/libraries/save_data/save_memory.cpp index 84179bc27dc..13e122c6033 100644 --- a/src/core/libraries/save_data/save_memory.cpp +++ b/src/core/libraries/save_data/save_memory.cpp @@ -6,14 +6,16 @@ #include #include #include +#include #include #include #include #include "common/assert.h" +#include "common/elf_info.h" #include "common/logging/log.h" -#include "common/polyfill_thread.h" +#include "common/path_util.h" #include "common/singleton.h" #include "common/thread.h" #include "core/file_sys/fs.h" @@ -23,265 +25,202 @@ using Common::FS::IOFile; namespace fs = std::filesystem; 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 IconName = "icon0.png"; +constexpr std::string_view CorruptFileName = "corrupted"; namespace Libraries::SaveData::SaveMemory { static Core::FileSys::MntPoints* g_mnt = Common::Singleton::Instance(); -static OrbisUserServiceUserId g_user_id{}; -static std::string g_game_serial{}; -static std::filesystem::path g_save_path{}; -static std::filesystem::path g_param_sfo_path{}; -static PSF g_param_sfo; - -static bool g_save_memory_initialized = false; -static std::mutex g_saving_memory_mutex; -static std::vector g_save_memory; - -static std::filesystem::path g_icon_path; -static std::vector g_icon_memory; - -static std::condition_variable g_trigger_save_memory; -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(buf, count); - file.Close(); - - fs::remove(path); - fs::rename(tmp_path, path); -} +struct SlotData { + OrbisUserServiceUserId user_id; + std::string game_serial; + std::filesystem::path folder_path; + PSF sfo; + std::vector memory_cache; +}; + +static std::mutex g_slot_mtx; +static std::unordered_map g_attached_slots; + +void PersistMemory(u32 slot_id, bool lock) { + std::unique_lock lck{g_slot_mtx, std::defer_lock}; + if (lock) { + lck.lock(); + } + auto& data = g_attached_slots[slot_id]; + auto memoryPath = data.folder_path / FilenameSaveDataMemory; + fs::create_directories(memoryPath.parent_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 - g_saving_memory = true; - std::scoped_lock lk{g_saving_memory_mutex}; + int n = 0; + std::string errMsg; + while (n++ < 10) { try { - LOG_DEBUG(Lib_SaveData, "Saving save data memory {}", fmt::UTF(g_save_path.u8string())); - - if (g_memory_dirty) { - g_memory_dirty = false; - SaveFileSafe(g_save_memory.data(), g_save_memory.size(), - g_save_path / FilenameSaveDataMemory); - } - if (g_param_dirty) { - g_param_dirty = false; - static std::vector 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; + IOFile f; + int r = f.Open(memoryPath, Common::FS::FileAccessMode::Write); + if (f.IsOpen()) { + f.WriteRaw(data.memory_cache.data(), data.memory_cache.size()); + f.Close(); + return; } - } 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()), - }, - }); + 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)); } - g_saving_memory = false; } + 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; } -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; +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 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}; }); +size_t SetupSaveMemory(OrbisUserServiceUserId user_id, u32 slot_id, std::string_view game_serial) { + std::lock_guard lck{g_slot_mtx}; - g_save_memory.resize(memory_size); - SaveInstance::SetupDefaultParamSFO(g_param_sfo, std::string{DirnameSaveDataMemory}, - g_game_serial); + const auto save_dir = GetSavePath(user_id, slot_id, game_serial); - g_save_memory_initialized = true; + 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 = {}, + }; - if (!fs::exists(g_param_sfo_path)) { - // Create save memory - fs::create_directories(g_save_path / sce_sys); + SaveInstance::SetupDefaultParamSFO(data.sfo, GetSaveDir(slot_id), std::string{game_serial}); - 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 + auto param_sfo_path = SaveInstance::GetParamSFOPath(save_dir); + if (!fs::exists(param_sfo_path)) { + return 0; + } - 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)); + if (!data.sfo.Open(param_sfo_path) || fs::exists(save_dir / CorruptFileName)) { + if (!Backup::Restore(save_dir)) { // Could not restore the backup + return 0; } + } - 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(g_save_memory.data(), std::min(save_size, memory_size)); - memory_file.Close(); + const auto memory = save_dir / FilenameSaveDataMemory; + if (fs::exists(memory)) { + return fs::file_size(memory); } - return existed_size; + return 0; } -void SetIcon(void* buf, size_t buf_size) { +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(src_icon)) { - if (fs::exists(g_icon_path)) { - fs::remove(g_icon_path); - } - fs::copy_file(src_icon, g_icon_path); + if (fs::exists(icon_path)) { + fs::remove(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(g_icon_memory.data(), size); - file.Close(); + if (fs::exists(src_icon)) { + fs::create_directories(icon_path.parent_path()); + fs::copy_file(src_icon, icon_path); } } 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(g_icon_memory.data(), buf_size); + IOFile file(icon_path, Common::FS::FileAccessMode::Write); + file.WriteRaw(buf, 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; +bool IsSaveMemoryInitialized(u32 slot_id) { + std::lock_guard lck{g_slot_mtx}; + return g_attached_slots.contains(slot_id); } -std::span GetIcon() { - return {g_icon_memory}; +PSF& GetParamSFO(u32 slot_id) { + std::lock_guard lck{g_slot_mtx}; + auto& data = g_attached_slots[slot_id]; + return data.sfo; } -void SaveSFO(bool sync) { - if (!sync) { - g_param_dirty = true; - return; +std::vector 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 bool ok = g_param_sfo.Encode(g_param_sfo_path); + const u64 size = f.GetSize(); + std::vector 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) { 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)); } } -bool IsSaving() { - return g_saving_memory; -} - -bool TriggerSaveWithoutEvent() { - if (g_saving_memory) { - return false; - } - g_trigger_save_memory.notify_one(); - return true; -} -bool TriggerSave() { - if (g_saving_memory) { - return false; +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}); + } } - 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"); + s64 read_size = buf_size; + if (read_size + offset > memory.size()) { + read_size = memory.size() - offset; } - std::memcpy(buf, g_save_memory.data() + offset, buf_size); + std::memcpy(buf, memory.data() + offset, read_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); +void WriteMemory(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 (offset + buf_size > memory.size()) { + memory.resize(offset + buf_size); } - std::memcpy(g_save_memory.data() + offset, buf, buf_size); - g_memory_dirty = true; + std::memcpy(memory.data() + offset, buf, buf_size); + PersistMemory(slot_id, false); + Backup::NewRequest(data.user_id, data.game_serial, GetSaveDir(slot_id), + Backup::OrbisSaveDataEventType::__DO_NOT_SAVE); } } // namespace Libraries::SaveData::SaveMemory \ No newline at end of file diff --git a/src/core/libraries/save_data/save_memory.h b/src/core/libraries/save_data/save_memory.h index 04eeaa65260..68186563455 100644 --- a/src/core/libraries/save_data/save_memory.h +++ b/src/core/libraries/save_data/save_memory.h @@ -3,7 +3,7 @@ #pragma once -#include +#include #include "save_backup.h" class PSF; @@ -14,36 +14,30 @@ using OrbisUserServiceUserId = s32; 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 -size_t CreateSaveMemory(size_t memory_size); +[[nodiscard]] std::filesystem::path GetSavePath(OrbisUserServiceUserId user_id, u32 slot_id, + std::string_view game_serial); -// Initialize the icon. Set buf to null to read the standard icon. -void SetIcon(void* buf, size_t buf_size); +// returns the size of the save memory if exists +size_t SetupSaveMemory(OrbisUserServiceUserId user_id, u32 slot_id, std::string_view game_serial); -// Update the icon -void WriteIcon(void* buf, size_t buf_size); +// Write the icon. Set buf to null to read the standard icon. +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 GetIcon(); +[[nodiscard]] std::vector GetIcon(u32 slot_id); // 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(); - -bool TriggerSave(); - -void ReadMemory(void* buf, size_t buf_size, int64_t offset); - -void WriteMemory(void* buf, size_t buf_size, int64_t offset); +void WriteMemory(u32 slot_id, void* buf, size_t buf_size, int64_t offset); } // namespace Libraries::SaveData::SaveMemory \ No newline at end of file diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index b573ded1ea8..e9ad77d693f 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -177,7 +177,8 @@ struct OrbisSaveDataMemoryGet2 { OrbisSaveDataMemoryData* data; OrbisSaveDataParam* param; OrbisSaveDataIcon* icon; - std::array _reserved; + u32 slotId; + std::array _reserved; }; struct OrbisSaveDataMemorySet2 { @@ -186,6 +187,8 @@ struct OrbisSaveDataMemorySet2 { const OrbisSaveDataMemoryData* data; const OrbisSaveDataParam* param; const OrbisSaveDataIcon* icon; + u32 dataNum; + u32 slotId; std::array _reserved; }; @@ -198,7 +201,8 @@ struct OrbisSaveDataMemorySetup2 { const OrbisSaveDataParam* initParam; // +4.5 const OrbisSaveDataIcon* initIcon; - std::array _reserved; + u32 slotId; + std::array _reserved; }; struct OrbisSaveDataMemorySetupResult { @@ -206,9 +210,16 @@ struct OrbisSaveDataMemorySetupResult { std::array _reserved; }; +enum OrbisSaveDataMemorySyncOption : u32 { + NONE = 0, + BLOCKING = 1, +}; + struct OrbisSaveDataMemorySync { OrbisUserServiceUserId userId; - std::array _reserved; + u32 slotId; + OrbisSaveDataMemorySyncOption option; + std::array _reserved; }; struct OrbisSaveDataMount2 { @@ -327,6 +338,7 @@ static void initialize() { g_initialized = true; g_game_serial = ElfInfo::Instance().GameSerial(); g_fw_ver = ElfInfo::Instance().FirmwareVer(); + Backup::StartThread(); } // 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); return Error::OK; @@ -1136,22 +1147,27 @@ Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getPar LOG_INFO(Lib_SaveData, "called with invalid 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"); return Error::MEMORY_NOT_READY; } LOG_DEBUG(Lib_SaveData, "called"); auto data = getParam->data; 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; if (param != nullptr) { - param->FromSFO(SaveMemory::GetParamSFO()); + param->FromSFO(SaveMemory::GetParamSFO(slot_id)); } auto icon = getParam->icon; 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()); std::memcpy(icon->buf, icon_mem.data(), total); icon->dataSize = total; @@ -1494,36 +1510,37 @@ Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* LOG_INFO(Lib_SaveData, "called with invalid 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"); 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"); auto data = setParam->data; 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; if (param != nullptr) { - param->ToSFO(SaveMemory::GetParamSFO()); - SaveMemory::SaveSFO(); + param->ToSFO(SaveMemory::GetParamSFO(slot_id)); + SaveMemory::SaveSFO(slot_id); } + auto icon = setParam->icon; if (icon != nullptr) { - SaveMemory::WriteIcon(icon->buf, icon->bufSize); + SaveMemory::SetIcon(slot_id, icon->buf, icon->bufSize); } - SaveMemory::TriggerSaveWithoutEvent(); return Error::OK; } @@ -1563,9 +1580,12 @@ Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetu } 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) { if (instance.has_value() && instance->GetSavePath() == save_path) { return Error::BUSY; @@ -1573,21 +1593,21 @@ Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetu } 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 (g_fw_ver >= ElfInfo::FW_45 && setupParam->initParam != nullptr) { - auto& sfo = SaveMemory::GetParamSFO(); + auto& sfo = SaveMemory::GetParamSFO(slot_id); setupParam->initParam->ToSFO(sfo); } - SaveMemory::SaveSFO(); + SaveMemory::SaveSFO(slot_id); auto init_icon = setupParam->initIcon; 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 { - SaveMemory::SetIcon(nullptr, 0); + SaveMemory::SetIcon(slot_id); } - SaveMemory::TriggerSaveWithoutEvent(); } if (g_fw_ver >= ElfInfo::FW_45 && result != nullptr) { result->existedMemorySize = existed_size; @@ -1631,15 +1651,23 @@ Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncPa LOG_INFO(Lib_SaveData, "called with invalid 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"); return Error::MEMORY_NOT_READY; } LOG_DEBUG(Lib_SaveData, "called"); - bool ok = SaveMemory::TriggerSave(); - if (!ok) { - return Error::BUSY_FOR_SAVING; - } + + SaveMemory::PersistMemory(slot_id); + 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; } diff --git a/src/emulator.cpp b/src/emulator.cpp index e77c2b87f59..702b116c7f5 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -33,6 +33,7 @@ #include "core/libraries/ngs2/ngs2.h" #include "core/libraries/np_trophy/np_trophy.h" #include "core/libraries/rtc/rtc.h" +#include "core/libraries/save_data/save_backup.h" #include "core/linker.h" #include "core/memory.h" #include "emulator.h" @@ -259,7 +260,7 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector