diff --git a/CMakeLists.txt b/CMakeLists.txt index b4f6c71ef..46659d3d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -236,6 +236,8 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.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/save_memory.cpp + src/core/libraries/save_data/save_memory.h src/core/libraries/save_data/savedata.cpp src/core/libraries/save_data/savedata.h src/core/libraries/system/savedatadialog.cpp diff --git a/src/core/file_format/psf.cpp b/src/core/file_format/psf.cpp index 48802ca8c..17e4a1398 100644 --- a/src/core/file_format/psf.cpp +++ b/src/core/file_format/psf.cpp @@ -135,7 +135,11 @@ bool PSF::Encode(const std::filesystem::path& filepath) const { std::vector PSF::Encode() const { std::vector psf_buffer; + Encode(psf_buffer); + return psf_buffer; +} +void PSF::Encode(std::vector& psf_buffer) const { psf_buffer.resize(sizeof(PSFHeader) + sizeof(PSFRawEntry) * entry_list.size()); { @@ -197,8 +201,6 @@ std::vector PSF::Encode() const { ASSERT_MSG(additional_padding >= 0, "PSF entry max size mismatch"); std::ranges::fill_n(std::back_inserter(psf_buffer), additional_padding, 0); } - - return psf_buffer; } std::optional> PSF::GetBinary(std::string_view key) const { diff --git a/src/core/file_format/psf.h b/src/core/file_format/psf.h index 373b66c7e..6a14f58e5 100644 --- a/src/core/file_format/psf.h +++ b/src/core/file_format/psf.h @@ -60,6 +60,7 @@ public: bool Open(const std::vector& psf_buffer); [[nodiscard]] std::vector Encode() const; + void Encode(std::vector& buf) const; bool Encode(const std::filesystem::path& filepath) const; std::optional> GetBinary(std::string_view key) const; diff --git a/src/core/libraries/save_data/save_backup.cpp b/src/core/libraries/save_data/save_backup.cpp index 60e4858ea..93af373a8 100644 --- a/src/core/libraries/save_data/save_backup.cpp +++ b/src/core/libraries/save_data/save_backup.cpp @@ -188,6 +188,14 @@ std::optional PopLastEvent() { return req; } +void PushBackupEvent(BackupRequest&& req) { + std::scoped_lock lk{g_backup_queue_mutex}; + g_result_queue.push_back(std::move(req)); + if (g_result_queue.size() > 20) { + g_result_queue.pop_front(); + } +} + float GetProgress() { return static_cast(g_backup_progress) / 100.0f; } diff --git a/src/core/libraries/save_data/save_backup.h b/src/core/libraries/save_data/save_backup.h index bcf9f5ab1..f0aef3696 100644 --- a/src/core/libraries/save_data/save_backup.h +++ b/src/core/libraries/save_data/save_backup.h @@ -53,6 +53,8 @@ std::filesystem::path MakeBackupPath(const std::filesystem::path& save_path); std::optional PopLastEvent(); +void PushBackupEvent(BackupRequest&& req); + float GetProgress(); void ClearProgress(); diff --git a/src/core/libraries/save_data/save_instance.cpp b/src/core/libraries/save_data/save_instance.cpp index 18ba2d771..5278ac6fb 100644 --- a/src/core/libraries/save_data/save_instance.cpp +++ b/src/core/libraries/save_data/save_instance.cpp @@ -57,6 +57,7 @@ std::filesystem::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_ return Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / std::to_string(user_id) / game_serial / dir_name; } + int SaveInstance::GetMaxBlocks(const std::filesystem::path& save_path) { Common::FS::IOFile max_blocks_file{save_path / sce_sys / max_block_file_name, Common::FS::FileAccessMode::Read}; @@ -75,6 +76,25 @@ std::filesystem::path SaveInstance::GetParamSFOPath(const std::filesystem::path& return dir_path / sce_sys / "param.sfo"; } +void SaveInstance::SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name, + std::string game_serial) { + std::string locale = Config::getEmulatorLanguage(); + if (!default_title.contains(locale)) { + locale = "en"; + } + +#define P(type, key, ...) param_sfo.Add##type(std::string{key}, __VA_ARGS__) + // TODO Link with user service + P(Binary, SaveParams::ACCOUNT_ID, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + P(String, SaveParams::MAINTITLE, default_title.at(locale)); + P(String, SaveParams::SUBTITLE, ""); + P(String, SaveParams::DETAIL, ""); + P(String, SaveParams::SAVEDATA_DIRECTORY, std::move(dir_name)); + P(Integer, SaveParams::SAVEDATA_LIST_PARAM, 0); + P(String, SaveParams::TITLE_ID, std::move(game_serial)); +#undef P +} + SaveInstance::SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::string _game_serial, std::string_view _dir_name, int max_blocks) : slot_num(slot_num), user_id(user_id), game_serial(std::move(_game_serial)), @@ -190,21 +210,7 @@ void SaveInstance::CreateFiles() { const auto sce_sys_dir = save_path / sce_sys; fs::create_directories(sce_sys_dir); - std::string locale = Config::getEmulatorLanguage(); - if (!default_title.contains(locale)) { - locale = "en"; - } - -#define P(type, key, ...) param_sfo.Add##type(std::string{key}, __VA_ARGS__) - // TODO Link with user service - P(Binary, SaveParams::ACCOUNT_ID, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); - P(String, SaveParams::MAINTITLE, default_title.at(locale)); - P(String, SaveParams::SUBTITLE, ""); - P(String, SaveParams::DETAIL, ""); - P(String, SaveParams::SAVEDATA_DIRECTORY, dir_name); - P(Integer, SaveParams::SAVEDATA_LIST_PARAM, 0); - P(String, SaveParams::TITLE_ID, game_serial); -#undef P + SetupDefaultParamSFO(param_sfo, dir_name, game_serial); const bool ok = param_sfo.Encode(param_sfo_path); if (!ok) { diff --git a/src/core/libraries/save_data/save_instance.h b/src/core/libraries/save_data/save_instance.h index fc6edb59e..b70c3a1ed 100644 --- a/src/core/libraries/save_data/save_instance.h +++ b/src/core/libraries/save_data/save_instance.h @@ -67,6 +67,8 @@ public: // Get param.sfo path from a dir_path generated by MakeDirSavePath static std::filesystem::path GetParamSFOPath(const std::filesystem::path& dir_path); + static void SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name, std::string game_serial); + explicit SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::string game_serial, std::string_view dir_name, int max_blocks = 0); @@ -126,7 +128,6 @@ public: return read_only; } -private: void CreateFiles(); }; diff --git a/src/core/libraries/save_data/save_memory.cpp b/src/core/libraries/save_data/save_memory.cpp new file mode 100644 index 000000000..6a685ee39 --- /dev/null +++ b/src/core/libraries/save_data/save_memory.cpp @@ -0,0 +1,287 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "save_memory.h" + +#include +#include +#include +#include +#include + +#include + +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/polyfill_thread.h" +#include "common/singleton.h" +#include "common/thread.h" +#include "core/file_sys/fs.h" +#include "save_instance.h" + +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 FilenameSaveDataMemory = "memory.dat"; + +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); +} + +[[noreturn]] void SaveThreadLoop() { + Common::SetCurrentThreadName("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}; + try { + LOG_DEBUG(Lib_SaveData, "Saving save data memory {}", g_save_path.string()); + + 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; + } + } 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 {}", g_param_sfo_path.string()); + 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(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)) { + fs::copy_file(src_icon, 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(); + } else { + g_icon_memory.resize(buf_size); + std::memcpy(g_icon_memory.data(), buf, buf_size); + IOFile file(g_icon_path, Common::FS::FileAccessMode::Append); + file.Seek(0); + file.WriteRaw(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 GetIcon() { + return {g_icon_memory}; +} + +void SaveSFO(bool sync) { + if (!sync) { + g_param_dirty = true; + return; + } + const bool ok = g_param_sfo.Encode(g_param_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, + 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; + } + 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 > g_save_memory.size()) { + UNREACHABLE_MSG("ReadMemory out of bounds"); + } + 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 > g_save_memory.size()) { + UNREACHABLE_MSG("WriteMemory out of bounds"); + } + if (offset + buf_size > g_save_memory.size()) { + UNREACHABLE_MSG("WriteMemory out of bounds"); + } + std::memcpy(g_save_memory.data() + offset, buf, buf_size); + g_memory_dirty = true; +} + +} // 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 new file mode 100644 index 000000000..04eeaa652 --- /dev/null +++ b/src/core/libraries/save_data/save_memory.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "save_backup.h" + +class PSF; + +namespace Libraries::SaveData { +using OrbisUserServiceUserId = s32; +} + +namespace Libraries::SaveData::SaveMemory { + +void SetDirectories(OrbisUserServiceUserId user_id, std::string game_serial); + +[[nodiscard]] const std::filesystem::path& GetSavePath(); + +// returns the size of the existed save memory +size_t CreateSaveMemory(size_t memory_size); + +// Initialize the icon. Set buf to null to read the standard icon. +void SetIcon(void* buf, size_t buf_size); + +// Update the icon +void WriteIcon(void* buf, size_t buf_size); + +[[nodiscard]] bool IsSaveMemoryInitialized(); + +[[nodiscard]] PSF& GetParamSFO(); + +[[nodiscard]] std::span GetIcon(); + +// Save now or wait for the background thread to save +void SaveSFO(bool sync = false); + +[[nodiscard]] bool IsSaving(); + +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); + +} // 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 7b639ff40..b5b5f278a 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "common/assert.h" @@ -18,8 +19,10 @@ #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/save_data/savedata.h" +#include "core/libraries/system/msgdialog.h" #include "save_backup.h" #include "save_instance.h" +#include "save_memory.h" namespace fs = std::filesystem; namespace chrono = std::chrono; @@ -43,7 +46,16 @@ enum class Error : u32 { BAD_MOUNTED = 0x809F000D, BROKEN = 0x809F000F, INVALID_LOGIN_USER = 0x809F0011, + MEMORY_NOT_READY = 0x809F0012, BACKUP_BUSY = 0x809F0013, + BUSY_FOR_SAVING = 0x809F0016, +}; + +enum class OrbisSaveDataSaveDataMemoryOption : u32 { + NONE = 0, + SET_PARAM = 1 << 0, + DOUBLE_BUFFER = 1 << 1, + UNLOCK_LIMITATIONS = 1 << 2, }; using OrbisUserServiceUserId = s32; @@ -64,9 +76,6 @@ constexpr size_t OrbisSaveDataSubtitleMaxsize = 128; // Maximum subtitle name 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"; - enum class OrbisSaveDataMountMode : u32 { RDONLY = 1 << 0, RDWR = 1 << 1, @@ -131,6 +140,14 @@ struct OrbisSaveDataParam { const auto time_since_epoch = sfo.GetLastWrite().time_since_epoch(); mtime = chrono::duration_cast(time_since_epoch).count(); } + + void ToSFO(PSF& sfo) const { + sfo.AddString(std::string{SaveParams::MAINTITLE}, std::string{title}, true); + sfo.AddString(std::string{SaveParams::SUBTITLE}, std::string{subTitle}, true); + sfo.AddString(std::string{SaveParams::DETAIL}, std::string{detail}, true); + sfo.AddInteger(std::string{SaveParams::SAVEDATA_LIST_PARAM}, static_cast(userParam), + true); + } }; struct OrbisSaveDataFingerprint { @@ -187,6 +204,51 @@ struct OrbisSaveDataIcon { } }; +struct OrbisSaveDataMemoryData { + void* buf; + size_t bufSize; + s64 offset; + u8 _reserved[40]; +}; + +struct OrbisSaveDataMemoryGet2 { + OrbisUserServiceUserId userId; + std::array _pad; + OrbisSaveDataMemoryData* data; + OrbisSaveDataParam* param; + OrbisSaveDataIcon* icon; + std::array _reserved; +}; + +struct OrbisSaveDataMemorySet2 { + OrbisUserServiceUserId userId; + std::array _pad; + const OrbisSaveDataMemoryData* data; + const OrbisSaveDataParam* param; + const OrbisSaveDataIcon* icon; + std::array _reserved; +}; + +struct OrbisSaveDataMemorySetup2 { + OrbisSaveDataSaveDataMemoryOption option; + OrbisUserServiceUserId userId; + size_t memorySize; + size_t iconMemorySize; + const OrbisSaveDataParam* initParam; + const OrbisSaveDataIcon* initIcon; + std::array _reserved; +}; + +struct OrbisSaveDataMemorySetupResult { + size_t existedMemorySize; + std::array _reserved; +}; + +struct OrbisSaveDataMemorySync { + OrbisUserServiceUserId userId; + std::array _reserved; +}; + struct OrbisSaveDataMount2 { OrbisUserServiceUserId userId; s32 : 32; @@ -282,8 +344,6 @@ struct OrbisSaveDataEvent { std::array _reserved; }; -bool is_rw_mode = false; - static bool g_initialized = false; static std::string g_game_serial; static std::array, 16> g_mount_slots; @@ -402,6 +462,9 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info, LOG_ERROR(Lib_SaveData, "Corrupted save data"); return Error::BROKEN; } + if (e.code() == std::errc::no_space_on_device) { + return Error::NO_SPACE_FS; + } LOG_ERROR(Lib_SaveData, "Failed to mount save data: {}", e.what()); return Error::INTERNAL; } @@ -871,7 +934,7 @@ Error PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam*, LOG_INFO(Lib_SaveData, "called with invalid parameter"); return Error::PARAMETER; } - LOG_DEBUG(Lib_SaveData, "called"); + LOG_TRACE(Lib_SaveData, "called"); auto last_event = Backup::PopLastEvent(); if (!last_event.has_value()) { @@ -1017,25 +1080,56 @@ int PS4_SYSV_ABI sceSaveDataGetSaveDataCount() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const u32 userId, void* buf, const size_t bufSize, - const int64_t offset) { - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(userId) / g_game_serial / "sdmemory/save_mem1.sav"; - - Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Read); - if (!file.IsOpen()) { - return false; - } - file.Seek(offset); - size_t nbytes = file.ReadRaw(buf, bufSize); - LOG_INFO(Lib_SaveData, "called: bufSize = {}, offset = {}", bufSize, offset, nbytes); - - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const OrbisUserServiceUserId userId, void* buf, + const size_t bufSize, const int64_t offset) { + LOG_DEBUG(Lib_SaveData, "Redirecting to sceSaveDataGetSaveDataMemory2"); + OrbisSaveDataMemoryData data{}; + data.buf = buf; + data.bufSize = bufSize; + data.offset = offset; + OrbisSaveDataMemoryGet2 param{}; + param.userId = userId; + param.data = &data; + param.param = nullptr; + param.icon = nullptr; + return sceSaveDataGetSaveDataMemory2(¶m); } -int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(/*OrbisSaveDataMemoryGet2* getParam*/) { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; + } + if (getParam == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + if (!SaveMemory::IsSaveMemoryInitialized()) { + LOG_INFO(Lib_SaveData, "called without save memory initialized"); + return Error::MEMORY_NOT_READY; + } + if (SaveMemory::IsSaving()) { + LOG_TRACE(Lib_SaveData, "called while saving"); + return Error::BUSY_FOR_SAVING; + } + LOG_DEBUG(Lib_SaveData, "called"); + auto data = getParam->data; + if (data != nullptr) { + SaveMemory::ReadMemory(data->buf, data->bufSize, data->offset); + } + auto param = getParam->param; + if (param != nullptr) { + param->FromSFO(SaveMemory::GetParamSFO()); + } + auto icon = getParam->icon; + if (icon != nullptr) { + auto icon_mem = SaveMemory::GetIcon(); + size_t total = std::min(icon->bufSize, icon_mem.size()); + std::memcpy(icon->buf, icon_mem.data(), total); + icon->dataSize = total; + } + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataGetSaveDataRootDir() { @@ -1317,13 +1411,7 @@ Error PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint case OrbisSaveDataParamType::ALL: { const auto param = static_cast(paramBuf); ASSERT(paramBufSize == sizeof(OrbisSaveDataParam)); - - param_sfo->AddString(std::string{SaveParams::MAINTITLE}, std::string{param->title}, true); - param_sfo->AddString(std::string{SaveParams::SUBTITLE}, std::string{param->subTitle}, true); - param_sfo->AddString(std::string{SaveParams::DETAIL}, std::string{param->detail}, true); - param_sfo->AddInteger(std::string{SaveParams::SAVEDATA_LIST_PARAM}, - static_cast(param->userParam), true); - + param->ToSFO(*param_sfo); return Error::OK; } break; case OrbisSaveDataParamType::TITLE: { @@ -1339,7 +1427,7 @@ Error PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint param_sfo->AddString(std::string{SaveParams::DETAIL}, {value}, true); } break; case OrbisSaveDataParamType::USER_PARAM: { - const auto value = static_cast(paramBuf); + const auto value = static_cast(paramBuf); param_sfo->AddInteger(std::string{SaveParams::SAVEDATA_LIST_PARAM}, *value, true); } break; default: @@ -1354,15 +1442,53 @@ int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(const u32 userId, const void* buf, - const size_t bufSize, const int64_t offset) { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf, + size_t bufSize, int64_t offset) { + LOG_DEBUG(Lib_SaveData, "Redirecting to sceSaveDataSetSaveDataMemory2"); + OrbisSaveDataMemoryData data{}; + data.buf = buf; + data.bufSize = bufSize; + data.offset = offset; + OrbisSaveDataMemorySet2 setParam{}; + setParam.userId = userId; + setParam.data = &data; + return sceSaveDataSetSaveDataMemory2(&setParam); } -int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(/*const OrbisSaveDataMemorySet2* setParam*/) { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; + } + if (setParam == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + if (!SaveMemory::IsSaveMemoryInitialized()) { + LOG_INFO(Lib_SaveData, "called without save memory initialized"); + return Error::MEMORY_NOT_READY; + } + 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); + } + auto param = setParam->param; + if (param != nullptr) { + param->ToSFO(SaveMemory::GetParamSFO()); + SaveMemory::SaveSFO(); + } + auto icon = setParam->icon; + if (icon != nullptr) { + SaveMemory::WriteIcon(icon->buf, icon->bufSize); + } + + SaveMemory::TriggerSaveWithoutEvent(); + return Error::OK; } int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(/*u32 userId, size_t memorySize, @@ -1371,10 +1497,59 @@ int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(/*u32 userId, size_t memorySize, return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(/*const OrbisSaveDataMemorySetup2* setupParam, - OrbisSaveDataMemorySetupResult* result*/) { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam, + OrbisSaveDataMemorySetupResult* result) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; + } + if (setupParam == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called"); + + SaveMemory::SetDirectories(setupParam->userId, g_game_serial); + + const auto& save_path = SaveMemory::GetSavePath(); + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetSavePath() == save_path) { + return Error::BUSY; + } + } + + try { + size_t existed_size = SaveMemory::CreateSaveMemory(setupParam->memorySize); + if (existed_size == 0) { // Just created + if (setupParam->initParam != nullptr) { + auto& sfo = SaveMemory::GetParamSFO(); + setupParam->initParam->ToSFO(sfo); + } + SaveMemory::SaveSFO(); + + auto init_icon = setupParam->initIcon; + if (init_icon != nullptr) { + SaveMemory::SetIcon(init_icon->buf, init_icon->bufSize); + } else { + SaveMemory::SetIcon(nullptr, 0); + } + } + if (result != nullptr) { + result->existedMemorySize = existed_size; + } + } catch (const fs::filesystem_error& e) { + LOG_ERROR(Lib_SaveData, "Failed to create/load save memory: {}", e.what()); + + const MsgDialog::MsgDialogState dialog{MsgDialog::MsgDialogState::UserState{ + .type = MsgDialog::ButtonType::OK, + .msg = "Failed to create or load save memory:\n" + std::string{e.what()}, + }}; + MsgDialog::ShowMsgDialog(dialog); + + return Error::INTERNAL; + } + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataShutdownStart() { @@ -1392,9 +1567,25 @@ int PS4_SYSV_ABI sceSaveDataSyncCloudList() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(/*OrbisSaveDataMemorySync* syncParam*/) { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return Error::NOT_INITIALIZED; + } + if (syncParam == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + if (!SaveMemory::IsSaveMemoryInitialized()) { + 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; + } + return Error::OK; } Error PS4_SYSV_ABI sceSaveDataTerminate() { diff --git a/src/core/libraries/save_data/savedata.h b/src/core/libraries/save_data/savedata.h index 83fc4ac2d..b23da04c8 100644 --- a/src/core/libraries/save_data/savedata.h +++ b/src/core/libraries/save_data/savedata.h @@ -13,6 +13,8 @@ namespace Libraries::SaveData { enum class Error : u32; enum class OrbisSaveDataParamType : u32; +using OrbisUserServiceUserId = s32; + struct OrbisSaveDataBackup; struct OrbisSaveDataCheckBackupData; struct OrbisSaveDataDelete; @@ -21,6 +23,11 @@ struct OrbisSaveDataDirNameSearchResult; struct OrbisSaveDataEvent; struct OrbisSaveDataEventParam; struct OrbisSaveDataIcon; +struct OrbisSaveDataMemoryGet2; +struct OrbisSaveDataMemorySet2; +struct OrbisSaveDataMemorySetup2; +struct OrbisSaveDataMemorySetupResult; +struct OrbisSaveDataMemorySync; struct OrbisSaveDataMount2; struct OrbisSaveDataMount; struct OrbisSaveDataMountInfo; @@ -28,74 +35,6 @@ struct OrbisSaveDataMountPoint; struct OrbisSaveDataMountResult; struct OrbisSaveDataRestoreBackupData; -/*typedef u32 OrbisSaveDataSaveDataMemoryOption; -#define ORBIS_SAVE_DATA_MEMORY_OPTION_NONE (0x00000000) -#define ORBIS_SAVE_DATA_MEMORY_OPTION_SET_PARAM (0x00000001 << 0) -#define ORBIS_SAVE_DATA_MEMORY_OPTION_DOUBLE_BUFFER (0x00000001 << 1) - -struct OrbisSaveDataMemorySetup2 { - OrbisSaveDataSaveDataMemoryOption option; - s32 userId; - size_t memorySize; - size_t iconMemorySize; - const OrbisSaveDataParam* initParam; - const OrbisSaveDataIcon* initIcon; - u32 slotId; - u8 reserved[20]; -}; - -struct OrbisSaveDataMemorySetupResult { - size_t existedMemorySize; - u8 reserved[16]; -}; - -struct OrbisSaveDataMemoryData { - void* buf; - size_t bufSize; - off_t offset; - u8 reserved[40]; -}; - -struct OrbisSaveDataMemoryGet2 { - s32 userId; - u8 padding[4]; - OrbisSaveDataMemoryData* data; - OrbisSaveDataParam* param; - OrbisSaveDataIcon* icon; - u32 slotId; - u8 reserved[28]; -}; - -struct OrbisSaveDataMemorySet2 { - s32 userId; - u8 padding[4]; - const OrbisSaveDataMemoryData* data; - const OrbisSaveDataParam* param; - const OrbisSaveDataIcon* icon; - u32 dataNum; - u8 slotId; - u8 reserved[24]; -}; - -typedef u32 OrbisSaveDataMemorySyncOption; - -#define SCE_SAVE_DATA_MEMORY_SYNC_OPTION_NONE (0x00000000) -#define SCE_SAVE_DATA_MEMORY_SYNC_OPTION_BLOCKING (0x00000001 << 0) - -struct OrbisSaveDataMemorySync { - s32 userId; - u32 slotId; - OrbisSaveDataMemorySyncOption option; - u8 reserved[28]; -}; - -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_ALL = 0; -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_TITLE = 1; -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_SUB_TITLE = 2; -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_DETAIL = 3; -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_USER_PARAM = 4; -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_MTIME = 5;*/ - int PS4_SYSV_ABI sceSaveDataAbort(); Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup); int PS4_SYSV_ABI sceSaveDataBindPsnAccount(); @@ -150,9 +89,9 @@ Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint size_t paramBufSize, size_t* gotSize); 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); -int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(/*OrbisSaveDataMemoryGet2* getParam*/); +Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(OrbisUserServiceUserId userId, void* buf, + size_t bufSize, int64_t offset); +Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam); int PS4_SYSV_ABI sceSaveDataGetSaveDataRootDir(); int PS4_SYSV_ABI sceSaveDataGetSaveDataRootPath(); int PS4_SYSV_ABI sceSaveDataGetSaveDataRootUsbPath(); @@ -187,17 +126,17 @@ Error PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint OrbisSaveDataParamType paramType, const void* paramBuf, size_t paramBufSize); int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser(); -int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(const u32 userId, const void* buf, - const size_t bufSize, const int64_t offset); -int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(/*const OrbisSaveDataMemorySet2* setParam*/); +Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf, + size_t bufSize, int64_t offset); +Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam); int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(/*u32 userId, size_t memorySize, - OrbisSaveDataParam* param*/); -int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(/*const OrbisSaveDataMemorySetup2* setupParam, - OrbisSaveDataMemorySetupResult* result*/); + OrbisSaveDataParam* param*/); +Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam, + OrbisSaveDataMemorySetupResult* result); int PS4_SYSV_ABI sceSaveDataShutdownStart(); int PS4_SYSV_ABI sceSaveDataSupportedFakeBrokenStatus(); int PS4_SYSV_ABI sceSaveDataSyncCloudList(); -int PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(/*OrbisSaveDataMemorySync* syncParam*/); +Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam); Error PS4_SYSV_ABI sceSaveDataTerminate(); int PS4_SYSV_ABI sceSaveDataTransferringMount(); Error PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint); diff --git a/src/core/libraries/system/msgdialog_ui.cpp b/src/core/libraries/system/msgdialog_ui.cpp index 63b3390c9..eb0ee9098 100644 --- a/src/core/libraries/system/msgdialog_ui.cpp +++ b/src/core/libraries/system/msgdialog_ui.cpp @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + #include #include "common/assert.h" #include "imgui/imgui_std.h" @@ -81,6 +83,21 @@ MsgDialogState::MsgDialogState(const OrbisParam& param) { } } +MsgDialogState::MsgDialogState(UserState mode) { + this->mode = MsgDialogMode::USER_MSG; + this->state = mode; +} + +MsgDialogState::MsgDialogState(ProgressState mode) { + this->mode = MsgDialogMode::PROGRESS_BAR; + this->state = mode; +} + +MsgDialogState::MsgDialogState(SystemState mode) { + this->mode = MsgDialogMode::SYSTEM_MSG; + this->state = mode; +} + void MsgDialogUi::DrawUser() { const auto& [button_type, msg, btn_param1, btn_param2] = state->GetState(); @@ -269,4 +286,16 @@ void MsgDialogUi::Draw() { End(); first_render = false; -} \ No newline at end of file +} + +DialogResult Libraries::MsgDialog::ShowMsgDialog(MsgDialogState state, bool block) { + DialogResult result{}; + Status status = Status::RUNNING; + MsgDialogUi dialog(&state, &status, &result); + if (block) { + while (status == Status::RUNNING) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + return result; +} diff --git a/src/core/libraries/system/msgdialog_ui.h b/src/core/libraries/system/msgdialog_ui.h index 845abdc43..f67424869 100644 --- a/src/core/libraries/system/msgdialog_ui.h +++ b/src/core/libraries/system/msgdialog_ui.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include "common/fixed_value.h" @@ -129,6 +130,11 @@ private: public: explicit MsgDialogState(const OrbisParam& param); + + explicit MsgDialogState(UserState mode); + explicit MsgDialogState(ProgressState mode); + explicit MsgDialogState(SystemState mode); + MsgDialogState() = default; [[nodiscard]] OrbisUserServiceUserId GetUserId() const { @@ -174,4 +180,8 @@ public: } }; +// Utility function to show a message dialog +// !!! This function can block !!! +DialogResult ShowMsgDialog(MsgDialogState state, bool block = true); + }; // namespace Libraries::MsgDialog \ No newline at end of file