mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-05-24 12:25:00 +00:00
Savefixes vii (#2279)
* savedata: rewrite save memory functions to handle slot id & better backup management * savedata: auto-restore backup if needed * safe save backup shutdown replaced exit by quick_exit
This commit is contained in:
parent
929e15260d
commit
0358271b93
9 changed files with 285 additions and 309 deletions
|
@ -80,6 +80,7 @@ public:
|
|||
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() {
|
||||
|
|
|
@ -121,16 +121,18 @@ 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();
|
||||
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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,13 +46,12 @@ static const std::unordered_map<std::string, std::string> default_title = {
|
|||
|
||||
namespace Libraries::SaveData {
|
||||
|
||||
std::filesystem::path SaveInstance::MakeTitleSavePath(OrbisUserServiceUserId user_id,
|
||||
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,
|
||||
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<fs::filesystem_error> err;
|
||||
if (!ignore_corrupt && fs::exists(corrupt_file_path)) {
|
||||
throw std::filesystem::filesystem_error(
|
||||
"Corrupted save data", corrupt_file_path,
|
||||
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<int>(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,
|
||||
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,7 +217,7 @@ 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,
|
||||
throw fs::filesystem_error("Failed to write param.sfo", param_sfo_path,
|
||||
std::make_error_code(std::errc::permission_denied));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 @@ public:
|
|||
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();
|
||||
|
||||
|
|
|
@ -6,14 +6,16 @@
|
|||
#include <condition_variable>
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <core/libraries/system/msgdialog_ui.h>
|
||||
|
||||
#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<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;
|
||||
struct SlotData {
|
||||
OrbisUserServiceUserId user_id;
|
||||
std::string game_serial;
|
||||
std::filesystem::path folder_path;
|
||||
PSF sfo;
|
||||
std::vector<u8> memory_cache;
|
||||
};
|
||||
|
||||
static bool g_save_memory_initialized = false;
|
||||
static std::mutex g_saving_memory_mutex;
|
||||
static std::vector<u8> g_save_memory;
|
||||
static std::mutex g_slot_mtx;
|
||||
static std::unordered_map<u32, SlotData> g_attached_slots;
|
||||
|
||||
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("shadPS4:SaveData:SaveDataMemoryThread");
|
||||
std::mutex mtx;
|
||||
while (true) {
|
||||
{
|
||||
std::unique_lock lk{mtx};
|
||||
g_trigger_save_memory.wait(lk);
|
||||
void PersistMemory(u32 slot_id, bool lock) {
|
||||
std::unique_lock lck{g_slot_mtx, std::defer_lock};
|
||||
if (lock) {
|
||||
lck.lock();
|
||||
}
|
||||
// Save the memory
|
||||
g_saving_memory = true;
|
||||
std::scoped_lock lk{g_saving_memory_mutex};
|
||||
auto& data = g_attached_slots[slot_id];
|
||||
auto memoryPath = data.folder_path / FilenameSaveDataMemory;
|
||||
fs::create_directories(memoryPath.parent_path());
|
||||
|
||||
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<u8> buf;
|
||||
g_param_sfo.Encode(buf);
|
||||
SaveFileSafe(buf.data(), buf.size(), g_param_sfo_path);
|
||||
}
|
||||
if (g_icon_dirty) {
|
||||
g_icon_dirty = false;
|
||||
SaveFileSafe(g_icon_memory.data(), g_icon_memory.size(), g_icon_path);
|
||||
}
|
||||
|
||||
if (g_save_event) {
|
||||
Backup::PushBackupEvent(Backup::BackupRequest{
|
||||
.user_id = g_user_id,
|
||||
.title_id = g_game_serial,
|
||||
.dir_name = std::string{DirnameSaveDataMemory},
|
||||
.origin = Backup::OrbisSaveDataEventType::SAVE_DATA_MEMORY_SYNC,
|
||||
.save_path = g_save_path,
|
||||
});
|
||||
g_save_event = false;
|
||||
}
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
LOG_ERROR(Lib_SaveData, "Failed to save save data memory: {}", e.what());
|
||||
MsgDialog::ShowMsgDialog(MsgDialog::MsgDialogState{
|
||||
MsgDialog::MsgDialogState::UserState{
|
||||
.type = MsgDialog::ButtonType::OK,
|
||||
.msg = fmt::format("Failed to save save data memory.\nCode: <{}>\n{}",
|
||||
e.code().message(), e.what()),
|
||||
},
|
||||
});
|
||||
}
|
||||
g_saving_memory = false;
|
||||
}
|
||||
}
|
||||
|
||||
void SetDirectories(OrbisUserServiceUserId user_id, std::string _game_serial) {
|
||||
g_user_id = user_id;
|
||||
g_game_serial = std::move(_game_serial);
|
||||
g_save_path = SaveInstance::MakeDirSavePath(user_id, g_game_serial, DirnameSaveDataMemory);
|
||||
g_param_sfo_path = SaveInstance::GetParamSFOPath(g_save_path);
|
||||
g_param_sfo = PSF();
|
||||
g_icon_path = g_save_path / sce_sys / "icon0.png";
|
||||
}
|
||||
|
||||
const std::filesystem::path& GetSavePath() {
|
||||
return g_save_path;
|
||||
}
|
||||
|
||||
size_t CreateSaveMemory(size_t memory_size) {
|
||||
size_t existed_size = 0;
|
||||
|
||||
static std::once_flag init_save_thread_flag;
|
||||
std::call_once(init_save_thread_flag,
|
||||
[] { g_save_memory_thread = std::jthread{SaveThreadLoop}; });
|
||||
|
||||
g_save_memory.resize(memory_size);
|
||||
SaveInstance::SetupDefaultParamSFO(g_param_sfo, std::string{DirnameSaveDataMemory},
|
||||
g_game_serial);
|
||||
|
||||
g_save_memory_initialized = true;
|
||||
|
||||
if (!fs::exists(g_param_sfo_path)) {
|
||||
// Create save memory
|
||||
fs::create_directories(g_save_path / sce_sys);
|
||||
|
||||
IOFile memory_file{g_save_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Write};
|
||||
bool ok = memory_file.SetSize(memory_size);
|
||||
if (!ok) {
|
||||
LOG_ERROR(Lib_SaveData, "Failed to set memory size");
|
||||
throw std::filesystem::filesystem_error(
|
||||
"Failed to set save memory size", g_save_path / FilenameSaveDataMemory,
|
||||
std::make_error_code(std::errc::no_space_on_device));
|
||||
}
|
||||
memory_file.Close();
|
||||
} else {
|
||||
// Load save memory
|
||||
|
||||
bool ok = g_param_sfo.Open(g_param_sfo_path);
|
||||
if (!ok) {
|
||||
LOG_ERROR(Lib_SaveData, "Failed to open SFO at {}",
|
||||
fmt::UTF(g_param_sfo_path.u8string()));
|
||||
throw std::filesystem::filesystem_error(
|
||||
"failed to open SFO", g_param_sfo_path,
|
||||
std::make_error_code(std::errc::illegal_byte_sequence));
|
||||
}
|
||||
|
||||
IOFile memory_file{g_save_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Read};
|
||||
if (!memory_file.IsOpen()) {
|
||||
LOG_ERROR(Lib_SaveData, "Failed to open save memory");
|
||||
throw std::filesystem::filesystem_error(
|
||||
"failed to open save memory", g_save_path / FilenameSaveDataMemory,
|
||||
std::make_error_code(std::errc::permission_denied));
|
||||
}
|
||||
size_t save_size = memory_file.GetSize();
|
||||
existed_size = save_size;
|
||||
memory_file.Seek(0);
|
||||
memory_file.ReadRaw<u8>(g_save_memory.data(), std::min(save_size, memory_size));
|
||||
memory_file.Close();
|
||||
}
|
||||
|
||||
return existed_size;
|
||||
}
|
||||
|
||||
void SetIcon(void* buf, size_t buf_size) {
|
||||
if (buf == nullptr) {
|
||||
const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png");
|
||||
if (fs::exists(src_icon)) {
|
||||
if (fs::exists(g_icon_path)) {
|
||||
fs::remove(g_icon_path);
|
||||
}
|
||||
fs::copy_file(src_icon, g_icon_path);
|
||||
}
|
||||
if (fs::exists(g_icon_path)) {
|
||||
IOFile file(g_icon_path, Common::FS::FileAccessMode::Read);
|
||||
size_t size = file.GetSize();
|
||||
file.Seek(0);
|
||||
g_icon_memory.resize(size);
|
||||
file.ReadRaw<u8>(g_icon_memory.data(), size);
|
||||
file.Close();
|
||||
}
|
||||
} else {
|
||||
g_icon_memory.resize(buf_size);
|
||||
std::memcpy(g_icon_memory.data(), buf, buf_size);
|
||||
IOFile file(g_icon_path, Common::FS::FileAccessMode::Write);
|
||||
file.Seek(0);
|
||||
file.WriteRaw<u8>(g_icon_memory.data(), buf_size);
|
||||
file.Close();
|
||||
}
|
||||
}
|
||||
|
||||
void WriteIcon(void* buf, size_t buf_size) {
|
||||
if (buf_size != g_icon_memory.size()) {
|
||||
g_icon_memory.resize(buf_size);
|
||||
}
|
||||
std::memcpy(g_icon_memory.data(), buf, buf_size);
|
||||
g_icon_dirty = true;
|
||||
}
|
||||
|
||||
bool IsSaveMemoryInitialized() {
|
||||
return g_save_memory_initialized;
|
||||
}
|
||||
|
||||
PSF& GetParamSFO() {
|
||||
return g_param_sfo;
|
||||
}
|
||||
|
||||
std::span<u8> GetIcon() {
|
||||
return {g_icon_memory};
|
||||
}
|
||||
|
||||
void SaveSFO(bool sync) {
|
||||
if (!sync) {
|
||||
g_param_dirty = true;
|
||||
IOFile f;
|
||||
int r = f.Open(memoryPath, Common::FS::FileAccessMode::Write);
|
||||
if (f.IsOpen()) {
|
||||
f.WriteRaw<u8>(data.memory_cache.data(), data.memory_cache.size());
|
||||
f.Close();
|
||||
return;
|
||||
}
|
||||
const bool ok = g_param_sfo.Encode(g_param_sfo_path);
|
||||
const auto err = std::error_code{r, std::iostream_category()};
|
||||
throw std::filesystem::filesystem_error{err.message(), err};
|
||||
} catch (const std::filesystem::filesystem_error& e) {
|
||||
errMsg = std::string{e.what()};
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
}
|
||||
const MsgDialog::MsgDialogState dialog{MsgDialog::MsgDialogState::UserState{
|
||||
.type = MsgDialog::ButtonType::OK,
|
||||
.msg = "Failed to persist save memory:\n" + errMsg + "\nat " +
|
||||
Common::FS::PathToUTF8String(memoryPath),
|
||||
}};
|
||||
MsgDialog::ShowMsgDialog(dialog);
|
||||
}
|
||||
|
||||
std::string GetSaveDir(u32 slot_id) {
|
||||
std::string dir(StandardDirnameSaveDataMemory);
|
||||
if (slot_id > 0) {
|
||||
dir += std::to_string(slot_id);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
std::filesystem::path GetSavePath(OrbisUserServiceUserId user_id, u32 slot_id,
|
||||
std::string_view game_serial) {
|
||||
std::string dir(StandardDirnameSaveDataMemory);
|
||||
if (slot_id > 0) {
|
||||
dir += std::to_string(slot_id);
|
||||
}
|
||||
return SaveInstance::MakeDirSavePath(user_id, Common::ElfInfo::Instance().GameSerial(), dir);
|
||||
}
|
||||
|
||||
size_t SetupSaveMemory(OrbisUserServiceUserId user_id, u32 slot_id, std::string_view game_serial) {
|
||||
std::lock_guard lck{g_slot_mtx};
|
||||
|
||||
const auto save_dir = GetSavePath(user_id, slot_id, game_serial);
|
||||
|
||||
auto& data = g_attached_slots[slot_id];
|
||||
data = SlotData{
|
||||
.user_id = user_id,
|
||||
.game_serial = std::string{game_serial},
|
||||
.folder_path = save_dir,
|
||||
.sfo = {},
|
||||
.memory_cache = {},
|
||||
};
|
||||
|
||||
SaveInstance::SetupDefaultParamSFO(data.sfo, GetSaveDir(slot_id), std::string{game_serial});
|
||||
|
||||
auto param_sfo_path = SaveInstance::GetParamSFOPath(save_dir);
|
||||
if (!fs::exists(param_sfo_path)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!data.sfo.Open(param_sfo_path) || fs::exists(save_dir / CorruptFileName)) {
|
||||
if (!Backup::Restore(save_dir)) { // Could not restore the backup
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const auto memory = save_dir / FilenameSaveDataMemory;
|
||||
if (fs::exists(memory)) {
|
||||
return fs::file_size(memory);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SetIcon(u32 slot_id, void* buf, size_t buf_size) {
|
||||
std::lock_guard lck{g_slot_mtx};
|
||||
const auto& data = g_attached_slots[slot_id];
|
||||
const auto icon_path = data.folder_path / sce_sys / "icon0.png";
|
||||
if (buf == nullptr) {
|
||||
const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png");
|
||||
if (fs::exists(icon_path)) {
|
||||
fs::remove(icon_path);
|
||||
}
|
||||
if (fs::exists(src_icon)) {
|
||||
fs::create_directories(icon_path.parent_path());
|
||||
fs::copy_file(src_icon, icon_path);
|
||||
}
|
||||
} else {
|
||||
IOFile file(icon_path, Common::FS::FileAccessMode::Write);
|
||||
file.WriteRaw<u8>(buf, buf_size);
|
||||
file.Close();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsSaveMemoryInitialized(u32 slot_id) {
|
||||
std::lock_guard lck{g_slot_mtx};
|
||||
return g_attached_slots.contains(slot_id);
|
||||
}
|
||||
|
||||
PSF& GetParamSFO(u32 slot_id) {
|
||||
std::lock_guard lck{g_slot_mtx};
|
||||
auto& data = g_attached_slots[slot_id];
|
||||
return data.sfo;
|
||||
}
|
||||
|
||||
std::vector<u8> GetIcon(u32 slot_id) {
|
||||
std::lock_guard lck{g_slot_mtx};
|
||||
auto& data = g_attached_slots[slot_id];
|
||||
const auto icon_path = data.folder_path / sce_sys / "icon0.png";
|
||||
IOFile f{icon_path, Common::FS::FileAccessMode::Read};
|
||||
if (!f.IsOpen()) {
|
||||
return {};
|
||||
}
|
||||
const u64 size = f.GetSize();
|
||||
std::vector<u8> ret;
|
||||
ret.resize(size);
|
||||
f.ReadSpan(std::span{ret});
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SaveSFO(u32 slot_id) {
|
||||
std::lock_guard lck{g_slot_mtx};
|
||||
const auto& data = g_attached_slots[slot_id];
|
||||
const auto sfo_path = SaveInstance::GetParamSFOPath(data.folder_path);
|
||||
fs::create_directories(sfo_path.parent_path());
|
||||
const bool ok = data.sfo.Encode(sfo_path);
|
||||
if (!ok) {
|
||||
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;
|
||||
|
||||
void ReadMemory(u32 slot_id, void* buf, size_t buf_size, int64_t offset) {
|
||||
std::lock_guard lk{g_slot_mtx};
|
||||
auto& data = g_attached_slots[slot_id];
|
||||
auto& memory = data.memory_cache;
|
||||
if (memory.empty()) { // Load file
|
||||
IOFile f{data.folder_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Read};
|
||||
if (f.IsOpen()) {
|
||||
memory.resize(f.GetSize());
|
||||
f.Seek(0);
|
||||
f.ReadSpan(std::span{memory});
|
||||
}
|
||||
}
|
||||
s64 read_size = buf_size;
|
||||
if (read_size + offset > memory.size()) {
|
||||
read_size = memory.size() - offset;
|
||||
}
|
||||
std::memcpy(buf, memory.data() + offset, read_size);
|
||||
}
|
||||
|
||||
bool TriggerSaveWithoutEvent() {
|
||||
if (g_saving_memory) {
|
||||
return false;
|
||||
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);
|
||||
}
|
||||
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 + buf_size > g_save_memory.size()) {
|
||||
UNREACHABLE_MSG("ReadMemory out of bounds");
|
||||
}
|
||||
std::memcpy(buf, g_save_memory.data() + offset, buf_size);
|
||||
}
|
||||
|
||||
void WriteMemory(void* buf, size_t buf_size, int64_t offset) {
|
||||
std::scoped_lock lk{g_saving_memory_mutex};
|
||||
if (offset + buf_size > g_save_memory.size()) {
|
||||
g_save_memory.resize(offset + buf_size);
|
||||
}
|
||||
std::memcpy(g_save_memory.data() + offset, buf, buf_size);
|
||||
g_memory_dirty = true;
|
||||
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
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#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<u8> GetIcon();
|
||||
[[nodiscard]] std::vector<u8> 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
|
|
@ -177,7 +177,8 @@ struct OrbisSaveDataMemoryGet2 {
|
|||
OrbisSaveDataMemoryData* data;
|
||||
OrbisSaveDataParam* param;
|
||||
OrbisSaveDataIcon* icon;
|
||||
std::array<u8, 32> _reserved;
|
||||
u32 slotId;
|
||||
std::array<u8, 28> _reserved;
|
||||
};
|
||||
|
||||
struct OrbisSaveDataMemorySet2 {
|
||||
|
@ -186,6 +187,8 @@ struct OrbisSaveDataMemorySet2 {
|
|||
const OrbisSaveDataMemoryData* data;
|
||||
const OrbisSaveDataParam* param;
|
||||
const OrbisSaveDataIcon* icon;
|
||||
u32 dataNum;
|
||||
u32 slotId;
|
||||
std::array<u8, 32> _reserved;
|
||||
};
|
||||
|
||||
|
@ -198,7 +201,8 @@ struct OrbisSaveDataMemorySetup2 {
|
|||
const OrbisSaveDataParam* initParam;
|
||||
// +4.5
|
||||
const OrbisSaveDataIcon* initIcon;
|
||||
std::array<u8, 24> _reserved;
|
||||
u32 slotId;
|
||||
std::array<u8, 20> _reserved;
|
||||
};
|
||||
|
||||
struct OrbisSaveDataMemorySetupResult {
|
||||
|
@ -206,9 +210,16 @@ struct OrbisSaveDataMemorySetupResult {
|
|||
std::array<u8, 16> _reserved;
|
||||
};
|
||||
|
||||
enum OrbisSaveDataMemorySyncOption : u32 {
|
||||
NONE = 0,
|
||||
BLOCKING = 1,
|
||||
};
|
||||
|
||||
struct OrbisSaveDataMemorySync {
|
||||
OrbisUserServiceUserId userId;
|
||||
std::array<u8, 36> _reserved;
|
||||
u32 slotId;
|
||||
OrbisSaveDataMemorySyncOption option;
|
||||
std::array<u8, 28> _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();
|
||||
}
|
||||
auto icon = setParam->icon;
|
||||
if (icon != nullptr) {
|
||||
SaveMemory::WriteIcon(icon->buf, icon->bufSize);
|
||||
param->ToSFO(SaveMemory::GetParamSFO(slot_id));
|
||||
SaveMemory::SaveSFO(slot_id);
|
||||
}
|
||||
|
||||
auto icon = setParam->icon;
|
||||
if (icon != nullptr) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
@ -271,7 +272,7 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector<std::str
|
|||
UpdatePlayTime(id);
|
||||
#endif
|
||||
|
||||
std::exit(0);
|
||||
std::quick_exit(0);
|
||||
}
|
||||
|
||||
void Emulator::LoadSystemModules(const std::string& game_serial) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue