SaveDataDialog implementation

- Fix Mounting/Unmounting check of SaveInstance
This commit is contained in:
Vinicius Rangel 2024-09-19 22:03:19 -03:00
parent d059ea5ac3
commit edfbc70075
No known key found for this signature in database
GPG key ID: A5B154D904B761D9
17 changed files with 1455 additions and 194 deletions

View file

@ -240,8 +240,10 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.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
src/core/libraries/system/savedatadialog.h
src/core/libraries/save_data/dialog/savedatadialog.cpp
src/core/libraries/save_data/dialog/savedatadialog.h
src/core/libraries/save_data/dialog/savedatadialog_ui.cpp
src/core/libraries/save_data/dialog/savedatadialog_ui.h
src/core/libraries/system/sysmodule.cpp
src/core/libraries/system/sysmodule.h
src/core/libraries/system/systemservice.cpp

View file

@ -21,7 +21,9 @@ static inline u32 get_max_size(std::string_view key, u32 default_value) {
}
bool PSF::Open(const std::filesystem::path& filepath) {
last_write = std::filesystem::last_write_time(filepath);
if (std::filesystem::exists(filepath)) {
last_write = std::filesystem::last_write_time(filepath);
}
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {

View file

@ -112,17 +112,10 @@ int PS4_SYSV_ABI sceAppContentAppParamGetInt(OrbisAppContentAppParamId paramId,
value = param_sfo->GetInteger("USER_DEFINED_PARAM_4");
break;
default:
LOG_ERROR(Lib_AppContent, " paramId = {}, value = {} paramId is not valid", paramId,
*value);
LOG_ERROR(Lib_AppContent, " paramId = {} paramId is not valid", paramId);
return ORBIS_APP_CONTENT_ERROR_PARAMETER;
}
if (!value.has_value()) {
LOG_ERROR(Lib_AppContent,
" paramId = {}, value = {} value is not valid can't read param.sfo?", paramId,
*value);
return ORBIS_APP_CONTENT_ERROR_PARAMETER;
}
*out_value = *value;
*out_value = value.value_or(0);
return ORBIS_OK;
}

View file

@ -27,12 +27,12 @@
#include "core/libraries/playgo/playgo.h"
#include "core/libraries/random/random.h"
#include "core/libraries/rtc/rtc.h"
#include "core/libraries/save_data/dialog/savedatadialog.h"
#include "core/libraries/save_data/savedata.h"
#include "core/libraries/screenshot/screenshot.h"
#include "core/libraries/system/commondialog.h"
#include "core/libraries/system/msgdialog.h"
#include "core/libraries/system/posix.h"
#include "core/libraries/system/savedatadialog.h"
#include "core/libraries/system/sysmodule.h"
#include "core/libraries/system/systemservice.h"
#include "core/libraries/system/userservice.h"
@ -57,11 +57,11 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
Libraries::Net::RegisterlibSceNet(sym);
Libraries::NetCtl::RegisterlibSceNetCtl(sym);
Libraries::SaveData::RegisterlibSceSaveData(sym);
Libraries::SaveData::Dialog::RegisterlibSceSaveDataDialog(sym);
Libraries::Ssl::RegisterlibSceSsl(sym);
Libraries::SysModule::RegisterlibSceSysmodule(sym);
Libraries::Posix::Registerlibsceposix(sym);
Libraries::AudioIn::RegisterlibSceAudioIn(sym);
Libraries::SaveDataDialog::RegisterlibSceSaveDataDialog(sym);
Libraries::NpManager::RegisterlibSceNpManager(sym);
Libraries::NpScore::RegisterlibSceNpScore(sym);
Libraries::NpTrophy::RegisterlibSceNpTrophy(sym);

View file

@ -0,0 +1,163 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/libraries/libs.h"
#include "core/libraries/system/commondialog.h"
#include "magic_enum.hpp"
#include "savedatadialog.h"
#include "savedatadialog_ui.h"
namespace Libraries::SaveData::Dialog {
using CommonDialog::Error;
using CommonDialog::Result;
using CommonDialog::Status;
static auto g_status = Status::NONE;
static SaveDialogState g_state{};
static SaveDialogResult g_result{};
static SaveDialogUi g_save_dialog_ui;
Error PS4_SYSV_ABI sceSaveDataDialogClose() {
LOG_DEBUG(Lib_SaveDataDialog, "called");
if (g_status != Status::RUNNING) {
return Error::NOT_RUNNING;
}
g_save_dialog_ui.Finish(ButtonId::INVALID);
g_save_dialog_ui = SaveDialogUi{};
return Error::OK;
}
Error PS4_SYSV_ABI sceSaveDataDialogGetResult(OrbisSaveDataDialogResult* result) {
LOG_DEBUG(Lib_SaveDataDialog, "called");
if (g_status != Status::FINISHED) {
return Error::NOT_FINISHED;
}
if (result == nullptr) {
return Error::ARG_NULL;
}
g_result.CopyTo(*result);
return Error::OK;
}
Status PS4_SYSV_ABI sceSaveDataDialogGetStatus() {
LOG_TRACE(Lib_SaveDataDialog, "called status={}", magic_enum::enum_name(g_status));
return g_status;
}
Error PS4_SYSV_ABI sceSaveDataDialogInitialize() {
LOG_DEBUG(Lib_SaveDataDialog, "called");
if (!CommonDialog::g_isInitialized) {
return Error::NOT_SYSTEM_INITIALIZED;
}
if (g_status != Status::NONE) {
return Error::ALREADY_INITIALIZED;
}
if (CommonDialog::g_isUsed) {
return Error::BUSY;
}
g_status = Status::INITIALIZED;
CommonDialog::g_isUsed = true;
return Error::OK;
}
s32 PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay() {
return 1;
}
Error PS4_SYSV_ABI sceSaveDataDialogOpen(const OrbisSaveDataDialogParam* param) {
if (g_status != Status::INITIALIZED && g_status != Status::FINISHED) {
LOG_INFO(Lib_SaveDataDialog, "called without initialize");
return Error::INVALID_STATE;
}
if (param == nullptr) {
LOG_DEBUG(Lib_SaveDataDialog, "called param:(NULL)");
return Error::ARG_NULL;
}
LOG_DEBUG(Lib_SaveDataDialog, "called param->mode: {}", magic_enum::enum_name(param->mode));
ASSERT(param->size == sizeof(OrbisSaveDataDialogParam));
ASSERT(param->baseParam.size == sizeof(CommonDialog::BaseParam));
g_result = {};
g_state = SaveDialogState{*param};
g_status = Status::RUNNING;
g_save_dialog_ui = SaveDialogUi(&g_state, &g_status, &g_result);
return Error::OK;
}
Error PS4_SYSV_ABI sceSaveDataDialogProgressBarInc(OrbisSaveDataDialogProgressBarTarget target,
u32 delta) {
LOG_DEBUG(Lib_SaveDataDialog, "called");
if (g_status != Status::RUNNING) {
return Error::NOT_RUNNING;
}
if (g_state.GetMode() != SaveDataDialogMode::PROGRESS_BAR) {
return Error::NOT_SUPPORTED;
}
if (target != OrbisSaveDataDialogProgressBarTarget::DEFAULT) {
return Error::PARAM_INVALID;
}
g_state.GetState<SaveDialogState::ProgressBarState>().progress += delta;
return Error::OK;
}
Error PS4_SYSV_ABI sceSaveDataDialogProgressBarSetValue(OrbisSaveDataDialogProgressBarTarget target,
u32 rate) {
LOG_DEBUG(Lib_SaveDataDialog, "called");
if (g_status != Status::RUNNING) {
return Error::NOT_RUNNING;
}
if (g_state.GetMode() != SaveDataDialogMode::PROGRESS_BAR) {
return Error::NOT_SUPPORTED;
}
if (target != OrbisSaveDataDialogProgressBarTarget::DEFAULT) {
return Error::PARAM_INVALID;
}
g_state.GetState<SaveDialogState::ProgressBarState>().progress = rate;
return Error::OK;
}
Error PS4_SYSV_ABI sceSaveDataDialogTerminate() {
LOG_DEBUG(Lib_SaveDataDialog, "called");
if (g_status == Status::RUNNING) {
sceSaveDataDialogClose();
}
if (g_status == Status::NONE) {
return Error::NOT_INITIALIZED;
}
g_save_dialog_ui = SaveDialogUi{};
g_status = Status::NONE;
CommonDialog::g_isUsed = false;
return Error::OK;
}
Status PS4_SYSV_ABI sceSaveDataDialogUpdateStatus() {
LOG_TRACE(Lib_SaveDataDialog, "called status={}", magic_enum::enum_name(g_status));
return g_status;
}
void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("fH46Lag88XY", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogClose);
LIB_FUNCTION("yEiJ-qqr6Cg", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogGetResult);
LIB_FUNCTION("ERKzksauAJA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogGetStatus);
LIB_FUNCTION("s9e3+YpRnzw", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogInitialize);
LIB_FUNCTION("en7gNVnh878", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogIsReadyToDisplay);
LIB_FUNCTION("4tPhsP6FpDI", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogOpen);
LIB_FUNCTION("V-uEeFKARJU", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogProgressBarInc);
LIB_FUNCTION("hay1CfTmLyA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogProgressBarSetValue);
LIB_FUNCTION("YuH2FA7azqQ", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogTerminate);
LIB_FUNCTION("KK3Bdg1RWK0", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogUpdateStatus);
};
} // namespace Libraries::SaveData::Dialog

View file

@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/system/commondialog.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::SaveData::Dialog {
struct OrbisSaveDataDialogParam;
struct OrbisSaveDataDialogResult;
enum class OrbisSaveDataDialogProgressBarTarget : u32;
CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogClose();
CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogGetResult(OrbisSaveDataDialogResult* result);
CommonDialog::Status PS4_SYSV_ABI sceSaveDataDialogGetStatus();
CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogInitialize();
s32 PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay();
CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogOpen(const OrbisSaveDataDialogParam* param);
CommonDialog::Error PS4_SYSV_ABI
sceSaveDataDialogProgressBarInc(OrbisSaveDataDialogProgressBarTarget target, u32 delta);
CommonDialog::Error PS4_SYSV_ABI
sceSaveDataDialogProgressBarSetValue(OrbisSaveDataDialogProgressBarTarget target, u32 rate);
CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogTerminate();
CommonDialog::Status PS4_SYSV_ABI sceSaveDataDialogUpdateStatus();
void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::SaveData::Dialog

View file

@ -0,0 +1,802 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fmt/chrono.h>
#include <imgui.h>
#include <magic_enum.hpp>
#include "common/singleton.h"
#include "core/file_sys/fs.h"
#include "core/libraries/save_data/save_instance.h"
#include "imgui/imgui_std.h"
#include "savedatadialog_ui.h"
using namespace ImGui;
using namespace Libraries::CommonDialog;
constexpr u32 OrbisSaveDataBlockSize = 32768; // 32 KiB
constexpr auto SAVE_ICON_SIZE = ImVec2{152.0f, 85.0f};
constexpr auto SAVE_ICON_PADDING = ImVec2{8.0f, 2.0f};
static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f};
constexpr auto FOOTER_HEIGHT = BUTTON_SIZE.y + 15.0f;
static constexpr float PROGRESS_BAR_WIDTH{0.8f};
static ::Core::FileSys::MntPoints* g_mnt =
Common::Singleton<::Core::FileSys::MntPoints>::Instance();
static std::string SpaceSizeToString(size_t size) {
std::string size_str;
if (size > 1024 * 1024 * 1024) { // > 1GB
size_str = fmt::format("{:.2f} GB", double(size / 1024 / 1024) / 1024.0f);
} else if (size > 1024 * 1024) { // > 1MB
size_str = fmt::format("{:.2f} MB", double(size / 1024) / 1024.0f);
} else if (size > 1024) { // > 1KB
size_str = fmt::format("{:.2f} KB", double(size) / 1024.0f);
} else {
size_str = fmt::format("{} B", size);
}
return size_str;
}
namespace Libraries::SaveData::Dialog {
void SaveDialogResult::CopyTo(OrbisSaveDataDialogResult& result) const {
result.mode = this->mode;
result.result = this->result;
result.buttonId = this->button_id;
if (result.dirName != nullptr) {
result.dirName->data.FromString(this->dir_name);
}
if (result.param != nullptr && this->param.GetString(SaveParams::MAINTITLE).has_value()) {
result.param->FromSFO(this->param);
}
result.userData = this->user_data;
}
SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) {
this->mode = param.mode;
this->type = param.dispType;
this->user_data = param.userData;
if (param.optionParam != nullptr) {
this->enable_back = {param.optionParam->back == OptionBack::ENABLE};
}
static std::string game_serial{*Common::Singleton<PSF>::Instance()->GetString("CONTENT_ID"), 7,
9};
const auto item = param.items;
this->user_id = item->userId;
if (item->titleId == nullptr) {
this->title_id = game_serial;
} else {
this->title_id = item->titleId->data.to_string();
}
for (u32 i = 0; i < item->dirNameNum; i++) {
const auto dir_name = item->dirName[i].data.to_view();
if (dir_name.empty()) {
continue;
}
auto dir_path = SaveInstance::MakeDirSavePath(user_id, title_id, dir_name);
auto param_sfo_path = dir_path / "sce_sys" / "param.sfo";
if (!std::filesystem::exists(param_sfo_path)) {
continue;
}
PSF param_sfo;
param_sfo.Open(param_sfo_path);
auto last_write = param_sfo.GetLastWrite();
#ifdef _WIN32
auto utc_time = std::chrono::file_clock::to_utc(last_write);
#else
auto utc_time = std::chrono::file_clock::to_sys(last_write);
#endif
std::string date_str = fmt::format("{:%d %b, %Y %R}", utc_time);
size_t size = Common::FS::GetDirectorySize(dir_path);
std::string size_str = SpaceSizeToString(size);
auto icon_path = dir_path / "sce_sys" / "icon0.png";
RefCountedTexture icon;
if (std::filesystem::exists(icon_path)) {
icon = RefCountedTexture::DecodePngFile(icon_path);
}
bool is_corrupted = std::filesystem::exists(dir_path / "sce_sys" / "corrupted");
this->save_list.emplace_back(Item{
.dir_name = std::string{dir_name},
.icon = icon,
.title = std::string{*param_sfo.GetString(SaveParams::MAINTITLE)},
.subtitle = std::string{*param_sfo.GetString(SaveParams::SUBTITLE)},
.details = std::string{*param_sfo.GetString(SaveParams::DETAIL)},
.date = date_str,
.size = size_str,
.last_write = param_sfo.GetLastWrite(),
.pfo = param_sfo,
.is_corrupted = is_corrupted,
});
}
if (type == DialogType::SAVE) {
RefCountedTexture icon;
std::string title{"New Save"};
const auto new_item = item->newItem;
if (new_item != nullptr && new_item->iconBuf && new_item->iconSize) {
auto buf = (u8*)new_item->iconBuf;
icon = RefCountedTexture::DecodePngTexture({buf, buf + new_item->iconSize});
} else {
const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png");
if (std::filesystem::exists(src_icon)) {
icon = RefCountedTexture::DecodePngFile(src_icon);
}
}
if (new_item != nullptr && new_item->title != nullptr) {
title = std::string{new_item->title};
}
this->new_item = Item{
.dir_name = "",
.icon = icon,
.title = title,
};
}
if (item->focusPos != FocusPos::DIRNAME) {
this->focus_pos = item->focusPos;
} else {
this->focus_pos = item->focusPosDirName->data.to_string();
}
this->style = item->itemStyle;
switch (mode) {
case SaveDataDialogMode::USER_MSG: {
this->state = UserState{param};
} break;
case SaveDataDialogMode::SYSTEM_MSG:
this->state = SystemState{*this, param};
break;
case SaveDataDialogMode::ERROR_CODE: {
this->state = ErrorCodeState{param};
} break;
case SaveDataDialogMode::PROGRESS_BAR: {
this->state = ProgressBarState{*this, param};
} break;
default:
break;
}
}
SaveDialogState::UserState::UserState(const OrbisSaveDataDialogParam& param) {
auto& user = *param.userMsgParam;
this->type = user.buttonType;
this->msg_type = user.msgType;
this->msg = user.msg != nullptr ? std::string{user.msg} : std::string{};
}
SaveDialogState::SystemState::SystemState(const SaveDialogState& state,
const OrbisSaveDataDialogParam& param) {
#define M(save, load, del) \
if (type == DialogType::SAVE) \
this->msg = save; \
else if (type == DialogType::LOAD) \
this->msg = load; \
else if (type == DialogType::DELETE) \
this->msg = del; \
else \
UNREACHABLE()
auto type = param.dispType;
auto& sys = *param.sysMsgParam;
switch (sys.msgType) {
case SystemMessageType::NODATA: {
this->msg = "There is no saved data";
} break;
case SystemMessageType::CONFIRM:
show_no = true;
M("Do you want to save?", "Do you want to load this saved data?",
"Do you want to delete this saved data?");
break;
case SystemMessageType::OVERWRITE:
show_no = true;
M("Do you want to overwrite the existing saved data?", "##UNKNOWN##", "##UNKNOWN##");
break;
case SystemMessageType::NOSPACE:
M(fmt::format(
"There is not enough space to save the data. To continue {} free space is required.",
SpaceSizeToString(sys.value * OrbisSaveDataBlockSize)),
"##UNKNOWN##", "##UNKNOWN##");
break;
case SystemMessageType::PROGRESS:
hide_ok = true;
show_cancel = state.enable_back.value_or(false);
M("Saving...", "Loading...", "Deleting...");
break;
case SystemMessageType::FILE_CORRUPTED:
this->msg = "The saved data is corrupted.";
break;
case SystemMessageType::FINISHED:
M("Saved successfully.", "Loading complete.", "Deletion complete.");
break;
case SystemMessageType::NOSPACE_CONTINUABLE:
M(fmt::format("There is not enough space to save the data. {} free space is required.",
SpaceSizeToString(sys.value * OrbisSaveDataBlockSize)),
"##UNKNOWN##", "##UNKNOWN##");
break;
case SystemMessageType::CORRUPTED_AND_DELETED: {
show_cancel = state.enable_back.value_or(true);
const char* msg1 = "The saved data is corrupted and will be deleted.";
M(msg1, msg1, "##UNKNOWN##");
} break;
case SystemMessageType::CORRUPTED_AND_CREATED: {
show_cancel = state.enable_back.value_or(true);
const char* msg2 = "The saved data is corrupted. This saved data will be deleted and a new "
"one will be created.";
M(msg2, msg2, "##UNKNOWN##");
} break;
case SystemMessageType::CORRUPTED_AND_RESTORE: {
show_cancel = state.enable_back.value_or(true);
const char* msg3 =
"The saved data is corrupted. The data that was backed up by the system will be "
"restored.";
M(msg3, msg3, "##UNKNOWN##");
} break;
case SystemMessageType::TOTAL_SIZE_EXCEEDED:
M("Cannot create more saved data", "##UNKNOWN##", "##UNKNOWN##");
break;
default:
msg = fmt::format("Unknown message type: {}", magic_enum::enum_name(sys.msgType));
break;
}
#undef M
}
SaveDialogState::ErrorCodeState::ErrorCodeState(const OrbisSaveDataDialogParam& param) {
auto& err = *param.errorCodeParam;
constexpr auto NOT_FOUND = 0x809F0008;
constexpr auto BROKEN = 0x809F000F;
switch (err.errorCode) {
case NOT_FOUND:
this->error_msg = "There is not saved data.";
break;
case BROKEN:
this->error_msg = "The data is corrupted.";
break;
default:
this->error_msg = fmt::format("An error has occurred. ({:X})", err.errorCode);
break;
}
}
SaveDialogState::ProgressBarState::ProgressBarState(const SaveDialogState& state,
const OrbisSaveDataDialogParam& param) {
this->progress = 0;
auto& bar = *param.progressBarParam;
switch (bar.sysMsgType) {
case ProgressSystemMessageType::INVALID:
this->msg = bar.msg != nullptr ? std::string{bar.msg} : std::string{};
break;
case ProgressSystemMessageType::PROGRESS:
switch (state.type) {
case DialogType::SAVE:
this->msg = "Saving...";
break;
case DialogType::LOAD:
this->msg = "Loading...";
break;
case DialogType::DELETE:
this->msg = "Deleting...";
break;
}
break;
case ProgressSystemMessageType::RESTORE:
this->msg = "Restoring saved data...";
break;
}
}
SaveDialogUi::SaveDialogUi(SaveDialogState* state, Status* status, SaveDialogResult* result)
: state(state), status(status), result(result) {
if (status && *status == Status::RUNNING) {
first_render = true;
AddLayer(this);
}
}
SaveDialogUi::~SaveDialogUi() {
Finish(ButtonId::INVALID);
}
SaveDialogUi::SaveDialogUi(SaveDialogUi&& other) noexcept
: Layer(other), state(other.state), status(other.status), result(other.result) {
std::scoped_lock lock(draw_mutex, other.draw_mutex);
other.state = nullptr;
other.status = nullptr;
other.result = nullptr;
if (status && *status == Status::RUNNING) {
first_render = true;
AddLayer(this);
}
}
SaveDialogUi& SaveDialogUi::operator=(SaveDialogUi other) {
std::scoped_lock lock(draw_mutex, other.draw_mutex);
using std::swap;
swap(state, other.state);
swap(status, other.status);
swap(result, other.result);
if (status && *status == Status::RUNNING) {
first_render = true;
AddLayer(this);
}
return *this;
}
void SaveDialogUi::Finish(ButtonId buttonId, Result r) {
std::unique_lock lock(draw_mutex);
if (result) {
result->mode = this->state->mode;
result->result = r;
result->button_id = buttonId;
result->user_data = this->state->user_data;
if (state && state->mode != SaveDataDialogMode::LIST && !state->save_list.empty()) {
result->dir_name = state->save_list.front().dir_name;
}
}
if (status) {
*status = Status::FINISHED;
}
RemoveLayer(this);
}
void SaveDialogUi::Draw() {
std::unique_lock lock{draw_mutex};
if (status == nullptr || *status != Status::RUNNING || state == nullptr) {
return;
}
const auto& ctx = *GetCurrentContext();
const auto& io = ctx.IO;
ImVec2 window_size;
if (state->GetMode() == SaveDataDialogMode::LIST) {
window_size = ImVec2{
std::min(io.DisplaySize.x - 200.0f, 1100.0f),
std::min(io.DisplaySize.y - 100.0f, 700.0f),
};
} else {
window_size = ImVec2{
std::min(io.DisplaySize.x, 500.0f),
std::min(io.DisplaySize.y, 300.0f),
};
}
CentralizeWindow();
SetNextWindowSize(window_size);
SetNextWindowCollapsed(false);
if (first_render || !io.NavActive) {
SetNextWindowFocus();
}
if (Begin("Save Data Dialog##SaveDataDialog", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) {
DrawPrettyBackground();
Separator();
// Draw title bigger
SetWindowFontScale(1.7f);
switch (state->type) {
case DialogType::SAVE:
TextUnformatted("Save");
break;
case DialogType::LOAD:
TextUnformatted("Load");
break;
case DialogType::DELETE:
TextUnformatted("Delete");
break;
}
SetWindowFontScale(1.0f);
Separator();
BeginGroup();
switch (state->GetMode()) {
case SaveDataDialogMode::LIST:
DrawList();
break;
case SaveDataDialogMode::USER_MSG:
DrawUser();
break;
case SaveDataDialogMode::SYSTEM_MSG:
DrawSystemMessage();
break;
case SaveDataDialogMode::ERROR_CODE:
DrawErrorCode();
break;
case SaveDataDialogMode::PROGRESS_BAR:
DrawProgressBar();
break;
default:
TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "!!!Unknown dialog mode!!!");
}
EndGroup();
}
End();
first_render = false;
if (*status == Status::FINISHED) {
if (state) {
*state = SaveDialogState{};
}
state = nullptr;
status = nullptr;
result = nullptr;
}
}
void SaveDialogUi::DrawItem(int _id, const SaveDialogState::Item& item, bool clickable) {
constexpr auto text_spacing = 1.2f;
auto& ctx = *GetCurrentContext();
auto& window = *ctx.CurrentWindow;
auto content_region_avail = GetContentRegionAvail();
const auto outer_pos = window.DC.CursorPos;
const auto pos = outer_pos + SAVE_ICON_PADDING;
const ImVec2 size = {content_region_avail.x - SAVE_ICON_PADDING.x,
SAVE_ICON_SIZE.y + SAVE_ICON_PADDING.y};
const ImRect bb{outer_pos, outer_pos + size + SAVE_ICON_PADDING};
const ImGuiID id = GetID(_id);
ItemSize(size + ImVec2{0.0f, SAVE_ICON_PADDING.y * 2.0f});
if (!ItemAdd(bb, id)) {
return;
}
window.DrawList->AddRectFilled(bb.Min + SAVE_ICON_PADDING, bb.Max - SAVE_ICON_PADDING,
GetColorU32(ImVec4{0.3f}));
bool hovered = false;
if (clickable) {
bool held;
bool pressed = ButtonBehavior(bb, id, &hovered, &held);
if (pressed) {
result->dir_name = item.dir_name;
result->param = item.pfo;
Finish(ButtonId::INVALID);
}
RenderNavHighlight(bb, id);
}
if (item.icon) {
auto texture = item.icon.GetTexture();
window.DrawList->AddImage(texture.im_id, pos, pos + SAVE_ICON_SIZE);
} else {
// placeholder
window.DrawList->AddRectFilled(pos, pos + SAVE_ICON_SIZE, GetColorU32(ImVec4{0.7f}));
}
auto pos_x = SAVE_ICON_SIZE.x + 5.0f;
auto pos_y = 2.0f;
if (!item.title.empty()) {
const char* begin = &item.title.front();
const char* end = &item.title.back() + 1;
SetWindowFontScale(2.0f);
RenderText(pos + ImVec2{pos_x, pos_y}, begin, end, false);
if (item.is_corrupted) {
float width = CalcTextSize(begin, end).x + 10.0f;
PushStyleColor(ImGuiCol_Text, 0xFF0000FF);
RenderText(pos + ImVec2{pos_x + width, pos_y}, "- Corrupted", nullptr, false);
PopStyleColor();
}
pos_y += ctx.FontSize * text_spacing;
}
SetWindowFontScale(1.3f);
if (state->style == ItemStyle::TITLE_SUBTITLE_DATESIZE) {
if (!item.subtitle.empty()) {
const char* begin = &item.subtitle.front();
const char* end = &item.subtitle.back() + 1;
RenderText(pos + ImVec2{pos_x, pos_y}, begin, end, false);
}
pos_y += ctx.FontSize * text_spacing;
}
{
float width = 0.0f;
if (!item.date.empty()) {
const char* d_begin = &item.date.front();
const char* d_end = &item.date.back() + 1;
width = CalcTextSize(d_begin, d_end).x + 15.0f;
RenderText(pos + ImVec2{pos_x, pos_y}, d_begin, d_end, false);
}
if (!item.size.empty()) {
const char* s_begin = &item.size.front();
const char* s_end = &item.size.back() + 1;
RenderText(pos + ImVec2{pos_x + width, pos_y}, s_begin, s_end, false);
}
pos_y += ctx.FontSize * text_spacing;
}
if (state->style == ItemStyle::TITLE_DATASIZE_SUBTITLE && !item.subtitle.empty()) {
const char* begin = &item.subtitle.front();
const char* end = &item.subtitle.back() + 1;
RenderText(pos + ImVec2{pos_x, pos_y}, begin, end, false);
}
SetWindowFontScale(1.0f);
if (hovered) {
window.DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), 0.0f, 0, 2.0f);
}
}
void SaveDialogUi::DrawList() {
auto availableSize = GetContentRegionAvail();
constexpr auto footerHeight = 30.0f;
availableSize.y -= footerHeight + 1.0f;
BeginChild("##ScrollingRegion", availableSize, ImGuiChildFlags_NavFlattened);
int i = 0;
if (state->new_item.has_value()) {
DrawItem(i++, state->new_item.value());
}
for (const auto& item : state->save_list) {
DrawItem(i++, item);
}
if (first_render) { // Make the initial focus
if (std::holds_alternative<FocusPos>(state->focus_pos)) {
auto pos = std::get<FocusPos>(state->focus_pos);
if (pos == FocusPos::LISTHEAD || pos == FocusPos::DATAHEAD) {
SetItemCurrentNavFocus(GetID(0));
} else if (pos == FocusPos::LISTTAIL || pos == FocusPos::DATATAIL) {
SetItemCurrentNavFocus(GetID(std::max(i - 1, 0)));
} else { // Date
int idx = 0;
int max_idx = 0;
bool is_min = pos == FocusPos::DATAOLDEST;
std::filesystem::file_time_type max_write{};
if (state->new_item.has_value()) {
idx++;
}
for (const auto& item : state->save_list) {
if (item.last_write > max_write ^ is_min) {
max_write = item.last_write;
max_idx = idx;
}
idx++;
}
SetItemCurrentNavFocus(GetID(max_idx));
}
} else if (std::holds_alternative<std::string>(state->focus_pos)) {
auto dir_name = std::get<std::string>(state->focus_pos);
if (dir_name.empty()) {
SetItemCurrentNavFocus(GetID(0));
} else {
int idx = 0;
if (state->new_item.has_value()) {
if (dir_name == state->new_item->dir_name) {
SetItemCurrentNavFocus(GetID(idx));
}
idx++;
}
for (const auto& item : state->save_list) {
if (item.dir_name == dir_name) {
SetItemCurrentNavFocus(GetID(idx));
break;
}
idx++;
}
}
}
}
EndChild();
Separator();
if (state->enable_back.value_or(true)) {
constexpr auto back = "Back";
constexpr float pad = 7.0f;
const auto txt_size = CalcTextSize(back);
const auto button_size = ImVec2{
std::max(txt_size.x, 100.0f) + pad * 2.0f,
footerHeight - pad,
};
SetCursorPosX(GetContentRegionAvail().x - button_size.x);
if (Button(back, button_size)) {
result->dir_name.clear();
Finish(ButtonId::INVALID);
}
if (IsKeyPressed(ImGuiKey_GamepadFaceRight)) {
SetItemCurrentNavFocus();
}
}
}
void SaveDialogUi::DrawUser() {
const auto& user_state = state->GetState<SaveDialogState::UserState>();
const auto btn_type = user_state.type;
const auto ws = GetWindowSize();
if (!state->save_list.empty()) {
DrawItem(0, state->save_list.front(), false);
}
auto has_btn = btn_type != ButtonType::NONE;
ImVec2 btn_space;
if (has_btn) {
btn_space = ImVec2{0.0f, FOOTER_HEIGHT};
}
const auto& msg = user_state.msg;
if (!msg.empty()) {
const char* begin = &msg.front();
const char* end = &msg.back() + 1;
if (user_state.msg_type == UserMessageType::ERROR) {
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f));
// Maybe make the text bold?
}
DrawCenteredText(begin, end, GetContentRegionAvail() - btn_space);
if (user_state.msg_type == UserMessageType::ERROR) {
PopStyleColor();
}
}
if (has_btn) {
int count = 1;
if (btn_type == ButtonType::YESNO || btn_type == ButtonType::ONCANCEL) {
++count;
}
SetCursorPos({
ws.x / 2.0f - BUTTON_SIZE.x / 2.0f * static_cast<float>(count),
ws.y - FOOTER_HEIGHT + 5.0f,
});
BeginGroup();
if (btn_type == ButtonType::YESNO) {
if (Button("Yes", BUTTON_SIZE)) {
Finish(ButtonId::YES);
}
SameLine();
if (Button("No", BUTTON_SIZE)) {
Finish(ButtonId::NO);
}
if (first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) {
SetItemCurrentNavFocus();
}
} else {
if (Button("OK", BUTTON_SIZE)) {
Finish(ButtonId::OK);
}
if (first_render) {
SetItemCurrentNavFocus();
}
if (btn_type == ButtonType::ONCANCEL) {
SameLine();
if (Button("Cancel", BUTTON_SIZE)) {
Finish(ButtonId::INVALID, Result::USER_CANCELED);
}
if (IsKeyPressed(ImGuiKey_GamepadFaceRight)) {
SetItemCurrentNavFocus();
}
}
}
EndGroup();
}
}
void SaveDialogUi::DrawSystemMessage() {
const auto& sys_state = state->GetState<SaveDialogState::SystemState>();
if (!state->save_list.empty()) {
DrawItem(0, state->save_list.front(), false);
}
const auto ws = GetWindowSize();
const auto& msg = sys_state.msg;
if (!msg.empty()) {
const char* begin = &msg.front();
const char* end = &msg.back() + 1;
DrawCenteredText(begin, end, GetContentRegionAvail() - ImVec2{0.0f, FOOTER_HEIGHT});
}
int count = 1;
if (sys_state.hide_ok) {
--count;
}
if (sys_state.show_no || sys_state.show_cancel) {
++count;
}
SetCursorPos({
ws.x / 2.0f - BUTTON_SIZE.x / 2.0f * static_cast<float>(count),
ws.y - FOOTER_HEIGHT + 5.0f,
});
BeginGroup();
if (Button(sys_state.show_no ? "Yes" : "OK", BUTTON_SIZE)) {
Finish(ButtonId::YES);
}
SameLine();
if (sys_state.show_no) {
if (Button("No", BUTTON_SIZE)) {
Finish(ButtonId::NO);
}
} else if (sys_state.show_cancel) {
if (Button("Cancel", BUTTON_SIZE)) {
Finish(ButtonId::INVALID, Result::USER_CANCELED);
}
}
if (first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) {
SetItemCurrentNavFocus();
}
EndGroup();
}
void SaveDialogUi::DrawErrorCode() {
const auto& err_state = state->GetState<SaveDialogState::ErrorCodeState>();
if (!state->save_list.empty()) {
DrawItem(0, state->save_list.front(), false);
}
const auto ws = GetWindowSize();
const auto& msg = err_state.error_msg;
if (!msg.empty()) {
const char* begin = &msg.front();
const char* end = &msg.back() + 1;
DrawCenteredText(begin, end, GetContentRegionAvail() - ImVec2{0.0f, FOOTER_HEIGHT});
}
SetCursorPos({
ws.x / 2.0f - BUTTON_SIZE.x / 2.0f,
ws.y - FOOTER_HEIGHT + 5.0f,
});
if (Button("OK", BUTTON_SIZE)) {
Finish(ButtonId::OK);
}
if (first_render) {
SetItemCurrentNavFocus();
}
}
void SaveDialogUi::DrawProgressBar() {
const auto& bar_state = state->GetState<SaveDialogState::ProgressBarState>();
const auto ws = GetWindowSize();
if (!state->save_list.empty()) {
DrawItem(0, state->save_list.front(), false);
}
const auto& msg = bar_state.msg;
if (!msg.empty()) {
const char* begin = &msg.front();
const char* end = &msg.back() + 1;
DrawCenteredText(begin, end, GetContentRegionAvail() - ImVec2{0.0f, FOOTER_HEIGHT});
}
SetCursorPos({
ws.x * ((1 - PROGRESS_BAR_WIDTH) / 2.0f),
ws.y - FOOTER_HEIGHT + 5.0f,
});
ProgressBar(static_cast<float>(bar_state.progress) / 100.0f,
{PROGRESS_BAR_WIDTH * ws.x, BUTTON_SIZE.y});
}
}; // namespace Libraries::SaveData::Dialog

View file

@ -0,0 +1,317 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include <variant>
#include <vector>
#include "core/file_format/psf.h"
#include "core/libraries/save_data/savedata.h"
#include "core/libraries/system/commondialog.h"
#include "imgui/imgui_layer.h"
#include "imgui/imgui_texture.h"
namespace Libraries::SaveData::Dialog {
using OrbisUserServiceUserId = s32;
enum class SaveDataDialogMode : u32 {
INVALID = 0,
LIST = 1,
USER_MSG = 2,
SYSTEM_MSG = 3,
ERROR_CODE = 4,
PROGRESS_BAR = 5,
};
enum class DialogType : u32 {
SAVE = 1,
LOAD = 2,
DELETE = 3,
};
enum class DialogAnimation : u32 {
ON = 0,
OFF = 1,
};
enum class ButtonId : u32 {
INVALID = 0,
OK = 1,
YES = 1,
NO = 2,
};
enum class ButtonType : u32 {
OK = 0,
YESNO = 1,
NONE = 2,
ONCANCEL = 3,
};
enum class UserMessageType : u32 {
NORMAL = 0,
ERROR = 1,
};
enum class FocusPos : u32 {
LISTHEAD = 0,
LISTTAIL = 1,
DATAHEAD = 2,
DATATAIL = 3,
DATALTATEST = 4,
DATAOLDEST = 5,
DIRNAME = 6,
};
enum class ItemStyle : u32 {
TITLE_DATASIZE_SUBTITLE = 0,
TITLE_SUBTITLE_DATESIZE = 1,
TITLE_DATESIZE = 2,
};
enum class SystemMessageType : u32 {
NODATA = 1,
CONFIRM = 2,
OVERWRITE = 3,
NOSPACE = 4,
PROGRESS = 5,
FILE_CORRUPTED = 6,
FINISHED = 7,
NOSPACE_CONTINUABLE = 8,
CORRUPTED_AND_DELETED = 10,
CORRUPTED_AND_CREATED = 11,
CORRUPTED_AND_RESTORE = 13,
TOTAL_SIZE_EXCEEDED = 14,
};
enum class ProgressBarType : u32 {
PERCENTAGE = 0,
};
enum class ProgressSystemMessageType : u32 {
INVALID = 0,
PROGRESS = 1,
RESTORE = 2,
};
enum class OptionBack : u32 {
ENABLE = 0,
DISABLE = 1,
};
enum class OrbisSaveDataDialogProgressBarTarget : u32 {
DEFAULT = 0,
};
struct AnimationParam {
DialogAnimation userOK;
DialogAnimation userCancel;
std::array<u8, 32> _reserved;
};
struct SaveDialogNewItem {
const char* title;
void* iconBuf;
size_t iconSize;
std::array<u8, 32> _reserved;
};
struct SaveDialogItems {
OrbisUserServiceUserId userId;
s32 : 32;
const OrbisSaveDataTitleId* titleId;
const OrbisSaveDataDirName* dirName;
u32 dirNameNum;
s32 : 32;
const SaveDialogNewItem* newItem;
FocusPos focusPos;
s32 : 32;
const OrbisSaveDataDirName* focusPosDirName;
ItemStyle itemStyle;
std::array<u8, 32> _reserved;
};
struct UserMessageParam {
ButtonType buttonType;
UserMessageType msgType;
const char* msg;
std::array<u8, 32> _reserved;
};
struct SystemMessageParam {
SystemMessageType msgType;
s32 : 32;
u64 value;
std::array<u8, 32> _reserved;
};
struct ErrorCodeParam {
u32 errorCode;
std::array<u8, 32> _reserved;
};
struct ProgressBarParam {
ProgressBarType barType;
s32 : 32;
const char* msg;
ProgressSystemMessageType sysMsgType;
std::array<u8, 28> _reserved;
};
struct OptionParam {
OptionBack back;
std::array<u8, 32> _reserved;
};
struct OrbisSaveDataDialogParam {
CommonDialog::BaseParam baseParam;
s32 size;
SaveDataDialogMode mode;
DialogType dispType;
s32 : 32;
AnimationParam* animParam;
SaveDialogItems* items;
UserMessageParam* userMsgParam;
SystemMessageParam* sysMsgParam;
ErrorCodeParam* errorCodeParam;
ProgressBarParam* progressBarParam;
void* userData;
OptionParam* optionParam;
std::array<u8, 24> _reserved;
};
struct OrbisSaveDataDialogResult {
SaveDataDialogMode mode{};
CommonDialog::Result result{};
ButtonId buttonId{};
s32 : 32;
OrbisSaveDataDirName* dirName;
OrbisSaveDataParam* param;
void* userData;
std::array<u8, 32> _reserved;
};
struct SaveDialogResult {
SaveDataDialogMode mode{};
CommonDialog::Result result{CommonDialog::Result::OK};
ButtonId button_id{ButtonId::INVALID};
std::string dir_name{};
PSF param{};
void* user_data{};
void CopyTo(OrbisSaveDataDialogResult& result) const;
};
class SaveDialogState {
friend class SaveDialogUi;
public:
struct UserState {
ButtonType type{};
UserMessageType msg_type{};
std::string msg{};
UserState(const OrbisSaveDataDialogParam& param);
};
struct SystemState {
std::string msg{};
bool hide_ok{};
bool show_no{}; // Yes instead of OK
bool show_cancel{};
SystemState(const SaveDialogState& state, const OrbisSaveDataDialogParam& param);
};
struct ErrorCodeState {
std::string error_msg{};
ErrorCodeState(const OrbisSaveDataDialogParam& param);
};
struct ProgressBarState {
std::string msg{};
u32 progress{};
ProgressBarState(const SaveDialogState& state, const OrbisSaveDataDialogParam& param);
};
struct Item {
std::string dir_name{};
ImGui::RefCountedTexture icon{};
std::string title{};
std::string subtitle{};
std::string details{};
std::string date{};
std::string size{};
std::filesystem::file_time_type last_write{};
PSF pfo{};
bool is_corrupted{};
};
private:
SaveDataDialogMode mode{};
DialogType type{};
void* user_data{};
std::optional<bool> enable_back{};
OrbisUserServiceUserId user_id{};
std::string title_id{};
std::vector<Item> save_list{};
std::variant<FocusPos, std::string, std::monostate> focus_pos{std::monostate{}};
ItemStyle style{};
std::optional<Item> new_item{};
std::variant<UserState, SystemState, ErrorCodeState, ProgressBarState, std::monostate> state{
std::monostate{}};
public:
explicit SaveDialogState(const OrbisSaveDataDialogParam& param);
SaveDialogState() = default;
[[nodiscard]] SaveDataDialogMode GetMode() const {
return mode;
}
template <typename T>
[[nodiscard]] T& GetState() {
return std::get<T>(state);
}
};
class SaveDialogUi final : public ImGui::Layer {
bool first_render{false};
SaveDialogState* state{};
CommonDialog::Status* status{};
SaveDialogResult* result{};
std::recursive_mutex draw_mutex{};
public:
explicit SaveDialogUi(SaveDialogState* state = nullptr, CommonDialog::Status* status = nullptr,
SaveDialogResult* result = nullptr);
~SaveDialogUi() override;
SaveDialogUi(const SaveDialogUi& other) = delete;
SaveDialogUi(SaveDialogUi&& other) noexcept;
SaveDialogUi& operator=(SaveDialogUi other);
void Finish(ButtonId buttonId, CommonDialog::Result r = CommonDialog::Result::OK);
void Draw() override;
private:
void DrawItem(int id, const SaveDialogState::Item& item, bool clickable = true);
void DrawList();
void DrawUser();
void DrawSystemMessage();
void DrawErrorCode();
void DrawProgressBar();
};
}; // namespace Libraries::SaveData::Dialog

View file

@ -107,8 +107,10 @@ SaveInstance::SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::st
param_sfo_path = sce_sys_path / "param.sfo";
corrupt_file_path = sce_sys_path / "corrupted";
mount_point = "/savedata" + std::to_string(slot_num);
this->exists = fs::exists(param_sfo_path);
this->mounted = false;
this->mounted = g_mnt->GetMount(mount_point) != nullptr;
}
SaveInstance::~SaveInstance() {
@ -183,7 +185,6 @@ void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_cor
max_blocks = GetMaxBlocks(save_path);
mount_point = "/savedata" + std::to_string(slot_num);
g_mnt->Mount(save_path, mount_point, read_only);
mounted = true;
this->read_only = read_only;
@ -204,6 +205,7 @@ void SaveInstance::Umount() {
corrupt_file.Close();
fs::remove(corrupt_file_path);
g_mnt->Unmount(save_path, mount_point);
}
void SaveInstance::CreateFiles() {

View file

@ -124,6 +124,10 @@ public:
return max_blocks;
}
[[nodiscard]] bool Mounted() const noexcept {
return mounted;
}
[[nodiscard]] bool IsReadOnly() const noexcept {
return read_only;
}

View file

@ -67,13 +67,7 @@ constexpr u32 OrbisSaveDataBlocksMax = 32768; // 1 GiB
// Maximum size for a mount point "/savedataN", where N is a number
constexpr size_t OrbisSaveDataMountPointDataMaxsize = 16;
// Maximum size for a title ID (4 uppercase letters + 5 digits)
constexpr int OrbisSaveDataTitleIdDataSize = 10;
constexpr int OrbisSaveDataDirnameDataMaxsize = 32; // Maximum save directory name size
constexpr size_t OrbisSaveDataTitleMaxsize = 128; // Maximum title name size
constexpr size_t OrbisSaveDataSubtitleMaxsize = 128; // Maximum subtitle name size
constexpr size_t OrbisSaveDataDetailMaxsize = 1024; // Maximum detail name size
constexpr size_t OrbisSaveDataFingerprintDataSize = 65; // Maximum fingerprint size
enum class OrbisSaveDataMountMode : u32 {
@ -113,43 +107,6 @@ enum class OrbisSaveDataSortOrder : u32 {
DESCENT = 1,
};
struct OrbisSaveDataTitleId {
CString<OrbisSaveDataTitleIdDataSize> data;
std::array<char, 6> _pad;
};
struct OrbisSaveDataDirName {
CString<OrbisSaveDataDirnameDataMaxsize> data;
};
struct OrbisSaveDataParam {
CString<OrbisSaveDataTitleMaxsize> title;
CString<OrbisSaveDataSubtitleMaxsize> subTitle;
CString<OrbisSaveDataDetailMaxsize> detail;
u32 userParam;
int : 32;
time_t mtime;
std::array<u8, 32> _reserved;
void FromSFO(const PSF& sfo) {
memset(this, 0, sizeof(OrbisSaveDataParam));
title.FromString(*sfo.GetString(SaveParams::MAINTITLE));
subTitle.FromString(*sfo.GetString(SaveParams::SUBTITLE));
detail.FromString(*sfo.GetString(SaveParams::DETAIL));
userParam = sfo.GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0);
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 {
CString<OrbisSaveDataFingerprintDataSize> data;
std::array<u8, 15> _pad;
@ -359,9 +316,9 @@ static void initialize() {
static bool match(std::string_view str, std::string_view pattern) {
auto str_it = str.begin();
auto pat_it = pattern.begin();
while (str_it != str.end() || pat_it != pattern.end()) {
while (str_it != str.end() && pat_it != pattern.end()) {
if (*pat_it == '%') { // 0 or more wildcard
for (auto str_wild_it = str_it; str_wild_it != str.end(); ++str_wild_it) {
for (auto str_wild_it = str_it; str_wild_it <= str.end(); ++str_wild_it) {
if (match({str_wild_it, str.end()}, {pat_it + 1, pattern.end()})) {
return true;
}
@ -377,7 +334,7 @@ static bool match(std::string_view str, std::string_view pattern) {
++str_it;
++pat_it;
}
return pat_it == pattern.end();
return str_it == str.end() && pat_it == pattern.end();
}
static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
@ -386,9 +343,8 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
if (mount_info->userId < 0) {
return Error::INVALID_LOGIN_USER;
}
if (mount_info->blocks < OrbisSaveDataBlocksMin2 ||
mount_info->blocks > OrbisSaveDataBlocksMax || mount_info->dirName == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
if (mount_info->dirName == nullptr) {
LOG_INFO(Lib_SaveData, "called without dirName");
return Error::PARAMETER;
}
@ -434,6 +390,10 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
SaveInstance save_instance{slot_num, mount_info->userId, g_game_serial, dir_name,
(int)mount_info->blocks};
if (save_instance.Mounted()) {
UNREACHABLE_MSG("Save instance should not be mounted");
}
if (!create && !create_if_not_exist && !save_instance.Exists()) {
return Error::NOT_FOUND;
}
@ -444,6 +404,12 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
bool to_be_created = !save_instance.Exists();
if (to_be_created) { // Check size
if (mount_info->blocks < OrbisSaveDataBlocksMin2 ||
mount_info->blocks > OrbisSaveDataBlocksMax) {
LOG_INFO(Lib_SaveData, "called with invalid block size");
}
const auto root_save = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir);
fs::create_directories(root_save);
const auto available = fs::space(root_save).available;
@ -511,6 +477,23 @@ static Error Umount(const OrbisSaveDataMountPoint* mountPoint, bool call_backup
return Error::NOT_FOUND;
}
void OrbisSaveDataParam::FromSFO(const PSF& sfo) {
memset(this, 0, sizeof(OrbisSaveDataParam));
title.FromString(*sfo.GetString(SaveParams::MAINTITLE));
subTitle.FromString(*sfo.GetString(SaveParams::SUBTITLE));
detail.FromString(*sfo.GetString(SaveParams::DETAIL));
userParam = sfo.GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0);
const auto time_since_epoch = sfo.GetLastWrite().time_since_epoch();
mtime = chrono::duration_cast<chrono::seconds>(time_since_epoch).count();
}
void OrbisSaveDataParam::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);
}
int PS4_SYSV_ABI sceSaveDataAbort() {
LOG_ERROR(Lib_SaveData, "(STUBBED) called");
return ORBIS_OK;
@ -716,6 +699,9 @@ Error PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del) {
}
const std::string_view dirName{del->dirName->data};
LOG_DEBUG(Lib_SaveData, "called dirName: {}", dirName);
if (dirName.empty()) {
return Error::PARAMETER;
}
for (const auto& instance : g_mount_slots) {
if (instance.has_value() && instance->GetDirName() == dirName) {
return Error::BUSY;

View file

@ -3,18 +3,54 @@
#pragma once
#include "common/cstring.h"
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
class PSF;
namespace Libraries::SaveData {
constexpr size_t OrbisSaveDataTitleMaxsize = 128; // Maximum title name size
constexpr size_t OrbisSaveDataSubtitleMaxsize = 128; // Maximum subtitle name size
constexpr size_t OrbisSaveDataDetailMaxsize = 1024; // Maximum detail name size
enum class Error : u32;
enum class OrbisSaveDataParamType : u32;
using OrbisUserServiceUserId = s32;
// Maximum size for a title ID (4 uppercase letters + 5 digits)
constexpr int OrbisSaveDataTitleIdDataSize = 10;
// Maximum save directory name size
constexpr int OrbisSaveDataDirnameDataMaxsize = 32;
struct OrbisSaveDataTitleId {
Common::CString<OrbisSaveDataTitleIdDataSize> data;
std::array<char, 6> _pad;
};
struct OrbisSaveDataDirName {
Common::CString<OrbisSaveDataDirnameDataMaxsize> data;
};
struct OrbisSaveDataParam {
Common::CString<OrbisSaveDataTitleMaxsize> title;
Common::CString<OrbisSaveDataSubtitleMaxsize> subTitle;
Common::CString<OrbisSaveDataDetailMaxsize> detail;
u32 userParam;
int : 32;
time_t mtime;
std::array<u8, 32> _reserved;
void FromSFO(const PSF& sfo);
void ToSFO(PSF& sfo) const;
};
struct OrbisSaveDataBackup;
struct OrbisSaveDataCheckBackupData;
struct OrbisSaveDataDelete;

View file

@ -33,18 +33,6 @@ struct {
};
static_assert(std::size(user_button_texts) == static_cast<int>(ButtonType::TWO_BUTTONS) + 1);
static void DrawCenteredText(const char* text) {
const auto ws = GetWindowSize();
const auto text_size = CalcTextSize(text, nullptr, false, ws.x - 40.0f);
PushTextWrapPos(ws.x - 30.0f);
SetCursorPos({
(ws.x - text_size.x) / 2.0f,
(ws.y - text_size.y) / 2.0f - 50.0f,
});
Text("%s", text);
PopTextWrapPos();
}
MsgDialogState::MsgDialogState(const OrbisParam& param) {
this->mode = param.mode;
switch (mode) {
@ -102,7 +90,10 @@ void MsgDialogUi::DrawUser() {
const auto& [button_type, msg, btn_param1, btn_param2] =
state->GetState<MsgDialogState::UserState>();
const auto ws = GetWindowSize();
DrawCenteredText(msg.c_str());
if (!msg.empty()) {
DrawCenteredText(&msg.front(), &msg.back() + 1,
GetContentRegionAvail() - ImVec2{0.0f, 15.0f + BUTTON_SIZE.y});
}
ASSERT(button_type <= ButtonType::TWO_BUTTONS);
auto [count, text1, text2] = user_button_texts[static_cast<u32>(button_type)];
if (count == 0xFF) { // TWO_BUTTONS -> User defined message
@ -132,7 +123,7 @@ void MsgDialogUi::DrawUser() {
break;
}
}
if (first_render && !focus_first) {
if ((first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) && !focus_first) {
SetItemCurrentNavFocus();
}
PopID();
@ -142,7 +133,7 @@ void MsgDialogUi::DrawUser() {
if (Button(text1, BUTTON_SIZE)) {
Finish(ButtonId::BUTTON1);
}
if (first_render && focus_first) {
if ((first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) && focus_first) {
SetItemCurrentNavFocus();
}
PopID();

View file

@ -171,8 +171,6 @@ public:
void Finish(ButtonId buttonId, CommonDialog::Result r = CommonDialog::Result::OK);
void SetProgressBarValue(u32 value, bool increment);
void Draw() override;
};

View file

@ -1,84 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/system/savedatadialog.h"
namespace Libraries::SaveDataDialog {
int PS4_SYSV_ABI sceSaveDataDialogClose() {
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataDialogGetResult() {
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataDialogGetStatus() {
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataDialogInitialize() {
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay() {
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
return 1;
}
int PS4_SYSV_ABI sceSaveDataDialogOpen() {
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataDialogProgressBarInc() {
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataDialogProgressBarSetValue() {
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataDialogTerminate() {
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceSaveDataDialogUpdateStatus() {
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
return 3; // SCE_COMMON_DIALOG_STATUS_FINISHED
}
void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("fH46Lag88XY", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogClose);
LIB_FUNCTION("yEiJ-qqr6Cg", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogGetResult);
LIB_FUNCTION("ERKzksauAJA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogGetStatus);
LIB_FUNCTION("s9e3+YpRnzw", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogInitialize);
LIB_FUNCTION("en7gNVnh878", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogIsReadyToDisplay);
LIB_FUNCTION("4tPhsP6FpDI", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogOpen);
LIB_FUNCTION("V-uEeFKARJU", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogProgressBarInc);
LIB_FUNCTION("hay1CfTmLyA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogProgressBarSetValue);
LIB_FUNCTION("YuH2FA7azqQ", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogTerminate);
LIB_FUNCTION("KK3Bdg1RWK0", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
sceSaveDataDialogUpdateStatus);
};
} // namespace Libraries::SaveDataDialog

View file

@ -1,26 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::SaveDataDialog {
int PS4_SYSV_ABI sceSaveDataDialogClose();
int PS4_SYSV_ABI sceSaveDataDialogGetResult();
int PS4_SYSV_ABI sceSaveDataDialogGetStatus();
int PS4_SYSV_ABI sceSaveDataDialogInitialize();
int PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay();
int PS4_SYSV_ABI sceSaveDataDialogOpen();
int PS4_SYSV_ABI sceSaveDataDialogProgressBarInc();
int PS4_SYSV_ABI sceSaveDataDialogProgressBarSetValue();
int PS4_SYSV_ABI sceSaveDataDialogTerminate();
int PS4_SYSV_ABI sceSaveDataDialogUpdateStatus();
void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::SaveDataDialog

View file

@ -3,12 +3,25 @@
#pragma once
#include <cmath>
#include <imgui.h>
#include "imgui_internal.h"
#define IM_COL32_GRAY(x) IM_COL32(x, x, x, 0xFF)
namespace ImGui {
namespace Easing {
inline float FastInFastOutCubic(float x) {
constexpr float c4 = 1.587401f; // 4^(1/3)
constexpr float c05 = 0.7937f; // 0.5^(1/3)
return std::pow(c4 * x - c05, 3.0f) + 0.5f;
}
} // namespace Easing
inline void CentralizeWindow() {
const auto display_size = GetIO().DisplaySize;
SetNextWindowPos(display_size / 2.0f, ImGuiCond_Always, {0.5f});
@ -18,10 +31,39 @@ inline void KeepNavHighlight() {
GetCurrentContext()->NavDisableHighlight = false;
}
inline void SetItemCurrentNavFocus() {
inline void SetItemCurrentNavFocus(const ImGuiID id = -1) {
const auto ctx = GetCurrentContext();
SetFocusID(ctx->LastItemData.ID, ctx->CurrentWindow);
SetFocusID(id == -1 ? ctx->LastItemData.ID : id, ctx->CurrentWindow);
ctx->NavInitResult.Clear();
ctx->NavDisableHighlight = false;
}
inline void DrawPrettyBackground() {
const double time = GetTime() / 1.5f;
const float x = ((float)std::cos(time) + 1.0f) / 2.0f;
const float d = Easing::FastInFastOutCubic(x);
u8 top_left = ImLerp(0x13, 0x05, d);
u8 top_right = ImLerp(0x00, 0x07, d);
u8 bottom_right = ImLerp(0x03, 0x27, d);
u8 bottom_left = ImLerp(0x05, 0x00, d);
auto& window = *GetCurrentWindowRead();
auto inner_pos = window.DC.CursorPos - window.WindowPadding;
auto inner_size = GetContentRegionAvail() + window.WindowPadding * 2.0f;
GetWindowDrawList()->AddRectFilledMultiColor(
inner_pos, inner_pos + inner_size, IM_COL32_GRAY(top_left), IM_COL32_GRAY(top_right),
IM_COL32_GRAY(bottom_right), IM_COL32_GRAY(bottom_left));
}
static void DrawCenteredText(const char* text, const char* text_end = nullptr,
ImVec2 content = GetContentRegionAvail()) {
auto pos = GetCursorPos();
const auto text_size = CalcTextSize(text, text_end, false, content.x - 40.0f);
PushTextWrapPos(content.x);
SetCursorPos(pos + (content - text_size) / 2.0f);
TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
PopTextWrapPos();
SetCursorPos(pos + content);
}
} // namespace ImGui