Save fixes (#1031)

* Add ElfInfo to track current game info in a singleton

* SaveData compatibility with old firmwares

* sceKernelOpen: fix for write-only mode

* imgui: add font to render non-ascii characters

* save_data: fix Backup Job including old backup in the new backup

* Save backup: fix to avoid filling the queue

 Also limiting 1 backup / 10sec

* Save backup: fix search not handling empty pattern

*backup time improv
This commit is contained in:
Vinicius Rangel 2024-09-23 08:50:49 -03:00 committed by GitHub
parent a5001d11a8
commit 10d29cc007
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 421 additions and 105 deletions

72
src/common/elf_info.h Normal file
View file

@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include <string_view>
#include "assert.h"
#include "singleton.h"
#include "types.h"
namespace Core {
class Emulator;
}
namespace Common {
class ElfInfo {
friend class Core::Emulator;
bool initialized = false;
std::string game_serial{};
std::string title{};
std::string app_ver{};
u32 firmware_ver = 0;
u32 raw_firmware_ver = 0;
public:
static constexpr u32 FW_15 = 0x1500000;
static constexpr u32 FW_16 = 0x1600000;
static constexpr u32 FW_17 = 0x1700000;
static constexpr u32 FW_20 = 0x2000000;
static constexpr u32 FW_25 = 0x2500000;
static constexpr u32 FW_30 = 0x3000000;
static constexpr u32 FW_40 = 0x4000000;
static constexpr u32 FW_45 = 0x4500000;
static constexpr u32 FW_50 = 0x5000000;
static constexpr u32 FW_80 = 0x8000000;
static ElfInfo& Instance() {
return *Singleton<ElfInfo>::Instance();
}
[[nodiscard]] std::string_view GameSerial() const {
ASSERT(initialized);
return Instance().game_serial;
}
[[nodiscard]] std::string_view Title() const {
ASSERT(initialized);
return title;
}
[[nodiscard]] std::string_view AppVer() const {
ASSERT(initialized);
return app_ver;
}
[[nodiscard]] u32 FirmwareVer() const {
ASSERT(initialized);
return firmware_ver;
}
[[nodiscard]] u32 RawFirmwareVer() const {
ASSERT(initialized);
return raw_firmware_ver;
}
};
} // namespace Common

View file

@ -89,6 +89,8 @@ int PS4_SYSV_ABI sceKernelOpen(const char* path, int flags, u16 mode) {
}
// RW, then scekernelWrite is called and savedata is written just fine now.
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::ReadWrite);
} else if (write) {
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Write);
} else {
UNREACHABLE();
}

View file

@ -8,6 +8,7 @@
#include "common/assert.h"
#include "common/debug.h"
#include "common/elf_info.h"
#include "common/logging/log.h"
#include "common/polyfill_thread.h"
#include "common/singleton.h"
@ -243,8 +244,7 @@ int PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time,
}
int PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(int* ver) {
auto* param_sfo = Common::Singleton<PSF>::Instance();
int version = param_sfo->GetInteger("SYSTEM_VER").value_or(0x4700000);
int version = Common::ElfInfo::Instance().RawFirmwareVer();
LOG_INFO(Kernel, "returned system version = {:#x}", version);
*ver = version;
return (version > 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL;

View file

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/elf_info.h"
#include "common/logging/log.h"
#include "core/libraries/libs.h"
#include "core/libraries/system/commondialog.h"

View file

@ -5,6 +5,7 @@
#include <imgui.h>
#include <magic_enum.hpp>
#include "common/elf_info.h"
#include "common/singleton.h"
#include "common/string_util.h"
#include "core/file_sys/fs.h"
@ -14,6 +15,7 @@
using namespace ImGui;
using namespace Libraries::CommonDialog;
using Common::ElfInfo;
constexpr u32 OrbisSaveDataBlockSize = 32768; // 32 KiB
@ -47,7 +49,7 @@ void SaveDialogResult::CopyTo(OrbisSaveDataDialogResult& result) const {
result.mode = this->mode;
result.result = this->result;
result.buttonId = this->button_id;
if (has_item) {
if (mode == SaveDataDialogMode::LIST || ElfInfo::Instance().FirmwareVer() >= ElfInfo::FW_45) {
if (result.dirName != nullptr) {
result.dirName->data.FromString(this->dir_name);
}
@ -66,9 +68,7 @@ SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) {
this->enable_back = {param.optionParam->back == OptionBack::ENABLE};
}
const auto content_id = Common::Singleton<PSF>::Instance()->GetString("CONTENT_ID");
ASSERT_MSG(content_id.has_value(), "Failed to get CONTENT_ID");
static std::string game_serial{*content_id, 7, 9};
const auto& game_serial = Common::ElfInfo::Instance().GameSerial();
const auto item = param.items;
this->user_id = item->userId;
@ -203,6 +203,7 @@ SaveDialogState::SystemState::SystemState(const SaveDialogState& state,
auto& sys = *param.sysMsgParam;
switch (sys.msgType) {
case SystemMessageType::NODATA: {
return_cancel = true;
this->msg = "There is no saved data";
} break;
case SystemMessageType::CONFIRM:
@ -215,6 +216,7 @@ SaveDialogState::SystemState::SystemState(const SaveDialogState& state,
M("Do you want to overwrite the existing saved data?", "##UNKNOWN##", "##UNKNOWN##");
break;
case SystemMessageType::NOSPACE:
return_cancel = true;
M(fmt::format(
"There is not enough space to save the data. To continue {} free space is required.",
SpaceSizeToString(sys.value * OrbisSaveDataBlockSize)),
@ -226,12 +228,15 @@ SaveDialogState::SystemState::SystemState(const SaveDialogState& state,
M("Saving...", "Loading...", "Deleting...");
break;
case SystemMessageType::FILE_CORRUPTED:
return_cancel = true;
this->msg = "The saved data is corrupted.";
break;
case SystemMessageType::FINISHED:
return_cancel = true;
M("Saved successfully.", "Loading complete.", "Deletion complete.");
break;
case SystemMessageType::NOSPACE_CONTINUABLE:
return_cancel = true;
M(fmt::format("There is not enough space to save the data. {} free space is required.",
SpaceSizeToString(sys.value * OrbisSaveDataBlockSize)),
"##UNKNOWN##", "##UNKNOWN##");
@ -283,29 +288,36 @@ SaveDialogState::ErrorCodeState::ErrorCodeState(const OrbisSaveDataDialogParam&
}
SaveDialogState::ProgressBarState::ProgressBarState(const SaveDialogState& state,
const OrbisSaveDataDialogParam& param) {
static auto fw_ver = ElfInfo::Instance().FirmwareVer();
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...";
if (bar.msg != nullptr) {
this->msg = std::string{bar.msg};
} else {
switch (bar.sysMsgType) {
case ProgressSystemMessageType::INVALID:
this->msg = "INVALID";
break;
case DialogType::LOAD:
this->msg = "Loading...";
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 DialogType::DELETE:
this->msg = "Deleting...";
case ProgressSystemMessageType::RESTORE:
this->msg = "Restoring saved data...";
break;
}
break;
case ProgressSystemMessageType::RESTORE:
this->msg = "Restoring saved data...";
break;
}
}
@ -353,11 +365,8 @@ void SaveDialogUi::Finish(ButtonId buttonId, Result r) {
result->result = r;
result->button_id = buttonId;
result->user_data = this->state->user_data;
if (state) {
if (state->mode != SaveDataDialogMode::LIST && !state->save_list.empty()) {
result->dir_name = state->save_list.front().dir_name;
}
result->has_item = state->mode == SaveDataDialogMode::LIST || !state->save_list.empty();
if (state && state->mode != SaveDataDialogMode::LIST && !state->save_list.empty()) {
result->dir_name = state->save_list.front().dir_name;
}
}
if (status) {
@ -385,7 +394,7 @@ void SaveDialogUi::Draw() {
};
} else {
window_size = ImVec2{
std::min(io.DisplaySize.x, 500.0f),
std::min(io.DisplaySize.x, 600.0f),
std::min(io.DisplaySize.y, 300.0f),
};
}
@ -453,7 +462,7 @@ void SaveDialogUi::Draw() {
}
void SaveDialogUi::DrawItem(int _id, const SaveDialogState::Item& item, bool clickable) {
constexpr auto text_spacing = 1.2f;
constexpr auto text_spacing = 0.95f;
auto& ctx = *GetCurrentContext();
auto& window = *ctx.CurrentWindow;
@ -502,18 +511,20 @@ void SaveDialogUi::DrawItem(int _id, const SaveDialogState::Item& item, bool cli
if (!item.title.empty()) {
const char* begin = &item.title.front();
const char* end = &item.title.back() + 1;
SetWindowFontScale(2.0f);
SetWindowFontScale(1.5f);
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.1f);
SetWindowFontScale(1.3f);
if (item.is_corrupted) {
pos_y -= ctx.FontSize * text_spacing * 0.3f;
const auto bright = (int)std::abs(std::sin(ctx.Time) * 0.15f * 255.0f);
PushStyleColor(ImGuiCol_Text, IM_COL32(bright + 216, bright, bright, 0xFF));
RenderText(pos + ImVec2{pos_x, pos_y}, "Corrupted", nullptr, false);
PopStyleColor();
pos_y += ctx.FontSize * text_spacing * 0.8f;
}
if (state->style == ItemStyle::TITLE_SUBTITLE_DATESIZE) {
if (!item.subtitle.empty()) {
@ -643,6 +654,8 @@ void SaveDialogUi::DrawUser() {
if (!state->save_list.empty()) {
DrawItem(0, state->save_list.front(), false);
} else if (state->new_item) {
DrawItem(0, *state->new_item, false);
}
auto has_btn = btn_type != ButtonType::NONE;
@ -667,7 +680,7 @@ void SaveDialogUi::DrawUser() {
if (has_btn) {
int count = 1;
if (btn_type == ButtonType::YESNO || btn_type == ButtonType::ONCANCEL) {
if (btn_type == ButtonType::YESNO || btn_type == ButtonType::OKCANCEL) {
++count;
}
@ -683,19 +696,28 @@ void SaveDialogUi::DrawUser() {
}
SameLine();
if (Button("No", BUTTON_SIZE)) {
Finish(ButtonId::NO);
if (ElfInfo::Instance().FirmwareVer() < ElfInfo::FW_45) {
Finish(ButtonId::INVALID, Result::USER_CANCELED);
} else {
Finish(ButtonId::NO);
}
}
if (first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) {
SetItemCurrentNavFocus();
}
} else {
if (Button("OK", BUTTON_SIZE)) {
Finish(ButtonId::OK);
if (btn_type == ButtonType::OK &&
ElfInfo::Instance().FirmwareVer() < ElfInfo::FW_45) {
Finish(ButtonId::INVALID, Result::USER_CANCELED);
} else {
Finish(ButtonId::OK);
}
}
if (first_render) {
SetItemCurrentNavFocus();
}
if (btn_type == ButtonType::ONCANCEL) {
if (btn_type == ButtonType::OKCANCEL) {
SameLine();
if (Button("Cancel", BUTTON_SIZE)) {
Finish(ButtonId::INVALID, Result::USER_CANCELED);
@ -714,6 +736,8 @@ void SaveDialogUi::DrawSystemMessage() {
if (!state->save_list.empty()) {
DrawItem(0, state->save_list.front(), false);
} else if (state->new_item) {
DrawItem(0, *state->new_item, false);
}
const auto ws = GetWindowSize();
@ -737,12 +761,20 @@ void SaveDialogUi::DrawSystemMessage() {
});
BeginGroup();
if (Button(sys_state.show_no ? "Yes" : "OK", BUTTON_SIZE)) {
Finish(ButtonId::YES);
if (sys_state.return_cancel && ElfInfo::Instance().FirmwareVer() < ElfInfo::FW_45) {
Finish(ButtonId::INVALID, Result::USER_CANCELED);
} else {
Finish(ButtonId::YES);
}
}
SameLine();
if (sys_state.show_no) {
if (Button("No", BUTTON_SIZE)) {
Finish(ButtonId::NO);
if (ElfInfo::Instance().FirmwareVer() < ElfInfo::FW_45) {
Finish(ButtonId::INVALID, Result::USER_CANCELED);
} else {
Finish(ButtonId::NO);
}
}
} else if (sys_state.show_cancel) {
if (Button("Cancel", BUTTON_SIZE)) {
@ -760,6 +792,8 @@ void SaveDialogUi::DrawErrorCode() {
if (!state->save_list.empty()) {
DrawItem(0, state->save_list.front(), false);
} else if (state->new_item) {
DrawItem(0, *state->new_item, false);
}
const auto ws = GetWindowSize();
@ -775,7 +809,11 @@ void SaveDialogUi::DrawErrorCode() {
ws.y - FOOTER_HEIGHT + 5.0f,
});
if (Button("OK", BUTTON_SIZE)) {
Finish(ButtonId::OK);
if (ElfInfo::Instance().FirmwareVer() < ElfInfo::FW_45) {
Finish(ButtonId::INVALID, Result::USER_CANCELED);
} else {
Finish(ButtonId::OK);
}
}
if (first_render) {
SetItemCurrentNavFocus();
@ -789,6 +827,8 @@ void SaveDialogUi::DrawProgressBar() {
if (!state->save_list.empty()) {
DrawItem(0, state->save_list.front(), false);
} else if (state->new_item) {
DrawItem(0, *state->new_item, false);
}
const auto& msg = bar_state.msg;

View file

@ -48,7 +48,7 @@ enum class ButtonType : u32 {
OK = 0,
YESNO = 1,
NONE = 2,
ONCANCEL = 3,
OKCANCEL = 3,
};
enum class UserMessageType : u32 {
@ -201,7 +201,6 @@ struct SaveDialogResult {
std::string dir_name{};
PSF param{};
void* user_data{};
bool has_item{false};
void CopyTo(OrbisSaveDataDialogResult& result) const;
};
@ -223,6 +222,8 @@ public:
bool show_no{}; // Yes instead of OK
bool show_cancel{};
bool return_cancel{};
SystemState(const SaveDialogState& state, const OrbisSaveDataDialogParam& param);
};
struct ErrorCodeState {

View file

@ -52,7 +52,7 @@ static void backup(const std::filesystem::path& dir_name) {
std::vector<std::filesystem::path> backup_files;
for (const auto& entry : fs::directory_iterator(dir_name)) {
const auto filename = entry.path().filename();
if (filename != backup_dir) {
if (filename != ::backup_dir) {
backup_files.push_back(entry.path());
}
}
@ -80,18 +80,33 @@ static void backup(const std::filesystem::path& dir_name) {
static void BackupThreadBody() {
Common::SetCurrentThreadName("SaveData_BackupThread");
while (true) {
while (g_backup_status != WorkerStatus::Stopping) {
g_backup_status = WorkerStatus::Waiting;
g_backup_thread_semaphore.acquire();
bool wait;
BackupRequest req;
{
std::scoped_lock lk{g_backup_queue_mutex};
req = g_backup_queue.front();
wait = g_backup_queue.empty();
if (!wait) {
req = g_backup_queue.front();
}
}
if (wait) {
g_backup_thread_semaphore.acquire();
{
std::scoped_lock lk{g_backup_queue_mutex};
if (g_backup_queue.empty()) {
continue;
}
req = g_backup_queue.front();
}
}
if (req.save_path.empty()) {
break;
}
g_backup_status = WorkerStatus::Running;
LOG_INFO(Lib_SaveData, "Backing up the following directory: {}", req.save_path.string());
try {
backup(req.save_path);
@ -100,6 +115,11 @@ static void BackupThreadBody() {
}
LOG_DEBUG(Lib_SaveData, "Backing up the following directory: {} finished",
req.save_path.string());
{
std::scoped_lock lk{g_backup_queue_mutex};
g_backup_queue.front().done = true;
}
std::this_thread::sleep_for(std::chrono::seconds(10)); // Don't backup too often
{
std::scoped_lock lk{g_backup_queue_mutex};
g_backup_queue.pop_front();
@ -117,8 +137,8 @@ void StartThread() {
return;
}
LOG_DEBUG(Lib_SaveData, "Starting backup thread");
g_backup_thread = std::jthread{BackupThreadBody};
g_backup_status = WorkerStatus::Waiting;
g_backup_thread = std::jthread{BackupThreadBody};
}
void StopThread() {
@ -145,6 +165,12 @@ bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id,
}
{
std::scoped_lock lk{g_backup_queue_mutex};
for (const auto& it : g_backup_queue) {
if (it.dir_name == dir_name) {
LOG_TRACE(Lib_SaveData, "Backup request to {} ignored. Already queued", dir_name);
return false;
}
}
g_backup_queue.push_back(BackupRequest{
.user_id = user_id,
.title_id = std::string{title_id},
@ -184,8 +210,9 @@ WorkerStatus GetWorkerStatus() {
bool IsBackupExecutingFor(const std::filesystem::path& save_path) {
std::scoped_lock lk{g_backup_queue_mutex};
return std::ranges::find(g_backup_queue, save_path,
[](const auto& v) { return v.save_path; }) != g_backup_queue.end();
const auto& it =
std::ranges::find(g_backup_queue, save_path, [](const auto& v) { return v.save_path; });
return it != g_backup_queue.end() && !it->done;
}
std::filesystem::path MakeBackupPath(const std::filesystem::path& save_path) {

View file

@ -28,6 +28,8 @@ enum class OrbisSaveDataEventType : u32 {
};
struct BackupRequest {
bool done{};
OrbisUserServiceUserId user_id{};
std::string title_id{};
std::string dir_name{};

View file

@ -9,10 +9,10 @@
#include "common/assert.h"
#include "common/cstring.h"
#include "common/elf_info.h"
#include "common/enum.h"
#include "common/logging/log.h"
#include "common/path_util.h"
#include "common/singleton.h"
#include "common/string_util.h"
#include "core/file_format/psf.h"
#include "core/file_sys/fs.h"
@ -28,11 +28,13 @@ namespace fs = std::filesystem;
namespace chrono = std::chrono;
using Common::CString;
using Common::ElfInfo;
namespace Libraries::SaveData {
enum class Error : u32 {
OK = 0,
USER_SERVICE_NOT_INITIALIZED = 0x80960002,
PARAMETER = 0x809F0000,
NOT_INITIALIZED = 0x809F0001,
OUT_OF_MEMORY = 0x809F0002,
@ -191,7 +193,9 @@ struct OrbisSaveDataMemorySetup2 {
OrbisUserServiceUserId userId;
size_t memorySize;
size_t iconMemorySize;
// +4.5
const OrbisSaveDataParam* initParam;
// +4.5
const OrbisSaveDataIcon* initIcon;
std::array<u8, 24> _reserved;
};
@ -241,6 +245,7 @@ struct OrbisSaveDataMountResult {
OrbisSaveDataMountPoint mount_point;
OrbisSaveDataBlocks required_blocks;
u32 _unused;
// +4.5
OrbisSaveDataMountStatus mount_status;
std::array<u8, 28> _reserved;
s32 : 32;
@ -278,8 +283,11 @@ struct OrbisSaveDataDirNameSearchResult {
int : 32;
OrbisSaveDataDirName* dirNames;
u32 dirNamesNum;
// +1.7
u32 setNum;
// +1.7
OrbisSaveDataParam* params;
// +2.5
OrbisSaveDataSearchInfo* infos;
std::array<u8, 12> _reserved;
int : 32;
@ -303,14 +311,13 @@ struct OrbisSaveDataEvent {
static bool g_initialized = false;
static std::string g_game_serial;
static u32 g_fw_ver;
static std::array<std::optional<SaveInstance>, 16> g_mount_slots;
static void initialize() {
g_initialized = true;
static auto* param_sfo = Common::Singleton<PSF>::Instance();
const auto content_id = param_sfo->GetString("CONTENT_ID");
ASSERT_MSG(content_id.has_value(), "Failed to get CONTENT_ID");
g_game_serial = std::string(*content_id, 7, 9);
g_game_serial = ElfInfo::Instance().GameSerial();
g_fw_ver = ElfInfo::Instance().FirmwareVer();
}
// game_00other | game*other
@ -339,6 +346,16 @@ static bool match(std::string_view str, std::string_view pattern) {
return str_it == str.end() && pat_it == pattern.end();
}
static Error setNotInitializedError() {
if (g_fw_ver < ElfInfo::FW_20) {
return Error::INTERNAL;
}
if (g_fw_ver < ElfInfo::FW_25) {
return Error::USER_SERVICE_NOT_INITIALIZED;
}
return Error::NOT_INITIALIZED;
}
static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
OrbisSaveDataMountResult* mount_result) {
@ -354,7 +371,7 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
{
const auto save_path = SaveInstance::MakeDirSavePath(mount_info->userId, g_game_serial,
mount_info->dirName->data);
if (Backup::IsBackupExecutingFor(save_path)) {
if (Backup::IsBackupExecutingFor(save_path) && g_fw_ver) {
return Error::BACKUP_BUSY;
}
}
@ -363,11 +380,14 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
const bool is_ro = True(mount_mode & OrbisSaveDataMountMode::RDONLY);
const bool create = True(mount_mode & OrbisSaveDataMountMode::CREATE);
const bool create_if_not_exist = True(mount_mode & OrbisSaveDataMountMode::CREATE2);
const bool create_if_not_exist =
True(mount_mode & OrbisSaveDataMountMode::CREATE2) && g_fw_ver >= ElfInfo::FW_45;
ASSERT(!create || !create_if_not_exist); // Can't have both
const bool copy_icon = True(mount_mode & OrbisSaveDataMountMode::COPY_ICON);
const bool ignore_corrupt = True(mount_mode & OrbisSaveDataMountMode::DESTRUCT_OFF);
const bool ignore_corrupt =
True(mount_mode & OrbisSaveDataMountMode::DESTRUCT_OFF) || g_fw_ver < ElfInfo::FW_16;
const std::string_view dir_name{mount_info->dirName->data};
@ -439,9 +459,11 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
mount_result->mount_point.data.FromString(save_instance.GetMountPoint());
mount_result->mount_status = create_if_not_exist && to_be_created
? OrbisSaveDataMountStatus::CREATED
: OrbisSaveDataMountStatus::NOTHING;
if (g_fw_ver >= ElfInfo::FW_45) {
mount_result->mount_status = create_if_not_exist && to_be_created
? OrbisSaveDataMountStatus::CREATED
: OrbisSaveDataMountStatus::NOTHING;
}
g_mount_slots[slot_num].emplace(std::move(save_instance));
@ -451,7 +473,7 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
static Error Umount(const OrbisSaveDataMountPoint* mountPoint, bool call_backup = false) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
if (mountPoint == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
@ -504,7 +526,7 @@ int PS4_SYSV_ABI sceSaveDataAbort() {
Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
if (backup == nullptr || backup->dirName == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
@ -553,12 +575,13 @@ int PS4_SYSV_ABI sceSaveDataChangeInternal() {
Error PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
if (check == nullptr || check->dirName == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
return Error::PARAMETER;
}
LOG_DEBUG(Lib_SaveData, "called with titleId={}", check->titleId->data.to_view());
const std::string_view title{check->titleId != nullptr ? std::string_view{check->titleId->data}
: std::string_view{g_game_serial}};
@ -638,7 +661,7 @@ int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersionLatest() {
Error PS4_SYSV_ABI sceSaveDataClearProgress() {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
LOG_DEBUG(Lib_SaveData, "called");
Backup::ClearProgress();
@ -693,7 +716,7 @@ int PS4_SYSV_ABI sceSaveDataDebugTarget() {
Error PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
if (del == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
@ -745,7 +768,7 @@ Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond
OrbisSaveDataDirNameSearchResult* result) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
if (cond == nullptr || result == nullptr || cond->key > OrbisSaveDataSortKey::FREE_BLOCKS ||
cond->order > OrbisSaveDataSortOrder::DESCENT) {
@ -760,7 +783,9 @@ Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond
if (!fs::exists(save_path)) {
result->hitNum = 0;
result->setNum = 0;
if (g_fw_ver >= ElfInfo::FW_17) {
result->setNum = 0;
}
return Error::OK;
}
@ -777,9 +802,11 @@ Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond
if (cond->dirName != nullptr) {
// Filter names
const auto pat = Common::ToLower(std::string_view{cond->dirName->data});
std::erase_if(dir_list, [&](const std::string& dir_name) {
return !match(Common::ToLower(dir_name), pat);
});
if (!pat.empty()) {
std::erase_if(dir_list, [&](const std::string& dir_name) {
return !match(Common::ToLower(dir_name), pat);
});
}
}
std::unordered_map<std::string, PSF> map_dir_sfo;
@ -828,21 +855,25 @@ Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond
std::ranges::reverse(dir_list);
}
result->hitNum = dir_list.size();
size_t max_count = std::min(static_cast<size_t>(result->dirNamesNum), dir_list.size());
result->setNum = max_count;
if (g_fw_ver >= ElfInfo::FW_17) {
result->hitNum = dir_list.size();
result->setNum = max_count;
} else {
result->hitNum = max_count;
}
for (size_t i = 0; i < max_count; i++) {
auto& name_data = result->dirNames[i].data;
name_data.FromString(dir_list[i]);
if (result->params != nullptr) {
if (g_fw_ver >= ElfInfo::FW_17 && result->params != nullptr) {
auto& sfo = map_dir_sfo.at(dir_list[i]);
auto& param_data = result->params[i];
param_data.FromSFO(sfo);
}
if (result->infos != nullptr) {
if (g_fw_ver >= ElfInfo::FW_25 && result->infos != nullptr) {
auto& info = result->infos[i];
info.blocks = map_max_blocks.at(dir_list[i]);
info.freeBlocks = map_free_size.at(dir_list[i]);
@ -916,7 +947,7 @@ Error PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam*,
OrbisSaveDataEvent* event) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
if (event == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
@ -952,7 +983,7 @@ Error PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountP
OrbisSaveDataMountInfo* info) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
if (mountPoint == nullptr || info == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
@ -977,7 +1008,7 @@ Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint
size_t paramBufSize, size_t* gotSize) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
if (paramType > OrbisSaveDataParamType::MTIME || paramBuf == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
@ -1052,7 +1083,7 @@ Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint
Error PS4_SYSV_ABI sceSaveDataGetProgress(float* progress) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
if (progress == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
@ -1086,7 +1117,7 @@ Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const OrbisUserServiceUserId use
Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
if (getParam == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
@ -1182,7 +1213,7 @@ Error PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint
OrbisSaveDataIcon* icon) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
if (mountPoint == nullptr || icon == nullptr || icon->buf == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
@ -1211,7 +1242,7 @@ Error PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount,
OrbisSaveDataMountResult* mount_result) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
if (mount == nullptr && mount->dirName != nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
@ -1232,7 +1263,7 @@ Error PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount,
OrbisSaveDataMountResult* mount_result) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
if (mount == nullptr && mount->dirName != nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
@ -1276,7 +1307,7 @@ int PS4_SYSV_ABI sceSaveDataRegisterEventCallback() {
Error PS4_SYSV_ABI sceSaveDataRestoreBackupData(const OrbisSaveDataRestoreBackupData* restore) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
if (restore == nullptr || restore->dirName == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
@ -1327,7 +1358,7 @@ Error PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint
const OrbisSaveDataIcon* icon) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
if (mountPoint == nullptr || icon == nullptr || icon->buf == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
@ -1375,7 +1406,7 @@ Error PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint
size_t paramBufSize) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
if (paramType > OrbisSaveDataParamType::USER_PARAM || mountPoint == nullptr ||
paramBuf == nullptr) {
@ -1440,13 +1471,15 @@ Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, v
OrbisSaveDataMemorySet2 setParam{};
setParam.userId = userId;
setParam.data = &data;
setParam.param = nullptr;
setParam.icon = nullptr;
return sceSaveDataSetSaveDataMemory2(&setParam);
}
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
if (setParam == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
@ -1479,17 +1512,35 @@ Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2*
return Error::OK;
}
int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(/*u32 userId, size_t memorySize,
OrbisSaveDataParam* param*/) {
LOG_ERROR(Lib_SaveData, "(STUBBED) called");
return ORBIS_OK;
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(OrbisUserServiceUserId userId, size_t memorySize,
OrbisSaveDataParam* param) {
LOG_DEBUG(Lib_SaveData, "called: userId = {}, memorySize = {}", userId, memorySize);
OrbisSaveDataMemorySetup2 setupParam{};
setupParam.userId = userId;
setupParam.memorySize = memorySize;
setupParam.initParam = nullptr;
setupParam.initIcon = nullptr;
OrbisSaveDataMemorySetupResult result{};
const auto res = sceSaveDataSetupSaveDataMemory2(&setupParam, &result);
if (res != Error::OK) {
return res;
}
if (param != nullptr) {
OrbisSaveDataMemorySet2 setParam{};
setParam.userId = userId;
setParam.data = nullptr;
setParam.param = param;
setParam.icon = nullptr;
sceSaveDataSetSaveDataMemory2(&setParam);
}
return Error::OK;
}
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam,
OrbisSaveDataMemorySetupResult* result) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
if (setupParam == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
@ -1509,20 +1560,20 @@ Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetu
try {
size_t existed_size = SaveMemory::CreateSaveMemory(setupParam->memorySize);
if (existed_size == 0) { // Just created
if (setupParam->initParam != nullptr) {
if (g_fw_ver >= ElfInfo::FW_45 && setupParam->initParam != nullptr) {
auto& sfo = SaveMemory::GetParamSFO();
setupParam->initParam->ToSFO(sfo);
}
SaveMemory::SaveSFO();
auto init_icon = setupParam->initIcon;
if (init_icon != nullptr) {
if (g_fw_ver >= ElfInfo::FW_45 && init_icon != nullptr) {
SaveMemory::SetIcon(init_icon->buf, init_icon->bufSize);
} else {
SaveMemory::SetIcon(nullptr, 0);
}
}
if (result != nullptr) {
if (g_fw_ver >= ElfInfo::FW_45 && result != nullptr) {
result->existedMemorySize = existed_size;
}
} catch (const fs::filesystem_error& e) {
@ -1558,7 +1609,7 @@ int PS4_SYSV_ABI sceSaveDataSyncCloudList() {
Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam) {
if (!g_initialized) {
LOG_INFO(Lib_SaveData, "called without initialize");
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
if (syncParam == nullptr) {
LOG_INFO(Lib_SaveData, "called with invalid parameter");
@ -1579,11 +1630,15 @@ Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncPa
Error PS4_SYSV_ABI sceSaveDataTerminate() {
LOG_DEBUG(Lib_SaveData, "called");
if (!g_initialized) {
return Error::NOT_INITIALIZED;
return setNotInitializedError();
}
for (const auto& instance : g_mount_slots) {
for (auto& instance : g_mount_slots) {
if (instance.has_value()) {
return Error::BUSY;
if (g_fw_ver >= ElfInfo::FW_40) {
return Error::BUSY;
}
instance->Umount();
instance.reset();
}
}
g_initialized = false;

View file

@ -165,8 +165,8 @@ int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser();
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf,
size_t bufSize, int64_t offset);
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam);
int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(/*u32 userId, size_t memorySize,
OrbisSaveDataParam* param*/);
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(OrbisUserServiceUserId userId, size_t memorySize,
OrbisSaveDataParam* param);
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam,
OrbisSaveDataMemorySetupResult* result);
int PS4_SYSV_ABI sceSaveDataShutdownStart();

View file

@ -11,6 +11,7 @@
#include "common/memory_patcher.h"
#endif
#include "common/assert.h"
#include "common/elf_info.h"
#include "common/ntapi.h"
#include "common/path_util.h"
#include "common/polyfill_thread.h"
@ -91,10 +92,14 @@ void Emulator::Run(const std::filesystem::path& file) {
// Certain games may use /hostapp as well such as CUSA001100
mnt->Mount(file.parent_path(), "/hostapp");
auto& game_info = Common::ElfInfo::Instance();
// Loading param.sfo file if exists
std::string id;
std::string title;
std::string app_version;
u32 fw_version;
std::filesystem::path sce_sys_folder = file.parent_path() / "sce_sys";
if (std::filesystem::is_directory(sce_sys_folder)) {
for (const auto& entry : std::filesystem::directory_iterator(sce_sys_folder)) {
@ -119,7 +124,7 @@ void Emulator::Run(const std::filesystem::path& file) {
#endif
title = param_sfo->GetString("TITLE").value_or("Unknown title");
LOG_INFO(Loader, "Game id: {} Title: {}", id, title);
u32 fw_version = param_sfo->GetInteger("SYSTEM_VER").value_or(0x4700000);
fw_version = param_sfo->GetInteger("SYSTEM_VER").value_or(0x4700000);
app_version = param_sfo->GetString("APP_VER").value_or("Unknown version");
LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version);
} else if (entry.path().filename() == "playgo-chunk.dat") {
@ -141,6 +146,13 @@ void Emulator::Run(const std::filesystem::path& file) {
}
}
game_info.initialized = true;
game_info.game_serial = id;
game_info.title = title;
game_info.app_ver = app_version;
game_info.firmware_ver = fw_version & 0xFFF00000;
game_info.raw_firmware_ver = fw_version;
std::string game_title = fmt::format("{} - {} <{}>", id, title, app_version);
std::string window_title = "";
if (Common::isRelease) {

View file

@ -0,0 +1,32 @@
# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
project(ImGui_Resources)
add_executable(Dear_ImGui_FontEmbed ${CMAKE_SOURCE_DIR}/externals/dear_imgui/misc/fonts/binary_to_compressed_c.cpp)
set(FONT_LIST
NotoSansJP-Regular.ttf
)
set(OutputList "")
FOREACH (FONT_FILE ${FONT_LIST})
string(REGEX REPLACE "-" "_" fontname ${FONT_FILE})
string(TOLOWER ${fontname} fontname)
string(REGEX REPLACE ".ttf" "" fontname_cpp ${fontname})
set(fontname_cpp "imgui_font_${fontname_cpp}")
MESSAGE(STATUS "Embedding font ${FONT_FILE}")
set(OUTPUT "generated_fonts/imgui_fonts/${fontname}")
add_custom_command(
OUTPUT "${OUTPUT}.g.cpp"
COMMAND ${CMAKE_COMMAND} -E make_directory "generated_fonts/imgui_fonts"
COMMAND $<TARGET_FILE:Dear_ImGui_FontEmbed> -nostatic "${CMAKE_CURRENT_SOURCE_DIR}/fonts/${FONT_FILE}" ${fontname_cpp} > "${OUTPUT}.g.cpp"
DEPENDS Dear_ImGui_FontEmbed "fonts/${FONT_FILE}"
USES_TERMINAL
)
list(APPEND OutputList "${OUTPUT}.g.cpp")
ENDFOREACH ()
add_library(ImGui_Resources STATIC ${OutputList})
set(IMGUI_RESOURCES_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/generated_fonts PARENT_SCOPE)

Binary file not shown.

View file

@ -3,6 +3,7 @@
#include <SDL3/SDL_events.h>
#include <imgui.h>
#include "common/config.h"
#include "common/path_util.h"
#include "imgui/imgui_layer.h"
@ -14,6 +15,8 @@
#include "texture_manager.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "imgui_fonts/notosansjp_regular.ttf.g.cpp"
static void CheckVkResult(const vk::Result err) {
LOG_ERROR(ImGui, "Vulkan error {}", vk::to_string(err));
}
@ -50,6 +53,22 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w
io.DisplaySize = ImVec2((float)window.getWidth(), (float)window.getHeight());
io.IniFilename = SDL_strdup(config_path.string().c_str());
io.LogFilename = SDL_strdup(log_path.string().c_str());
ImFontGlyphRangesBuilder rb{};
rb.AddRanges(io.Fonts->GetGlyphRangesDefault());
rb.AddRanges(io.Fonts->GetGlyphRangesGreek());
rb.AddRanges(io.Fonts->GetGlyphRangesKorean());
rb.AddRanges(io.Fonts->GetGlyphRangesJapanese());
rb.AddRanges(io.Fonts->GetGlyphRangesCyrillic());
ImVector<ImWchar> ranges{};
rb.BuildRanges(&ranges);
ImFontConfig font_cfg{};
font_cfg.OversampleH = 2;
font_cfg.OversampleV = 1;
io.Fonts->AddFontFromMemoryCompressedTTF(imgui_font_notosansjp_regular_compressed_data,
imgui_font_notosansjp_regular_compressed_size, 16.0f,
&font_cfg, ranges.Data);
StyleColorsDark();
Sdl::Init(window.GetSdlWindow());