SavaData: SaveDataMemory features

This commit is contained in:
Vinicius Rangel 2024-09-12 06:30:48 -03:00
parent de0a38db0a
commit 0e61607f2e
No known key found for this signature in database
GPG key ID: A5B154D904B761D9
13 changed files with 669 additions and 142 deletions

View file

@ -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

View file

@ -135,7 +135,11 @@ bool PSF::Encode(const std::filesystem::path& filepath) const {
std::vector<u8> PSF::Encode() const {
std::vector<u8> psf_buffer;
Encode(psf_buffer);
return psf_buffer;
}
void PSF::Encode(std::vector<u8>& psf_buffer) const {
psf_buffer.resize(sizeof(PSFHeader) + sizeof(PSFRawEntry) * entry_list.size());
{
@ -197,8 +201,6 @@ std::vector<u8> 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<std::span<const u8>> PSF::GetBinary(std::string_view key) const {

View file

@ -60,6 +60,7 @@ public:
bool Open(const std::vector<u8>& psf_buffer);
[[nodiscard]] std::vector<u8> Encode() const;
void Encode(std::vector<u8>& buf) const;
bool Encode(const std::filesystem::path& filepath) const;
std::optional<std::span<const u8>> GetBinary(std::string_view key) const;

View file

@ -188,6 +188,14 @@ std::optional<BackupRequest> 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<float>(g_backup_progress) / 100.0f;
}

View file

@ -53,6 +53,8 @@ std::filesystem::path MakeBackupPath(const std::filesystem::path& save_path);
std::optional<BackupRequest> PopLastEvent();
void PushBackupEvent(BackupRequest&& req);
float GetProgress();
void ClearProgress();

View file

@ -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) {

View file

@ -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();
};

View file

@ -0,0 +1,287 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "save_memory.h"
#include <condition_variable>
#include <filesystem>
#include <mutex>
#include <utility>
#include <fmt/format.h>
#include <core/libraries/system/msgdialog_ui.h>
#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<Core::FileSys::MntPoints>::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<u8> g_save_memory;
static std::filesystem::path g_icon_path;
static std::vector<u8> 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<u8>(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<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 {}", 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<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)) {
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<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::Append);
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;
}
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

View file

@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#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<u8> 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

View file

@ -4,6 +4,7 @@
#include <span>
#include <vector>
#include <core/libraries/system/msgdialog_ui.h>
#include <magic_enum.hpp>
#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<chrono::seconds>(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<s32>(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<u8, 4> _pad;
OrbisSaveDataMemoryData* data;
OrbisSaveDataParam* param;
OrbisSaveDataIcon* icon;
std::array<u8, 32> _reserved;
};
struct OrbisSaveDataMemorySet2 {
OrbisUserServiceUserId userId;
std::array<u8, 4> _pad;
const OrbisSaveDataMemoryData* data;
const OrbisSaveDataParam* param;
const OrbisSaveDataIcon* icon;
std::array<u8, 32> _reserved;
};
struct OrbisSaveDataMemorySetup2 {
OrbisSaveDataSaveDataMemoryOption option;
OrbisUserServiceUserId userId;
size_t memorySize;
size_t iconMemorySize;
const OrbisSaveDataParam* initParam;
const OrbisSaveDataIcon* initIcon;
std::array<u8, 24> _reserved;
};
struct OrbisSaveDataMemorySetupResult {
size_t existedMemorySize;
std::array<u8, 16> _reserved;
};
struct OrbisSaveDataMemorySync {
OrbisUserServiceUserId userId;
std::array<u8, 36> _reserved;
};
struct OrbisSaveDataMount2 {
OrbisUserServiceUserId userId;
s32 : 32;
@ -282,8 +344,6 @@ struct OrbisSaveDataEvent {
std::array<u8, 40> _reserved;
};
bool is_rw_mode = false;
static bool g_initialized = false;
static std::string g_game_serial;
static std::array<std::optional<SaveInstance>, 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<u8>(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(&param);
}
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<const OrbisSaveDataParam*>(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<s32>(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<const u32*>(paramBuf);
const auto value = static_cast<const s32*>(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() {

View file

@ -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);

View file

@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
#include <imgui.h>
#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<MsgDialogState::UserState>();
@ -269,4 +286,16 @@ void MsgDialogUi::Draw() {
End();
first_render = false;
}
}
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;
}

View file

@ -3,6 +3,7 @@
#pragma once
#include <string>
#include <variant>
#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