mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-04-22 04:24:44 +00:00
SaveDataDialog implementation
- Fix Mounting/Unmounting check of SaveInstance
This commit is contained in:
parent
d059ea5ac3
commit
edfbc70075
17 changed files with 1455 additions and 194 deletions
|
@ -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
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
163
src/core/libraries/save_data/dialog/savedatadialog.cpp
Normal file
163
src/core/libraries/save_data/dialog/savedatadialog.cpp
Normal 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
|
33
src/core/libraries/save_data/dialog/savedatadialog.h
Normal file
33
src/core/libraries/save_data/dialog/savedatadialog.h
Normal 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
|
802
src/core/libraries/save_data/dialog/savedatadialog_ui.cpp
Normal file
802
src/core/libraries/save_data/dialog/savedatadialog_ui.cpp
Normal 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
|
317
src/core/libraries/save_data/dialog/savedatadialog_ui.h
Normal file
317
src/core/libraries/save_data/dialog/savedatadialog_ui.h
Normal 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
|
|
@ -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() {
|
||||
|
|
|
@ -124,6 +124,10 @@ public:
|
|||
return max_blocks;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Mounted() const noexcept {
|
||||
return mounted;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsReadOnly() const noexcept {
|
||||
return read_only;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -171,8 +171,6 @@ public:
|
|||
|
||||
void Finish(ButtonId buttonId, CommonDialog::Result r = CommonDialog::Result::OK);
|
||||
|
||||
void SetProgressBarValue(u32 value, bool increment);
|
||||
|
||||
void Draw() override;
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue