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

View File

@ -58,3 +58,7 @@ License: MIT
Files: externals/tracy/* Files: externals/tracy/*
Copyright: 2017-2024 Bartosz Taudul <wolf@nereid.pl> Copyright: 2017-2024 Bartosz Taudul <wolf@nereid.pl>
License: BSD-3-Clause License: BSD-3-Clause
Files: src/imgui/renderer/fonts/NotoSansJP-Regular.ttf
Copyright: 2012 Google Inc. All Rights Reserved.
License: OFL-1.1

View File

@ -360,6 +360,7 @@ set(COMMON src/common/logging/backend.cpp
src/common/debug.h src/common/debug.h
src/common/disassembler.cpp src/common/disassembler.cpp
src/common/disassembler.h src/common/disassembler.h
src/common/elf_info.h
src/common/endian.h src/common/endian.h
src/common/enum.h src/common/enum.h
src/common/io_file.cpp src/common/io_file.cpp
@ -810,6 +811,11 @@ add_subdirectory(${HOST_SHADERS_INCLUDE})
add_dependencies(shadps4 host_shaders) add_dependencies(shadps4 host_shaders)
target_include_directories(shadps4 PRIVATE ${HOST_SHADERS_INCLUDE}) target_include_directories(shadps4 PRIVATE ${HOST_SHADERS_INCLUDE})
# ImGui resources
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/imgui/renderer)
add_dependencies(shadps4 ImGui_Resources)
target_include_directories(shadps4 PRIVATE ${IMGUI_RESOURCES_INCLUDE})
if (ENABLE_QT_GUI) if (ENABLE_QT_GUI)
set_target_properties(shadps4 PROPERTIES set_target_properties(shadps4 PROPERTIES
# WIN32_EXECUTABLE ON # WIN32_EXECUTABLE ON

43
LICENSES/OFL-1.1.txt Normal file
View File

@ -0,0 +1,43 @@
SIL OPEN FONT LICENSE
Version 1.1 - 26 February 2007
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the copyright statement(s).
"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting, or substituting — in part or in whole — any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.

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. // RW, then scekernelWrite is called and savedata is written just fine now.
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::ReadWrite); 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 { } else {
UNREACHABLE(); UNREACHABLE();
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@
#include "common/memory_patcher.h" #include "common/memory_patcher.h"
#endif #endif
#include "common/assert.h" #include "common/assert.h"
#include "common/elf_info.h"
#include "common/ntapi.h" #include "common/ntapi.h"
#include "common/path_util.h" #include "common/path_util.h"
#include "common/polyfill_thread.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 // Certain games may use /hostapp as well such as CUSA001100
mnt->Mount(file.parent_path(), "/hostapp"); mnt->Mount(file.parent_path(), "/hostapp");
auto& game_info = Common::ElfInfo::Instance();
// Loading param.sfo file if exists // Loading param.sfo file if exists
std::string id; std::string id;
std::string title; std::string title;
std::string app_version; std::string app_version;
u32 fw_version;
std::filesystem::path sce_sys_folder = file.parent_path() / "sce_sys"; std::filesystem::path sce_sys_folder = file.parent_path() / "sce_sys";
if (std::filesystem::is_directory(sce_sys_folder)) { if (std::filesystem::is_directory(sce_sys_folder)) {
for (const auto& entry : std::filesystem::directory_iterator(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 #endif
title = param_sfo->GetString("TITLE").value_or("Unknown title"); title = param_sfo->GetString("TITLE").value_or("Unknown title");
LOG_INFO(Loader, "Game id: {} Title: {}", id, 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"); app_version = param_sfo->GetString("APP_VER").value_or("Unknown version");
LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version); LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version);
} else if (entry.path().filename() == "playgo-chunk.dat") { } 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 game_title = fmt::format("{} - {} <{}>", id, title, app_version);
std::string window_title = ""; std::string window_title = "";
if (Common::isRelease) { 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 <SDL3/SDL_events.h>
#include <imgui.h> #include <imgui.h>
#include "common/config.h" #include "common/config.h"
#include "common/path_util.h" #include "common/path_util.h"
#include "imgui/imgui_layer.h" #include "imgui/imgui_layer.h"
@ -14,6 +15,8 @@
#include "texture_manager.h" #include "texture_manager.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h" #include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "imgui_fonts/notosansjp_regular.ttf.g.cpp"
static void CheckVkResult(const vk::Result err) { static void CheckVkResult(const vk::Result err) {
LOG_ERROR(ImGui, "Vulkan error {}", vk::to_string(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.DisplaySize = ImVec2((float)window.getWidth(), (float)window.getHeight());
io.IniFilename = SDL_strdup(config_path.string().c_str()); io.IniFilename = SDL_strdup(config_path.string().c_str());
io.LogFilename = SDL_strdup(log_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(); StyleColorsDark();
Sdl::Init(window.GetSdlWindow()); Sdl::Init(window.GetSdlWindow());