mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2024-11-23 03:09:55 +00:00
Rewrite Save Data & Impl Save Data Dialog (#824)
* core: Rewrite PSF parser & add encoder add .sfo hex pattern to /scripts * core/fs: allow to mount path as read-only * common: Add CString wrapper to handle native null-terminated strings * SaveData: rewrite to implement full functionality * mock value for SYSTEM_VER * SavaData: backup features * SavaData: SaveDataMemory features * imgui Ref-counted textures - has a background thread to decode textures * imgui: rework gamepad navigation * PSF: fixed psf not using enum class for PSFEntryFmt (was a standard old ugly enum) - Add null check to CString when itself is used in a nullable field * SaveDataDialog implementation - Fix Mounting/Unmounting check of SaveInstance
This commit is contained in:
parent
077f8981a7
commit
0f4bcd8c83
@ -232,11 +232,18 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp
|
||||
src/core/libraries/system/msgdialog_ui.cpp
|
||||
src/core/libraries/system/posix.cpp
|
||||
src/core/libraries/system/posix.h
|
||||
src/core/libraries/save_data/error_codes.h
|
||||
src/core/libraries/save_data/save_backup.cpp
|
||||
src/core/libraries/save_data/save_backup.h
|
||||
src/core/libraries/save_data/save_instance.cpp
|
||||
src/core/libraries/save_data/save_instance.h
|
||||
src/core/libraries/save_data/save_memory.cpp
|
||||
src/core/libraries/save_data/save_memory.h
|
||||
src/core/libraries/save_data/savedata.cpp
|
||||
src/core/libraries/save_data/savedata.h
|
||||
src/core/libraries/system/savedatadialog.cpp
|
||||
src/core/libraries/system/savedatadialog.h
|
||||
src/core/libraries/save_data/dialog/savedatadialog.cpp
|
||||
src/core/libraries/save_data/dialog/savedatadialog.h
|
||||
src/core/libraries/save_data/dialog/savedatadialog_ui.cpp
|
||||
src/core/libraries/save_data/dialog/savedatadialog_ui.h
|
||||
src/core/libraries/system/sysmodule.cpp
|
||||
src/core/libraries/system/sysmodule.h
|
||||
src/core/libraries/system/systemservice.cpp
|
||||
@ -349,6 +356,7 @@ set(COMMON src/common/logging/backend.cpp
|
||||
src/common/concepts.h
|
||||
src/common/config.cpp
|
||||
src/common/config.h
|
||||
src/common/cstring.h
|
||||
src/common/debug.h
|
||||
src/common/disassembler.cpp
|
||||
src/common/disassembler.h
|
||||
@ -607,6 +615,7 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
|
||||
set(IMGUI src/imgui/imgui_config.h
|
||||
src/imgui/imgui_layer.h
|
||||
src/imgui/imgui_std.h
|
||||
src/imgui/imgui_texture.h
|
||||
src/imgui/layer/video_info.cpp
|
||||
src/imgui/layer/video_info.h
|
||||
src/imgui/renderer/imgui_core.cpp
|
||||
@ -615,6 +624,8 @@ set(IMGUI src/imgui/imgui_config.h
|
||||
src/imgui/renderer/imgui_impl_sdl3.h
|
||||
src/imgui/renderer/imgui_impl_vulkan.cpp
|
||||
src/imgui/renderer/imgui_impl_vulkan.h
|
||||
src/imgui/renderer/texture_manager.cpp
|
||||
src/imgui/renderer/texture_manager.h
|
||||
)
|
||||
|
||||
set(INPUT src/input/controller.cpp
|
||||
|
52
scripts/file_formats/sfo.hexpat
Normal file
52
scripts/file_formats/sfo.hexpat
Normal file
@ -0,0 +1,52 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import std.io;
|
||||
import std.sys;
|
||||
|
||||
struct Header {
|
||||
u32 magic;
|
||||
u32 version;
|
||||
u32 key_table_offset;
|
||||
u32 data_table_offset;
|
||||
u32 index_table_entries;
|
||||
};
|
||||
|
||||
struct KeyEntry {
|
||||
char name[];
|
||||
} [[inline]];
|
||||
|
||||
struct DataEntry<auto fmt, auto size> {
|
||||
if (fmt == 0x0404) {
|
||||
u32 int_value;
|
||||
} else if(fmt == 0x0004) {
|
||||
char bin_value[size];
|
||||
} else if(fmt == 0x0204) {
|
||||
char str_value[size];
|
||||
} else {
|
||||
std::warning("unknown fmt type");
|
||||
}
|
||||
} [[inline]];
|
||||
|
||||
struct IndexEntry {
|
||||
u16 key_offset;
|
||||
u16 param_fmt;
|
||||
u32 param_len;
|
||||
u32 param_max_len;
|
||||
u32 data_offset;
|
||||
};
|
||||
|
||||
struct Entry<auto KeyTableOffset, auto DataTableOffset> {
|
||||
u64 begin = $;
|
||||
IndexEntry index;
|
||||
KeyEntry key @ KeyTableOffset + index.key_offset;
|
||||
DataEntry<index.param_fmt, index.param_len> data @ DataTableOffset + index.data_offset;
|
||||
u8 data_empty[index.param_max_len - index.param_len] @ DataTableOffset + index.data_offset + index.param_len;
|
||||
$ = begin + sizeof(IndexEntry);
|
||||
};
|
||||
|
||||
Header header @ 0;
|
||||
std::assert(header.magic == 0x46535000, "Miss match magic");
|
||||
std::assert(header.version == 0x00000101, "Miss match version");
|
||||
|
||||
Entry<header.key_table_offset, header.data_table_offset> list[header.index_table_entries] @ 0x14;
|
133
src/common/cstring.h
Normal file
133
src/common/cstring.h
Normal file
@ -0,0 +1,133 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "assert.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
/**
|
||||
* @brief A null-terminated string with a fixed maximum length
|
||||
* This class is not meant to be used as a general-purpose string class
|
||||
* It is meant to be used as `char[N]` where memory layout is fixed
|
||||
* @tparam N Maximum length of the string
|
||||
* @tparam T Type of character
|
||||
*/
|
||||
template <size_t N, typename T = char>
|
||||
class CString {
|
||||
T data[N]{};
|
||||
|
||||
public:
|
||||
class Iterator;
|
||||
|
||||
CString() = default;
|
||||
|
||||
template <size_t M>
|
||||
explicit CString(const CString<M>& other)
|
||||
requires(M <= N)
|
||||
{
|
||||
std::ranges::copy(other.begin(), other.end(), data);
|
||||
}
|
||||
|
||||
void FromString(const std::basic_string_view<T>& str) {
|
||||
size_t p = str.copy(data, N - 1);
|
||||
data[p] = '\0';
|
||||
}
|
||||
|
||||
void Zero() {
|
||||
std::ranges::fill(data, 0);
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wtautological-undefined-compare"
|
||||
explicit(false) operator std::basic_string_view<T>() const {
|
||||
if (this == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return std::basic_string_view<T>{data};
|
||||
}
|
||||
|
||||
explicit operator std::basic_string<T>() const {
|
||||
if (this == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return std::basic_string<T>{data};
|
||||
}
|
||||
|
||||
std::basic_string<T> to_string() const {
|
||||
if (this == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return std::basic_string<T>{data};
|
||||
}
|
||||
|
||||
std::basic_string_view<T> to_view() const {
|
||||
if (this == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return std::basic_string_view<T>{data};
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
char* begin() {
|
||||
return data;
|
||||
}
|
||||
|
||||
const char* begin() const {
|
||||
return data;
|
||||
}
|
||||
|
||||
char* end() {
|
||||
return data + N;
|
||||
}
|
||||
|
||||
const char* end() const {
|
||||
return data + N;
|
||||
}
|
||||
|
||||
T& operator[](size_t idx) {
|
||||
return data[idx];
|
||||
}
|
||||
|
||||
const T& operator[](size_t idx) const {
|
||||
return data[idx];
|
||||
}
|
||||
|
||||
class Iterator {
|
||||
T* ptr;
|
||||
T* end;
|
||||
|
||||
public:
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = T;
|
||||
using pointer = T*;
|
||||
using reference = T&;
|
||||
using iterator_category = std::random_access_iterator_tag;
|
||||
|
||||
Iterator() = default;
|
||||
explicit Iterator(T* ptr) : ptr(ptr), end(ptr + N) {}
|
||||
|
||||
Iterator& operator++() {
|
||||
++ptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator operator++(int) {
|
||||
Iterator tmp = *this;
|
||||
++ptr;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
operator T*() {
|
||||
ASSERT_MSG(ptr >= end, "CString iterator out of bounds");
|
||||
return ptr;
|
||||
}
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(CString<13>) == sizeof(char[13])); // Ensure size still matches a simple array
|
||||
static_assert(std::weakly_incrementable<CString<13>::Iterator>);
|
||||
|
||||
} // namespace Common
|
@ -396,4 +396,18 @@ s64 IOFile::Tell() const {
|
||||
return ftello(file);
|
||||
}
|
||||
|
||||
u64 GetDirectorySize(const std::filesystem::path& path) {
|
||||
if (!fs::exists(path)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 total = 0;
|
||||
for (const auto& entry : fs::recursive_directory_iterator(path)) {
|
||||
if (fs::is_regular_file(entry.path())) {
|
||||
total += fs::file_size(entry.path());
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
} // namespace Common::FS
|
||||
|
@ -219,4 +219,6 @@ private:
|
||||
uintptr_t file_mapping = 0;
|
||||
};
|
||||
|
||||
u64 GetDirectorySize(const std::filesystem::path& path);
|
||||
|
||||
} // namespace Common::FS
|
||||
|
@ -14,12 +14,17 @@
|
||||
|
||||
namespace Common {
|
||||
|
||||
std::string ToLower(std::string str) {
|
||||
std::transform(str.begin(), str.end(), str.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
std::string ToLower(std::string_view input) {
|
||||
std::string str;
|
||||
str.resize(input.size());
|
||||
std::ranges::transform(input, str.begin(), tolower);
|
||||
return str;
|
||||
}
|
||||
|
||||
void ToLowerInPlace(std::string& str) {
|
||||
std::ranges::transform(str, str.begin(), tolower);
|
||||
}
|
||||
|
||||
std::vector<std::string> SplitString(const std::string& str, char delimiter) {
|
||||
std::istringstream iss(str);
|
||||
std::vector<std::string> output(1);
|
||||
|
@ -10,7 +10,9 @@
|
||||
namespace Common {
|
||||
|
||||
/// Make a string lowercase
|
||||
[[nodiscard]] std::string ToLower(std::string str);
|
||||
[[nodiscard]] std::string ToLower(std::string_view str);
|
||||
|
||||
void ToLowerInPlace(std::string& str);
|
||||
|
||||
std::vector<std::string> SplitString(const std::string& str, char delimiter);
|
||||
|
||||
|
@ -2,61 +2,275 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/io_file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_format/psf.h"
|
||||
|
||||
PSF::PSF() = default;
|
||||
static const std::unordered_map<std::string_view, u32> psf_known_max_sizes = {
|
||||
{"ACCOUNT_ID", 8}, {"CATEGORY", 4}, {"DETAIL", 1024}, {"FORMAT", 4},
|
||||
{"MAINTITLE", 128}, {"PARAMS", 1024}, {"SAVEDATA_BLOCKS", 8}, {"SAVEDATA_DIRECTORY", 32},
|
||||
{"SUBTITLE", 128}, {"TITLE_ID", 12},
|
||||
};
|
||||
static inline u32 get_max_size(std::string_view key, u32 default_value) {
|
||||
if (const auto& v = psf_known_max_sizes.find(key); v != psf_known_max_sizes.end()) {
|
||||
return v->second;
|
||||
}
|
||||
return default_value;
|
||||
}
|
||||
|
||||
PSF::~PSF() = default;
|
||||
bool PSF::Open(const std::filesystem::path& filepath) {
|
||||
if (std::filesystem::exists(filepath)) {
|
||||
last_write = std::filesystem::last_write_time(filepath);
|
||||
}
|
||||
|
||||
bool PSF::open(const std::string& filepath, const std::vector<u8>& psfBuffer) {
|
||||
if (!psfBuffer.empty()) {
|
||||
psf.resize(psfBuffer.size());
|
||||
psf = psfBuffer;
|
||||
} else {
|
||||
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
|
||||
if (!file.IsOpen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const u64 psfSize = file.GetSize();
|
||||
psf.resize(psfSize);
|
||||
std::vector<u8> psf(psfSize);
|
||||
file.Seek(0);
|
||||
file.Read(psf);
|
||||
file.Close();
|
||||
return Open(psf);
|
||||
}
|
||||
|
||||
bool PSF::Open(const std::vector<u8>& psf_buffer) {
|
||||
const u8* psf_data = psf_buffer.data();
|
||||
|
||||
entry_list.clear();
|
||||
map_binaries.clear();
|
||||
map_strings.clear();
|
||||
map_integers.clear();
|
||||
|
||||
// Parse file contents
|
||||
PSFHeader header;
|
||||
std::memcpy(&header, psf.data(), sizeof(header));
|
||||
for (u32 i = 0; i < header.index_table_entries; i++) {
|
||||
PSFEntry entry;
|
||||
std::memcpy(&entry, &psf[sizeof(PSFHeader) + i * sizeof(PSFEntry)], sizeof(entry));
|
||||
PSFHeader header{};
|
||||
std::memcpy(&header, psf_data, sizeof(header));
|
||||
|
||||
const std::string key = (char*)&psf[header.key_table_offset + entry.key_offset];
|
||||
if (entry.param_fmt == PSFEntry::Fmt::TextRaw ||
|
||||
entry.param_fmt == PSFEntry::Fmt::TextNormal) {
|
||||
map_strings[key] = (char*)&psf[header.data_table_offset + entry.data_offset];
|
||||
if (header.magic != PSF_MAGIC) {
|
||||
LOG_ERROR(Core, "Invalid PSF magic number");
|
||||
return false;
|
||||
}
|
||||
if (entry.param_fmt == PSFEntry::Fmt::Integer) {
|
||||
u32 value;
|
||||
std::memcpy(&value, &psf[header.data_table_offset + entry.data_offset], sizeof(value));
|
||||
map_integers[key] = value;
|
||||
if (header.version != PSF_VERSION_1_1 && header.version != PSF_VERSION_1_0) {
|
||||
LOG_ERROR(Core, "Unsupported PSF version: 0x{:08x}", header.version);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < header.index_table_entries; i++) {
|
||||
PSFRawEntry raw_entry{};
|
||||
std::memcpy(&raw_entry, psf_data + sizeof(PSFHeader) + i * sizeof(PSFRawEntry),
|
||||
sizeof(raw_entry));
|
||||
|
||||
Entry& entry = entry_list.emplace_back();
|
||||
entry.key = std::string{(char*)(psf_data + header.key_table_offset + raw_entry.key_offset)};
|
||||
entry.param_fmt = static_cast<PSFEntryFmt>(raw_entry.param_fmt.Raw());
|
||||
entry.max_len = raw_entry.param_max_len;
|
||||
|
||||
const u8* data = psf_data + header.data_table_offset + raw_entry.data_offset;
|
||||
|
||||
switch (entry.param_fmt) {
|
||||
case PSFEntryFmt::Binary: {
|
||||
std::vector<u8> value(raw_entry.param_len);
|
||||
std::memcpy(value.data(), data, raw_entry.param_len);
|
||||
map_binaries.emplace(i, std::move(value));
|
||||
} break;
|
||||
case PSFEntryFmt::Text: {
|
||||
std::string c_str{reinterpret_cast<const char*>(data)};
|
||||
map_strings.emplace(i, std::move(c_str));
|
||||
} break;
|
||||
case PSFEntryFmt::Integer: {
|
||||
ASSERT_MSG(raw_entry.param_len == sizeof(s32), "PSF integer entry size mismatch");
|
||||
s32 integer = *(s32*)data;
|
||||
map_integers.emplace(i, integer);
|
||||
} break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unknown PSF entry format");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string PSF::GetString(const std::string& key) {
|
||||
if (map_strings.find(key) != map_strings.end()) {
|
||||
return map_strings.at(key);
|
||||
}
|
||||
return "";
|
||||
bool PSF::Encode(const std::filesystem::path& filepath) const {
|
||||
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Write);
|
||||
if (!file.IsOpen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 PSF::GetInteger(const std::string& key) {
|
||||
if (map_integers.find(key) != map_integers.end()) {
|
||||
return map_integers.at(key);
|
||||
last_write = std::filesystem::file_time_type::clock::now();
|
||||
|
||||
const auto psf_buffer = Encode();
|
||||
return file.Write(psf_buffer) == psf_buffer.size();
|
||||
}
|
||||
return -1;
|
||||
|
||||
std::vector<u8> PSF::Encode() const {
|
||||
std::vector<u8> psf_buffer;
|
||||
Encode(psf_buffer);
|
||||
return psf_buffer;
|
||||
}
|
||||
|
||||
void PSF::Encode(std::vector<u8>& psf_buffer) const {
|
||||
psf_buffer.resize(sizeof(PSFHeader) + sizeof(PSFRawEntry) * entry_list.size());
|
||||
|
||||
{
|
||||
auto& header = *(PSFHeader*)psf_buffer.data();
|
||||
header.magic = PSF_MAGIC;
|
||||
header.version = PSF_VERSION_1_1;
|
||||
header.index_table_entries = entry_list.size();
|
||||
}
|
||||
|
||||
const size_t key_table_offset = psf_buffer.size();
|
||||
((PSFHeader*)psf_buffer.data())->key_table_offset = key_table_offset;
|
||||
for (size_t i = 0; i < entry_list.size(); i++) {
|
||||
auto& raw_entry = ((PSFRawEntry*)(psf_buffer.data() + sizeof(PSFHeader)))[i];
|
||||
const Entry& entry = entry_list[i];
|
||||
raw_entry.key_offset = psf_buffer.size() - key_table_offset;
|
||||
raw_entry.param_fmt.FromRaw(static_cast<u16>(entry.param_fmt));
|
||||
raw_entry.param_max_len = entry.max_len;
|
||||
std::ranges::copy(entry.key, std::back_inserter(psf_buffer));
|
||||
psf_buffer.push_back(0); // NULL terminator
|
||||
}
|
||||
|
||||
const size_t data_table_offset = psf_buffer.size();
|
||||
((PSFHeader*)psf_buffer.data())->data_table_offset = data_table_offset;
|
||||
for (size_t i = 0; i < entry_list.size(); i++) {
|
||||
if (psf_buffer.size() % 4 != 0) {
|
||||
std::ranges::fill_n(std::back_inserter(psf_buffer), 4 - psf_buffer.size() % 4, 0);
|
||||
}
|
||||
auto& raw_entry = ((PSFRawEntry*)(psf_buffer.data() + sizeof(PSFHeader)))[i];
|
||||
const Entry& entry = entry_list[i];
|
||||
raw_entry.data_offset = psf_buffer.size() - data_table_offset;
|
||||
|
||||
s32 additional_padding = s32(raw_entry.param_max_len);
|
||||
|
||||
switch (entry.param_fmt) {
|
||||
case PSFEntryFmt::Binary: {
|
||||
const auto& value = map_binaries.at(i);
|
||||
raw_entry.param_len = value.size();
|
||||
additional_padding -= s32(raw_entry.param_len);
|
||||
std::ranges::copy(value, std::back_inserter(psf_buffer));
|
||||
} break;
|
||||
case PSFEntryFmt::Text: {
|
||||
const auto& value = map_strings.at(i);
|
||||
raw_entry.param_len = value.size() + 1;
|
||||
additional_padding -= s32(raw_entry.param_len);
|
||||
std::ranges::copy(value, std::back_inserter(psf_buffer));
|
||||
psf_buffer.push_back(0); // NULL terminator
|
||||
} break;
|
||||
case PSFEntryFmt::Integer: {
|
||||
const auto& value = map_integers.at(i);
|
||||
raw_entry.param_len = sizeof(s32);
|
||||
additional_padding -= s32(raw_entry.param_len);
|
||||
const auto value_bytes = reinterpret_cast<const u8*>(&value);
|
||||
std::ranges::copy(value_bytes, value_bytes + sizeof(s32),
|
||||
std::back_inserter(psf_buffer));
|
||||
} break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unknown PSF entry format");
|
||||
}
|
||||
ASSERT_MSG(additional_padding >= 0, "PSF entry max size mismatch");
|
||||
std::ranges::fill_n(std::back_inserter(psf_buffer), additional_padding, 0);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::span<const u8>> PSF::GetBinary(std::string_view key) const {
|
||||
const auto& [it, index] = FindEntry(key);
|
||||
if (it == entry_list.end()) {
|
||||
return {};
|
||||
}
|
||||
ASSERT(it->param_fmt == PSFEntryFmt::Binary);
|
||||
return std::span{map_binaries.at(index)};
|
||||
}
|
||||
|
||||
std::optional<std::string_view> PSF::GetString(std::string_view key) const {
|
||||
const auto& [it, index] = FindEntry(key);
|
||||
if (it == entry_list.end()) {
|
||||
return {};
|
||||
}
|
||||
ASSERT(it->param_fmt == PSFEntryFmt::Text);
|
||||
return std::string_view{map_strings.at(index)};
|
||||
}
|
||||
|
||||
std::optional<s32> PSF::GetInteger(std::string_view key) const {
|
||||
const auto& [it, index] = FindEntry(key);
|
||||
if (it == entry_list.end()) {
|
||||
return {};
|
||||
}
|
||||
ASSERT(it->param_fmt == PSFEntryFmt::Integer);
|
||||
return map_integers.at(index);
|
||||
}
|
||||
|
||||
void PSF::AddBinary(std::string key, std::vector<u8> value, bool update) {
|
||||
auto [it, index] = FindEntry(key);
|
||||
bool exist = it != entry_list.end();
|
||||
if (exist && !update) {
|
||||
LOG_ERROR(Core, "PSF: Tried to add binary key that already exists: {}", key);
|
||||
return;
|
||||
}
|
||||
if (exist) {
|
||||
ASSERT_MSG(it->param_fmt == PSFEntryFmt::Binary, "PSF: Change format is not supported");
|
||||
it->max_len = get_max_size(key, value.size());
|
||||
map_binaries.at(index) = std::move(value);
|
||||
return;
|
||||
}
|
||||
Entry& entry = entry_list.emplace_back();
|
||||
entry.max_len = get_max_size(key, value.size());
|
||||
entry.key = std::move(key);
|
||||
entry.param_fmt = PSFEntryFmt::Binary;
|
||||
map_binaries.emplace(entry_list.size() - 1, std::move(value));
|
||||
}
|
||||
|
||||
void PSF::AddString(std::string key, std::string value, bool update) {
|
||||
auto [it, index] = FindEntry(key);
|
||||
bool exist = it != entry_list.end();
|
||||
if (exist && !update) {
|
||||
LOG_ERROR(Core, "PSF: Tried to add string key that already exists: {}", key);
|
||||
return;
|
||||
}
|
||||
if (exist) {
|
||||
ASSERT_MSG(it->param_fmt == PSFEntryFmt::Text, "PSF: Change format is not supported");
|
||||
it->max_len = get_max_size(key, value.size() + 1);
|
||||
map_strings.at(index) = std::move(value);
|
||||
return;
|
||||
}
|
||||
Entry& entry = entry_list.emplace_back();
|
||||
entry.max_len = get_max_size(key, value.size() + 1);
|
||||
entry.key = std::move(key);
|
||||
entry.param_fmt = PSFEntryFmt::Text;
|
||||
map_strings.emplace(entry_list.size() - 1, std::move(value));
|
||||
}
|
||||
|
||||
void PSF::AddInteger(std::string key, s32 value, bool update) {
|
||||
auto [it, index] = FindEntry(key);
|
||||
bool exist = it != entry_list.end();
|
||||
if (exist && !update) {
|
||||
LOG_ERROR(Core, "PSF: Tried to add integer key that already exists: {}", key);
|
||||
return;
|
||||
}
|
||||
if (exist) {
|
||||
ASSERT_MSG(it->param_fmt == PSFEntryFmt::Integer, "PSF: Change format is not supported");
|
||||
it->max_len = sizeof(s32);
|
||||
map_integers.at(index) = value;
|
||||
return;
|
||||
}
|
||||
Entry& entry = entry_list.emplace_back();
|
||||
entry.key = std::move(key);
|
||||
entry.param_fmt = PSFEntryFmt::Integer;
|
||||
entry.max_len = sizeof(s32);
|
||||
map_integers.emplace(entry_list.size() - 1, value);
|
||||
}
|
||||
|
||||
std::pair<std::vector<PSF::Entry>::iterator, size_t> PSF::FindEntry(std::string_view key) {
|
||||
auto entry =
|
||||
std::ranges::find_if(entry_list, [&](const auto& entry) { return entry.key == key; });
|
||||
return {entry, std::distance(entry_list.begin(), entry)};
|
||||
}
|
||||
|
||||
std::pair<std::vector<PSF::Entry>::const_iterator, size_t> PSF::FindEntry(
|
||||
std::string_view key) const {
|
||||
auto entry =
|
||||
std::ranges::find_if(entry_list, [&](const auto& entry) { return entry.key == key; });
|
||||
return {entry, std::distance(entry_list.begin(), entry)};
|
||||
}
|
||||
|
@ -3,11 +3,18 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "common/endian.h"
|
||||
|
||||
constexpr u32 PSF_MAGIC = 0x00505346;
|
||||
constexpr u32 PSF_VERSION_1_1 = 0x00000101;
|
||||
constexpr u32 PSF_VERSION_1_0 = 0x00000100;
|
||||
|
||||
struct PSFHeader {
|
||||
u32_be magic;
|
||||
u32_le version;
|
||||
@ -15,34 +22,72 @@ struct PSFHeader {
|
||||
u32_le data_table_offset;
|
||||
u32_le index_table_entries;
|
||||
};
|
||||
static_assert(sizeof(PSFHeader) == 0x14);
|
||||
|
||||
struct PSFEntry {
|
||||
enum Fmt : u16 {
|
||||
TextRaw = 0x0400, // String in UTF-8 format and not NULL terminated
|
||||
TextNormal = 0x0402, // String in UTF-8 format and NULL terminated
|
||||
Integer = 0x0404, // Unsigned 32-bit integer
|
||||
};
|
||||
|
||||
struct PSFRawEntry {
|
||||
u16_le key_offset;
|
||||
u16_be param_fmt;
|
||||
u32_le param_len;
|
||||
u32_le param_max_len;
|
||||
u32_le data_offset;
|
||||
};
|
||||
static_assert(sizeof(PSFRawEntry) == 0x10);
|
||||
|
||||
enum class PSFEntryFmt : u16 {
|
||||
Binary = 0x0004, // Binary data
|
||||
Text = 0x0204, // String in UTF-8 format and NULL terminated
|
||||
Integer = 0x0404, // Signed 32-bit integer
|
||||
};
|
||||
|
||||
class PSF {
|
||||
struct Entry {
|
||||
std::string key;
|
||||
PSFEntryFmt param_fmt;
|
||||
u32 max_len;
|
||||
};
|
||||
|
||||
public:
|
||||
PSF();
|
||||
~PSF();
|
||||
PSF() = default;
|
||||
~PSF() = default;
|
||||
|
||||
bool open(const std::string& filepath, const std::vector<u8>& psfBuffer);
|
||||
PSF(const PSF& other) = default;
|
||||
PSF(PSF&& other) noexcept = default;
|
||||
PSF& operator=(const PSF& other) = default;
|
||||
PSF& operator=(PSF&& other) noexcept = default;
|
||||
|
||||
std::string GetString(const std::string& key);
|
||||
u32 GetInteger(const std::string& key);
|
||||
bool Open(const std::filesystem::path& filepath);
|
||||
bool Open(const std::vector<u8>& psf_buffer);
|
||||
|
||||
std::unordered_map<std::string, std::string> map_strings;
|
||||
std::unordered_map<std::string, u32> map_integers;
|
||||
[[nodiscard]] std::vector<u8> Encode() const;
|
||||
void Encode(std::vector<u8>& buf) const;
|
||||
bool Encode(const std::filesystem::path& filepath) const;
|
||||
|
||||
std::optional<std::span<const u8>> GetBinary(std::string_view key) const;
|
||||
std::optional<std::string_view> GetString(std::string_view key) const;
|
||||
std::optional<s32> GetInteger(std::string_view key) const;
|
||||
|
||||
void AddBinary(std::string key, std::vector<u8> value, bool update = false);
|
||||
void AddString(std::string key, std::string value, bool update = false);
|
||||
void AddInteger(std::string key, s32 value, bool update = false);
|
||||
|
||||
[[nodiscard]] std::filesystem::file_time_type GetLastWrite() const {
|
||||
return last_write;
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::vector<Entry>& GetEntries() const {
|
||||
return entry_list;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<u8> psf;
|
||||
mutable std::filesystem::file_time_type last_write;
|
||||
|
||||
std::vector<Entry> entry_list;
|
||||
|
||||
std::unordered_map<size_t, std::vector<u8>> map_binaries;
|
||||
std::unordered_map<size_t, std::string> map_strings;
|
||||
std::unordered_map<size_t, s32> map_integers;
|
||||
|
||||
[[nodiscard]] std::pair<std::vector<Entry>::iterator, size_t> FindEntry(std::string_view key);
|
||||
[[nodiscard]] std::pair<std::vector<Entry>::const_iterator, size_t> FindEntry(
|
||||
std::string_view key) const;
|
||||
};
|
||||
|
@ -9,9 +9,10 @@ namespace Core::FileSys {
|
||||
|
||||
constexpr int RESERVED_HANDLES = 3; // First 3 handles are stdin,stdout,stderr
|
||||
|
||||
void MntPoints::Mount(const std::filesystem::path& host_folder, const std::string& guest_folder) {
|
||||
void MntPoints::Mount(const std::filesystem::path& host_folder, const std::string& guest_folder,
|
||||
bool read_only) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_mnt_pairs.emplace_back(host_folder, guest_folder);
|
||||
m_mnt_pairs.emplace_back(host_folder, guest_folder, read_only);
|
||||
}
|
||||
|
||||
void MntPoints::Unmount(const std::filesystem::path& host_folder, const std::string& guest_folder) {
|
||||
@ -26,7 +27,7 @@ void MntPoints::UnmountAll() {
|
||||
m_mnt_pairs.clear();
|
||||
}
|
||||
|
||||
std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory) {
|
||||
std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory, bool* is_read_only) {
|
||||
// Evil games like Turok2 pass double slashes e.g /app0//game.kpf
|
||||
std::string corrected_path(guest_directory);
|
||||
size_t pos = corrected_path.find("//");
|
||||
@ -40,6 +41,10 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (is_read_only) {
|
||||
*is_read_only = mount->read_only;
|
||||
}
|
||||
|
||||
// Nothing to do if getting the mount itself.
|
||||
if (corrected_path == mount->mount) {
|
||||
return mount->host_path;
|
||||
|
@ -22,16 +22,19 @@ public:
|
||||
struct MntPair {
|
||||
std::filesystem::path host_path;
|
||||
std::string mount; // e.g /app0/
|
||||
bool read_only;
|
||||
};
|
||||
|
||||
explicit MntPoints() = default;
|
||||
~MntPoints() = default;
|
||||
|
||||
void Mount(const std::filesystem::path& host_folder, const std::string& guest_folder);
|
||||
void Mount(const std::filesystem::path& host_folder, const std::string& guest_folder,
|
||||
bool read_only = false);
|
||||
void Unmount(const std::filesystem::path& host_folder, const std::string& guest_folder);
|
||||
void UnmountAll();
|
||||
|
||||
std::filesystem::path GetHostPath(std::string_view guest_directory);
|
||||
std::filesystem::path GetHostPath(std::string_view guest_directory,
|
||||
bool* is_read_only = nullptr);
|
||||
|
||||
const MntPair* GetMount(const std::string& guest_path) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
@ -90,37 +90,32 @@ int PS4_SYSV_ABI sceAppContentAddcontUnmount() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAppContentAppParamGetInt(OrbisAppContentAppParamId paramId, s32* value) {
|
||||
if (value == nullptr)
|
||||
int PS4_SYSV_ABI sceAppContentAppParamGetInt(OrbisAppContentAppParamId paramId, s32* out_value) {
|
||||
if (out_value == nullptr)
|
||||
return ORBIS_APP_CONTENT_ERROR_PARAMETER;
|
||||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
||||
std::optional<s32> value;
|
||||
switch (paramId) {
|
||||
case ORBIS_APP_CONTENT_APPPARAM_ID_SKU_FLAG:
|
||||
*value = ORBIS_APP_CONTENT_APPPARAM_SKU_FLAG_FULL;
|
||||
value = ORBIS_APP_CONTENT_APPPARAM_SKU_FLAG_FULL;
|
||||
break;
|
||||
case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_1:
|
||||
*value = param_sfo->GetInteger("USER_DEFINED_PARAM_1");
|
||||
value = param_sfo->GetInteger("USER_DEFINED_PARAM_1");
|
||||
break;
|
||||
case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_2:
|
||||
*value = param_sfo->GetInteger("USER_DEFINED_PARAM_2");
|
||||
value = param_sfo->GetInteger("USER_DEFINED_PARAM_2");
|
||||
break;
|
||||
case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_3:
|
||||
*value = param_sfo->GetInteger("USER_DEFINED_PARAM_3");
|
||||
value = param_sfo->GetInteger("USER_DEFINED_PARAM_3");
|
||||
break;
|
||||
case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_4:
|
||||
*value = param_sfo->GetInteger("USER_DEFINED_PARAM_4");
|
||||
value = param_sfo->GetInteger("USER_DEFINED_PARAM_4");
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Lib_AppContent, " paramId = {}, value = {} paramId is not valid", paramId,
|
||||
*value);
|
||||
return ORBIS_APP_CONTENT_ERROR_PARAMETER;
|
||||
}
|
||||
if (*value == -1) {
|
||||
LOG_ERROR(Lib_AppContent,
|
||||
" paramId = {}, value = {} value is not valid can't read param.sfo?", paramId,
|
||||
*value);
|
||||
LOG_ERROR(Lib_AppContent, " paramId = {} paramId is not valid", paramId);
|
||||
return ORBIS_APP_CONTENT_ERROR_PARAMETER;
|
||||
}
|
||||
*out_value = value.value_or(0);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -251,7 +246,7 @@ int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initPar
|
||||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
||||
|
||||
const auto addons_dir = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir);
|
||||
title_id = param_sfo->GetString("TITLE_ID");
|
||||
title_id = *param_sfo->GetString("TITLE_ID");
|
||||
auto addon_path = addons_dir / title_id;
|
||||
if (std::filesystem::exists(addon_path)) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(addon_path)) {
|
||||
|
@ -179,11 +179,16 @@ int PS4_SYSV_ABI sceKernelUnlink(const char* path) {
|
||||
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
|
||||
const auto host_path = mnt->GetHostPath(path);
|
||||
bool ro = false;
|
||||
const auto host_path = mnt->GetHostPath(path, &ro);
|
||||
if (host_path.empty()) {
|
||||
return SCE_KERNEL_ERROR_EACCES;
|
||||
}
|
||||
|
||||
if (ro) {
|
||||
return SCE_KERNEL_ERROR_EROFS;
|
||||
}
|
||||
|
||||
if (std::filesystem::is_directory(host_path)) {
|
||||
return SCE_KERNEL_ERROR_EPERM;
|
||||
}
|
||||
@ -270,11 +275,18 @@ int PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) {
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
const auto dir_name = mnt->GetHostPath(path);
|
||||
|
||||
bool ro = false;
|
||||
const auto dir_name = mnt->GetHostPath(path, &ro);
|
||||
|
||||
if (std::filesystem::exists(dir_name)) {
|
||||
return SCE_KERNEL_ERROR_EEXIST;
|
||||
}
|
||||
|
||||
if (ro) {
|
||||
return SCE_KERNEL_ERROR_EROFS;
|
||||
}
|
||||
|
||||
// CUSA02456: path = /aotl after sceSaveDataMount(mode = 1)
|
||||
if (dir_name.empty() || !std::filesystem::create_directory(dir_name)) {
|
||||
return SCE_KERNEL_ERROR_EIO;
|
||||
@ -299,7 +311,8 @@ int PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
|
||||
int PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) {
|
||||
LOG_INFO(Kernel_Fs, "(PARTIAL) path = {}", path);
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
const auto path_name = mnt->GetHostPath(path);
|
||||
bool ro = false;
|
||||
const auto path_name = mnt->GetHostPath(path, &ro);
|
||||
std::memset(sb, 0, sizeof(OrbisKernelStat));
|
||||
const bool is_dir = std::filesystem::is_directory(path_name);
|
||||
const bool is_file = std::filesystem::is_regular_file(path_name);
|
||||
@ -319,6 +332,10 @@ int PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) {
|
||||
sb->st_blocks = (sb->st_size + 511) / 512;
|
||||
// TODO incomplete
|
||||
}
|
||||
if (ro) {
|
||||
sb->st_mode &= ~0000555u;
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -500,11 +517,18 @@ s64 PS4_SYSV_ABI sceKernelPwrite(int d, void* buf, size_t nbytes, s64 offset) {
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelRename(const char* from, const char* to) {
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
const auto src_path = mnt->GetHostPath(from);
|
||||
bool ro = false;
|
||||
const auto src_path = mnt->GetHostPath(from, &ro);
|
||||
if (!std::filesystem::exists(src_path)) {
|
||||
return ORBIS_KERNEL_ERROR_ENOENT;
|
||||
}
|
||||
const auto dst_path = mnt->GetHostPath(to);
|
||||
if (ro) {
|
||||
return SCE_KERNEL_ERROR_EROFS;
|
||||
}
|
||||
const auto dst_path = mnt->GetHostPath(to, &ro);
|
||||
if (ro) {
|
||||
return SCE_KERNEL_ERROR_EROFS;
|
||||
}
|
||||
const bool src_is_dir = std::filesystem::is_directory(src_path);
|
||||
const bool dst_is_dir = std::filesystem::is_directory(dst_path);
|
||||
if (src_is_dir && !dst_is_dir) {
|
||||
|
@ -244,7 +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");
|
||||
int version = param_sfo->GetInteger("SYSTEM_VER").value_or(0x4700000);
|
||||
LOG_INFO(Kernel, "returned system version = {:#x}", version);
|
||||
*ver = version;
|
||||
return (version > 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL;
|
||||
|
@ -27,12 +27,12 @@
|
||||
#include "core/libraries/playgo/playgo.h"
|
||||
#include "core/libraries/random/random.h"
|
||||
#include "core/libraries/rtc/rtc.h"
|
||||
#include "core/libraries/save_data/dialog/savedatadialog.h"
|
||||
#include "core/libraries/save_data/savedata.h"
|
||||
#include "core/libraries/screenshot/screenshot.h"
|
||||
#include "core/libraries/system/commondialog.h"
|
||||
#include "core/libraries/system/msgdialog.h"
|
||||
#include "core/libraries/system/posix.h"
|
||||
#include "core/libraries/system/savedatadialog.h"
|
||||
#include "core/libraries/system/sysmodule.h"
|
||||
#include "core/libraries/system/systemservice.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
@ -57,11 +57,11 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
|
||||
Libraries::Net::RegisterlibSceNet(sym);
|
||||
Libraries::NetCtl::RegisterlibSceNetCtl(sym);
|
||||
Libraries::SaveData::RegisterlibSceSaveData(sym);
|
||||
Libraries::SaveData::Dialog::RegisterlibSceSaveDataDialog(sym);
|
||||
Libraries::Ssl::RegisterlibSceSsl(sym);
|
||||
Libraries::SysModule::RegisterlibSceSysmodule(sym);
|
||||
Libraries::Posix::Registerlibsceposix(sym);
|
||||
Libraries::AudioIn::RegisterlibSceAudioIn(sym);
|
||||
Libraries::SaveDataDialog::RegisterlibSceSaveDataDialog(sym);
|
||||
Libraries::NpManager::RegisterlibSceNpManager(sym);
|
||||
Libraries::NpScore::RegisterlibSceNpScore(sym);
|
||||
Libraries::NpTrophy::RegisterlibSceNpTrophy(sym);
|
||||
|
@ -31,10 +31,6 @@ public:
|
||||
void Finish();
|
||||
|
||||
void Draw() override;
|
||||
|
||||
bool ShouldGrabGamepad() override {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
}; // namespace Libraries::NpTrophy
|
163
src/core/libraries/save_data/dialog/savedatadialog.cpp
Normal file
163
src/core/libraries/save_data/dialog/savedatadialog.cpp
Normal file
@ -0,0 +1,163 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/system/commondialog.h"
|
||||
#include "magic_enum.hpp"
|
||||
#include "savedatadialog.h"
|
||||
#include "savedatadialog_ui.h"
|
||||
|
||||
namespace Libraries::SaveData::Dialog {
|
||||
|
||||
using CommonDialog::Error;
|
||||
using CommonDialog::Result;
|
||||
using CommonDialog::Status;
|
||||
|
||||
static auto g_status = Status::NONE;
|
||||
static SaveDialogState g_state{};
|
||||
static SaveDialogResult g_result{};
|
||||
static SaveDialogUi g_save_dialog_ui;
|
||||
|
||||
Error PS4_SYSV_ABI sceSaveDataDialogClose() {
|
||||
LOG_DEBUG(Lib_SaveDataDialog, "called");
|
||||
if (g_status != Status::RUNNING) {
|
||||
return Error::NOT_RUNNING;
|
||||
}
|
||||
g_save_dialog_ui.Finish(ButtonId::INVALID);
|
||||
g_save_dialog_ui = SaveDialogUi{};
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Error PS4_SYSV_ABI sceSaveDataDialogGetResult(OrbisSaveDataDialogResult* result) {
|
||||
LOG_DEBUG(Lib_SaveDataDialog, "called");
|
||||
if (g_status != Status::FINISHED) {
|
||||
return Error::NOT_FINISHED;
|
||||
}
|
||||
if (result == nullptr) {
|
||||
return Error::ARG_NULL;
|
||||
}
|
||||
g_result.CopyTo(*result);
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Status PS4_SYSV_ABI sceSaveDataDialogGetStatus() {
|
||||
LOG_TRACE(Lib_SaveDataDialog, "called status={}", magic_enum::enum_name(g_status));
|
||||
return g_status;
|
||||
}
|
||||
|
||||
Error PS4_SYSV_ABI sceSaveDataDialogInitialize() {
|
||||
LOG_DEBUG(Lib_SaveDataDialog, "called");
|
||||
if (!CommonDialog::g_isInitialized) {
|
||||
return Error::NOT_SYSTEM_INITIALIZED;
|
||||
}
|
||||
if (g_status != Status::NONE) {
|
||||
return Error::ALREADY_INITIALIZED;
|
||||
}
|
||||
if (CommonDialog::g_isUsed) {
|
||||
return Error::BUSY;
|
||||
}
|
||||
g_status = Status::INITIALIZED;
|
||||
CommonDialog::g_isUsed = true;
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
Error PS4_SYSV_ABI sceSaveDataDialogOpen(const OrbisSaveDataDialogParam* param) {
|
||||
if (g_status != Status::INITIALIZED && g_status != Status::FINISHED) {
|
||||
LOG_INFO(Lib_SaveDataDialog, "called without initialize");
|
||||
return Error::INVALID_STATE;
|
||||
}
|
||||
if (param == nullptr) {
|
||||
LOG_DEBUG(Lib_SaveDataDialog, "called param:(NULL)");
|
||||
return Error::ARG_NULL;
|
||||
}
|
||||
LOG_DEBUG(Lib_SaveDataDialog, "called param->mode: {}", magic_enum::enum_name(param->mode));
|
||||
ASSERT(param->size == sizeof(OrbisSaveDataDialogParam));
|
||||
ASSERT(param->baseParam.size == sizeof(CommonDialog::BaseParam));
|
||||
g_result = {};
|
||||
g_state = SaveDialogState{*param};
|
||||
g_status = Status::RUNNING;
|
||||
g_save_dialog_ui = SaveDialogUi(&g_state, &g_status, &g_result);
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Error PS4_SYSV_ABI sceSaveDataDialogProgressBarInc(OrbisSaveDataDialogProgressBarTarget target,
|
||||
u32 delta) {
|
||||
LOG_DEBUG(Lib_SaveDataDialog, "called");
|
||||
if (g_status != Status::RUNNING) {
|
||||
return Error::NOT_RUNNING;
|
||||
}
|
||||
if (g_state.GetMode() != SaveDataDialogMode::PROGRESS_BAR) {
|
||||
return Error::NOT_SUPPORTED;
|
||||
}
|
||||
if (target != OrbisSaveDataDialogProgressBarTarget::DEFAULT) {
|
||||
return Error::PARAM_INVALID;
|
||||
}
|
||||
g_state.GetState<SaveDialogState::ProgressBarState>().progress += delta;
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Error PS4_SYSV_ABI sceSaveDataDialogProgressBarSetValue(OrbisSaveDataDialogProgressBarTarget target,
|
||||
u32 rate) {
|
||||
LOG_DEBUG(Lib_SaveDataDialog, "called");
|
||||
if (g_status != Status::RUNNING) {
|
||||
return Error::NOT_RUNNING;
|
||||
}
|
||||
if (g_state.GetMode() != SaveDataDialogMode::PROGRESS_BAR) {
|
||||
return Error::NOT_SUPPORTED;
|
||||
}
|
||||
if (target != OrbisSaveDataDialogProgressBarTarget::DEFAULT) {
|
||||
return Error::PARAM_INVALID;
|
||||
}
|
||||
g_state.GetState<SaveDialogState::ProgressBarState>().progress = rate;
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Error PS4_SYSV_ABI sceSaveDataDialogTerminate() {
|
||||
LOG_DEBUG(Lib_SaveDataDialog, "called");
|
||||
if (g_status == Status::RUNNING) {
|
||||
sceSaveDataDialogClose();
|
||||
}
|
||||
if (g_status == Status::NONE) {
|
||||
return Error::NOT_INITIALIZED;
|
||||
}
|
||||
g_save_dialog_ui = SaveDialogUi{};
|
||||
g_status = Status::NONE;
|
||||
CommonDialog::g_isUsed = false;
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Status PS4_SYSV_ABI sceSaveDataDialogUpdateStatus() {
|
||||
LOG_TRACE(Lib_SaveDataDialog, "called status={}", magic_enum::enum_name(g_status));
|
||||
return g_status;
|
||||
}
|
||||
|
||||
void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("fH46Lag88XY", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
|
||||
sceSaveDataDialogClose);
|
||||
LIB_FUNCTION("yEiJ-qqr6Cg", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
|
||||
sceSaveDataDialogGetResult);
|
||||
LIB_FUNCTION("ERKzksauAJA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
|
||||
sceSaveDataDialogGetStatus);
|
||||
LIB_FUNCTION("s9e3+YpRnzw", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
|
||||
sceSaveDataDialogInitialize);
|
||||
LIB_FUNCTION("en7gNVnh878", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
|
||||
sceSaveDataDialogIsReadyToDisplay);
|
||||
LIB_FUNCTION("4tPhsP6FpDI", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
|
||||
sceSaveDataDialogOpen);
|
||||
LIB_FUNCTION("V-uEeFKARJU", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
|
||||
sceSaveDataDialogProgressBarInc);
|
||||
LIB_FUNCTION("hay1CfTmLyA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
|
||||
sceSaveDataDialogProgressBarSetValue);
|
||||
LIB_FUNCTION("YuH2FA7azqQ", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
|
||||
sceSaveDataDialogTerminate);
|
||||
LIB_FUNCTION("KK3Bdg1RWK0", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
|
||||
sceSaveDataDialogUpdateStatus);
|
||||
};
|
||||
|
||||
} // namespace Libraries::SaveData::Dialog
|
33
src/core/libraries/save_data/dialog/savedatadialog.h
Normal file
33
src/core/libraries/save_data/dialog/savedatadialog.h
Normal file
@ -0,0 +1,33 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/system/commondialog.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::SaveData::Dialog {
|
||||
|
||||
struct OrbisSaveDataDialogParam;
|
||||
struct OrbisSaveDataDialogResult;
|
||||
enum class OrbisSaveDataDialogProgressBarTarget : u32;
|
||||
|
||||
CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogClose();
|
||||
CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogGetResult(OrbisSaveDataDialogResult* result);
|
||||
CommonDialog::Status PS4_SYSV_ABI sceSaveDataDialogGetStatus();
|
||||
CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogInitialize();
|
||||
s32 PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay();
|
||||
CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogOpen(const OrbisSaveDataDialogParam* param);
|
||||
CommonDialog::Error PS4_SYSV_ABI
|
||||
sceSaveDataDialogProgressBarInc(OrbisSaveDataDialogProgressBarTarget target, u32 delta);
|
||||
CommonDialog::Error PS4_SYSV_ABI
|
||||
sceSaveDataDialogProgressBarSetValue(OrbisSaveDataDialogProgressBarTarget target, u32 rate);
|
||||
CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogTerminate();
|
||||
CommonDialog::Status PS4_SYSV_ABI sceSaveDataDialogUpdateStatus();
|
||||
|
||||
void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::SaveData::Dialog
|
802
src/core/libraries/save_data/dialog/savedatadialog_ui.cpp
Normal file
802
src/core/libraries/save_data/dialog/savedatadialog_ui.cpp
Normal file
@ -0,0 +1,802 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
#include <imgui.h>
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
#include "common/singleton.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "core/libraries/save_data/save_instance.h"
|
||||
#include "imgui/imgui_std.h"
|
||||
#include "savedatadialog_ui.h"
|
||||
|
||||
using namespace ImGui;
|
||||
using namespace Libraries::CommonDialog;
|
||||
|
||||
constexpr u32 OrbisSaveDataBlockSize = 32768; // 32 KiB
|
||||
|
||||
constexpr auto SAVE_ICON_SIZE = ImVec2{152.0f, 85.0f};
|
||||
constexpr auto SAVE_ICON_PADDING = ImVec2{8.0f, 2.0f};
|
||||
|
||||
static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f};
|
||||
constexpr auto FOOTER_HEIGHT = BUTTON_SIZE.y + 15.0f;
|
||||
static constexpr float PROGRESS_BAR_WIDTH{0.8f};
|
||||
|
||||
static ::Core::FileSys::MntPoints* g_mnt =
|
||||
Common::Singleton<::Core::FileSys::MntPoints>::Instance();
|
||||
|
||||
static std::string SpaceSizeToString(size_t size) {
|
||||
std::string size_str;
|
||||
if (size > 1024 * 1024 * 1024) { // > 1GB
|
||||
size_str = fmt::format("{:.2f} GB", double(size / 1024 / 1024) / 1024.0f);
|
||||
} else if (size > 1024 * 1024) { // > 1MB
|
||||
size_str = fmt::format("{:.2f} MB", double(size / 1024) / 1024.0f);
|
||||
} else if (size > 1024) { // > 1KB
|
||||
size_str = fmt::format("{:.2f} KB", double(size) / 1024.0f);
|
||||
} else {
|
||||
size_str = fmt::format("{} B", size);
|
||||
}
|
||||
return size_str;
|
||||
}
|
||||
|
||||
namespace Libraries::SaveData::Dialog {
|
||||
|
||||
void SaveDialogResult::CopyTo(OrbisSaveDataDialogResult& result) const {
|
||||
result.mode = this->mode;
|
||||
result.result = this->result;
|
||||
result.buttonId = this->button_id;
|
||||
if (result.dirName != nullptr) {
|
||||
result.dirName->data.FromString(this->dir_name);
|
||||
}
|
||||
if (result.param != nullptr && this->param.GetString(SaveParams::MAINTITLE).has_value()) {
|
||||
result.param->FromSFO(this->param);
|
||||
}
|
||||
result.userData = this->user_data;
|
||||
}
|
||||
|
||||
SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) {
|
||||
this->mode = param.mode;
|
||||
this->type = param.dispType;
|
||||
this->user_data = param.userData;
|
||||
if (param.optionParam != nullptr) {
|
||||
this->enable_back = {param.optionParam->back == OptionBack::ENABLE};
|
||||
}
|
||||
|
||||
static std::string game_serial{*Common::Singleton<PSF>::Instance()->GetString("CONTENT_ID"), 7,
|
||||
9};
|
||||
|
||||
const auto item = param.items;
|
||||
this->user_id = item->userId;
|
||||
|
||||
if (item->titleId == nullptr) {
|
||||
this->title_id = game_serial;
|
||||
} else {
|
||||
this->title_id = item->titleId->data.to_string();
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < item->dirNameNum; i++) {
|
||||
const auto dir_name = item->dirName[i].data.to_view();
|
||||
|
||||
if (dir_name.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto dir_path = SaveInstance::MakeDirSavePath(user_id, title_id, dir_name);
|
||||
|
||||
auto param_sfo_path = dir_path / "sce_sys" / "param.sfo";
|
||||
if (!std::filesystem::exists(param_sfo_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PSF param_sfo;
|
||||
param_sfo.Open(param_sfo_path);
|
||||
|
||||
auto last_write = param_sfo.GetLastWrite();
|
||||
#ifdef _WIN32
|
||||
auto utc_time = std::chrono::file_clock::to_utc(last_write);
|
||||
#else
|
||||
auto utc_time = std::chrono::file_clock::to_sys(last_write);
|
||||
#endif
|
||||
std::string date_str = fmt::format("{:%d %b, %Y %R}", utc_time);
|
||||
|
||||
size_t size = Common::FS::GetDirectorySize(dir_path);
|
||||
std::string size_str = SpaceSizeToString(size);
|
||||
|
||||
auto icon_path = dir_path / "sce_sys" / "icon0.png";
|
||||
RefCountedTexture icon;
|
||||
if (std::filesystem::exists(icon_path)) {
|
||||
icon = RefCountedTexture::DecodePngFile(icon_path);
|
||||
}
|
||||
|
||||
bool is_corrupted = std::filesystem::exists(dir_path / "sce_sys" / "corrupted");
|
||||
|
||||
this->save_list.emplace_back(Item{
|
||||
.dir_name = std::string{dir_name},
|
||||
.icon = icon,
|
||||
|
||||
.title = std::string{*param_sfo.GetString(SaveParams::MAINTITLE)},
|
||||
.subtitle = std::string{*param_sfo.GetString(SaveParams::SUBTITLE)},
|
||||
.details = std::string{*param_sfo.GetString(SaveParams::DETAIL)},
|
||||
.date = date_str,
|
||||
.size = size_str,
|
||||
.last_write = param_sfo.GetLastWrite(),
|
||||
.pfo = param_sfo,
|
||||
.is_corrupted = is_corrupted,
|
||||
});
|
||||
}
|
||||
|
||||
if (type == DialogType::SAVE) {
|
||||
RefCountedTexture icon;
|
||||
std::string title{"New Save"};
|
||||
|
||||
const auto new_item = item->newItem;
|
||||
if (new_item != nullptr && new_item->iconBuf && new_item->iconSize) {
|
||||
auto buf = (u8*)new_item->iconBuf;
|
||||
icon = RefCountedTexture::DecodePngTexture({buf, buf + new_item->iconSize});
|
||||
} else {
|
||||
const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png");
|
||||
if (std::filesystem::exists(src_icon)) {
|
||||
icon = RefCountedTexture::DecodePngFile(src_icon);
|
||||
}
|
||||
}
|
||||
if (new_item != nullptr && new_item->title != nullptr) {
|
||||
title = std::string{new_item->title};
|
||||
}
|
||||
|
||||
this->new_item = Item{
|
||||
.dir_name = "",
|
||||
.icon = icon,
|
||||
.title = title,
|
||||
};
|
||||
}
|
||||
|
||||
if (item->focusPos != FocusPos::DIRNAME) {
|
||||
this->focus_pos = item->focusPos;
|
||||
} else {
|
||||
this->focus_pos = item->focusPosDirName->data.to_string();
|
||||
}
|
||||
this->style = item->itemStyle;
|
||||
|
||||
switch (mode) {
|
||||
case SaveDataDialogMode::USER_MSG: {
|
||||
this->state = UserState{param};
|
||||
} break;
|
||||
case SaveDataDialogMode::SYSTEM_MSG:
|
||||
this->state = SystemState{*this, param};
|
||||
break;
|
||||
case SaveDataDialogMode::ERROR_CODE: {
|
||||
this->state = ErrorCodeState{param};
|
||||
} break;
|
||||
case SaveDataDialogMode::PROGRESS_BAR: {
|
||||
this->state = ProgressBarState{*this, param};
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SaveDialogState::UserState::UserState(const OrbisSaveDataDialogParam& param) {
|
||||
auto& user = *param.userMsgParam;
|
||||
this->type = user.buttonType;
|
||||
this->msg_type = user.msgType;
|
||||
this->msg = user.msg != nullptr ? std::string{user.msg} : std::string{};
|
||||
}
|
||||
|
||||
SaveDialogState::SystemState::SystemState(const SaveDialogState& state,
|
||||
const OrbisSaveDataDialogParam& param) {
|
||||
#define M(save, load, del) \
|
||||
if (type == DialogType::SAVE) \
|
||||
this->msg = save; \
|
||||
else if (type == DialogType::LOAD) \
|
||||
this->msg = load; \
|
||||
else if (type == DialogType::DELETE) \
|
||||
this->msg = del; \
|
||||
else \
|
||||
UNREACHABLE()
|
||||
|
||||
auto type = param.dispType;
|
||||
auto& sys = *param.sysMsgParam;
|
||||
switch (sys.msgType) {
|
||||
case SystemMessageType::NODATA: {
|
||||
this->msg = "There is no saved data";
|
||||
} break;
|
||||
case SystemMessageType::CONFIRM:
|
||||
show_no = true;
|
||||
M("Do you want to save?", "Do you want to load this saved data?",
|
||||
"Do you want to delete this saved data?");
|
||||
break;
|
||||
case SystemMessageType::OVERWRITE:
|
||||
show_no = true;
|
||||
M("Do you want to overwrite the existing saved data?", "##UNKNOWN##", "##UNKNOWN##");
|
||||
break;
|
||||
case SystemMessageType::NOSPACE:
|
||||
M(fmt::format(
|
||||
"There is not enough space to save the data. To continue {} free space is required.",
|
||||
SpaceSizeToString(sys.value * OrbisSaveDataBlockSize)),
|
||||
"##UNKNOWN##", "##UNKNOWN##");
|
||||
break;
|
||||
case SystemMessageType::PROGRESS:
|
||||
hide_ok = true;
|
||||
show_cancel = state.enable_back.value_or(false);
|
||||
M("Saving...", "Loading...", "Deleting...");
|
||||
break;
|
||||
case SystemMessageType::FILE_CORRUPTED:
|
||||
this->msg = "The saved data is corrupted.";
|
||||
break;
|
||||
case SystemMessageType::FINISHED:
|
||||
M("Saved successfully.", "Loading complete.", "Deletion complete.");
|
||||
break;
|
||||
case SystemMessageType::NOSPACE_CONTINUABLE:
|
||||
M(fmt::format("There is not enough space to save the data. {} free space is required.",
|
||||
SpaceSizeToString(sys.value * OrbisSaveDataBlockSize)),
|
||||
"##UNKNOWN##", "##UNKNOWN##");
|
||||
break;
|
||||
case SystemMessageType::CORRUPTED_AND_DELETED: {
|
||||
show_cancel = state.enable_back.value_or(true);
|
||||
const char* msg1 = "The saved data is corrupted and will be deleted.";
|
||||
M(msg1, msg1, "##UNKNOWN##");
|
||||
} break;
|
||||
case SystemMessageType::CORRUPTED_AND_CREATED: {
|
||||
show_cancel = state.enable_back.value_or(true);
|
||||
const char* msg2 = "The saved data is corrupted. This saved data will be deleted and a new "
|
||||
"one will be created.";
|
||||
M(msg2, msg2, "##UNKNOWN##");
|
||||
} break;
|
||||
case SystemMessageType::CORRUPTED_AND_RESTORE: {
|
||||
show_cancel = state.enable_back.value_or(true);
|
||||
const char* msg3 =
|
||||
"The saved data is corrupted. The data that was backed up by the system will be "
|
||||
"restored.";
|
||||
M(msg3, msg3, "##UNKNOWN##");
|
||||
} break;
|
||||
case SystemMessageType::TOTAL_SIZE_EXCEEDED:
|
||||
M("Cannot create more saved data", "##UNKNOWN##", "##UNKNOWN##");
|
||||
break;
|
||||
default:
|
||||
msg = fmt::format("Unknown message type: {}", magic_enum::enum_name(sys.msgType));
|
||||
break;
|
||||
}
|
||||
|
||||
#undef M
|
||||
}
|
||||
|
||||
SaveDialogState::ErrorCodeState::ErrorCodeState(const OrbisSaveDataDialogParam& param) {
|
||||
auto& err = *param.errorCodeParam;
|
||||
constexpr auto NOT_FOUND = 0x809F0008;
|
||||
constexpr auto BROKEN = 0x809F000F;
|
||||
switch (err.errorCode) {
|
||||
case NOT_FOUND:
|
||||
this->error_msg = "There is not saved data.";
|
||||
break;
|
||||
case BROKEN:
|
||||
this->error_msg = "The data is corrupted.";
|
||||
break;
|
||||
default:
|
||||
this->error_msg = fmt::format("An error has occurred. ({:X})", err.errorCode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
SaveDialogState::ProgressBarState::ProgressBarState(const SaveDialogState& state,
|
||||
const OrbisSaveDataDialogParam& param) {
|
||||
this->progress = 0;
|
||||
|
||||
auto& bar = *param.progressBarParam;
|
||||
switch (bar.sysMsgType) {
|
||||
case ProgressSystemMessageType::INVALID:
|
||||
this->msg = bar.msg != nullptr ? std::string{bar.msg} : std::string{};
|
||||
break;
|
||||
case ProgressSystemMessageType::PROGRESS:
|
||||
switch (state.type) {
|
||||
case DialogType::SAVE:
|
||||
this->msg = "Saving...";
|
||||
break;
|
||||
case DialogType::LOAD:
|
||||
this->msg = "Loading...";
|
||||
break;
|
||||
case DialogType::DELETE:
|
||||
this->msg = "Deleting...";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ProgressSystemMessageType::RESTORE:
|
||||
this->msg = "Restoring saved data...";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SaveDialogUi::SaveDialogUi(SaveDialogState* state, Status* status, SaveDialogResult* result)
|
||||
: state(state), status(status), result(result) {
|
||||
if (status && *status == Status::RUNNING) {
|
||||
first_render = true;
|
||||
AddLayer(this);
|
||||
}
|
||||
}
|
||||
|
||||
SaveDialogUi::~SaveDialogUi() {
|
||||
Finish(ButtonId::INVALID);
|
||||
}
|
||||
|
||||
SaveDialogUi::SaveDialogUi(SaveDialogUi&& other) noexcept
|
||||
: Layer(other), state(other.state), status(other.status), result(other.result) {
|
||||
std::scoped_lock lock(draw_mutex, other.draw_mutex);
|
||||
other.state = nullptr;
|
||||
other.status = nullptr;
|
||||
other.result = nullptr;
|
||||
if (status && *status == Status::RUNNING) {
|
||||
first_render = true;
|
||||
AddLayer(this);
|
||||
}
|
||||
}
|
||||
|
||||
SaveDialogUi& SaveDialogUi::operator=(SaveDialogUi other) {
|
||||
std::scoped_lock lock(draw_mutex, other.draw_mutex);
|
||||
using std::swap;
|
||||
swap(state, other.state);
|
||||
swap(status, other.status);
|
||||
swap(result, other.result);
|
||||
if (status && *status == Status::RUNNING) {
|
||||
first_render = true;
|
||||
AddLayer(this);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void SaveDialogUi::Finish(ButtonId buttonId, Result r) {
|
||||
std::unique_lock lock(draw_mutex);
|
||||
if (result) {
|
||||
result->mode = this->state->mode;
|
||||
result->result = r;
|
||||
result->button_id = buttonId;
|
||||
result->user_data = this->state->user_data;
|
||||
if (state && state->mode != SaveDataDialogMode::LIST && !state->save_list.empty()) {
|
||||
result->dir_name = state->save_list.front().dir_name;
|
||||
}
|
||||
}
|
||||
if (status) {
|
||||
*status = Status::FINISHED;
|
||||
}
|
||||
RemoveLayer(this);
|
||||
}
|
||||
|
||||
void SaveDialogUi::Draw() {
|
||||
std::unique_lock lock{draw_mutex};
|
||||
|
||||
if (status == nullptr || *status != Status::RUNNING || state == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& ctx = *GetCurrentContext();
|
||||
const auto& io = ctx.IO;
|
||||
|
||||
ImVec2 window_size;
|
||||
|
||||
if (state->GetMode() == SaveDataDialogMode::LIST) {
|
||||
window_size = ImVec2{
|
||||
std::min(io.DisplaySize.x - 200.0f, 1100.0f),
|
||||
std::min(io.DisplaySize.y - 100.0f, 700.0f),
|
||||
};
|
||||
} else {
|
||||
window_size = ImVec2{
|
||||
std::min(io.DisplaySize.x, 500.0f),
|
||||
std::min(io.DisplaySize.y, 300.0f),
|
||||
};
|
||||
}
|
||||
|
||||
CentralizeWindow();
|
||||
SetNextWindowSize(window_size);
|
||||
SetNextWindowCollapsed(false);
|
||||
if (first_render || !io.NavActive) {
|
||||
SetNextWindowFocus();
|
||||
}
|
||||
if (Begin("Save Data Dialog##SaveDataDialog", nullptr,
|
||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) {
|
||||
DrawPrettyBackground();
|
||||
|
||||
Separator();
|
||||
// Draw title bigger
|
||||
SetWindowFontScale(1.7f);
|
||||
switch (state->type) {
|
||||
case DialogType::SAVE:
|
||||
TextUnformatted("Save");
|
||||
break;
|
||||
case DialogType::LOAD:
|
||||
TextUnformatted("Load");
|
||||
break;
|
||||
case DialogType::DELETE:
|
||||
TextUnformatted("Delete");
|
||||
break;
|
||||
}
|
||||
SetWindowFontScale(1.0f);
|
||||
Separator();
|
||||
|
||||
BeginGroup();
|
||||
switch (state->GetMode()) {
|
||||
case SaveDataDialogMode::LIST:
|
||||
DrawList();
|
||||
break;
|
||||
case SaveDataDialogMode::USER_MSG:
|
||||
DrawUser();
|
||||
break;
|
||||
case SaveDataDialogMode::SYSTEM_MSG:
|
||||
DrawSystemMessage();
|
||||
break;
|
||||
case SaveDataDialogMode::ERROR_CODE:
|
||||
DrawErrorCode();
|
||||
break;
|
||||
case SaveDataDialogMode::PROGRESS_BAR:
|
||||
DrawProgressBar();
|
||||
break;
|
||||
default:
|
||||
TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "!!!Unknown dialog mode!!!");
|
||||
}
|
||||
EndGroup();
|
||||
}
|
||||
End();
|
||||
|
||||
first_render = false;
|
||||
if (*status == Status::FINISHED) {
|
||||
if (state) {
|
||||
*state = SaveDialogState{};
|
||||
}
|
||||
state = nullptr;
|
||||
status = nullptr;
|
||||
result = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void SaveDialogUi::DrawItem(int _id, const SaveDialogState::Item& item, bool clickable) {
|
||||
constexpr auto text_spacing = 1.2f;
|
||||
|
||||
auto& ctx = *GetCurrentContext();
|
||||
auto& window = *ctx.CurrentWindow;
|
||||
|
||||
auto content_region_avail = GetContentRegionAvail();
|
||||
const auto outer_pos = window.DC.CursorPos;
|
||||
const auto pos = outer_pos + SAVE_ICON_PADDING;
|
||||
|
||||
const ImVec2 size = {content_region_avail.x - SAVE_ICON_PADDING.x,
|
||||
SAVE_ICON_SIZE.y + SAVE_ICON_PADDING.y};
|
||||
const ImRect bb{outer_pos, outer_pos + size + SAVE_ICON_PADDING};
|
||||
|
||||
const ImGuiID id = GetID(_id);
|
||||
|
||||
ItemSize(size + ImVec2{0.0f, SAVE_ICON_PADDING.y * 2.0f});
|
||||
if (!ItemAdd(bb, id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.DrawList->AddRectFilled(bb.Min + SAVE_ICON_PADDING, bb.Max - SAVE_ICON_PADDING,
|
||||
GetColorU32(ImVec4{0.3f}));
|
||||
|
||||
bool hovered = false;
|
||||
if (clickable) {
|
||||
bool held;
|
||||
bool pressed = ButtonBehavior(bb, id, &hovered, &held);
|
||||
if (pressed) {
|
||||
result->dir_name = item.dir_name;
|
||||
result->param = item.pfo;
|
||||
Finish(ButtonId::INVALID);
|
||||
}
|
||||
RenderNavHighlight(bb, id);
|
||||
}
|
||||
|
||||
if (item.icon) {
|
||||
auto texture = item.icon.GetTexture();
|
||||
window.DrawList->AddImage(texture.im_id, pos, pos + SAVE_ICON_SIZE);
|
||||
} else {
|
||||
// placeholder
|
||||
window.DrawList->AddRectFilled(pos, pos + SAVE_ICON_SIZE, GetColorU32(ImVec4{0.7f}));
|
||||
}
|
||||
|
||||
auto pos_x = SAVE_ICON_SIZE.x + 5.0f;
|
||||
auto pos_y = 2.0f;
|
||||
|
||||
if (!item.title.empty()) {
|
||||
const char* begin = &item.title.front();
|
||||
const char* end = &item.title.back() + 1;
|
||||
SetWindowFontScale(2.0f);
|
||||
RenderText(pos + ImVec2{pos_x, pos_y}, begin, end, false);
|
||||
if (item.is_corrupted) {
|
||||
float width = CalcTextSize(begin, end).x + 10.0f;
|
||||
PushStyleColor(ImGuiCol_Text, 0xFF0000FF);
|
||||
RenderText(pos + ImVec2{pos_x + width, pos_y}, "- Corrupted", nullptr, false);
|
||||
PopStyleColor();
|
||||
}
|
||||
pos_y += ctx.FontSize * text_spacing;
|
||||
}
|
||||
|
||||
SetWindowFontScale(1.3f);
|
||||
|
||||
if (state->style == ItemStyle::TITLE_SUBTITLE_DATESIZE) {
|
||||
if (!item.subtitle.empty()) {
|
||||
const char* begin = &item.subtitle.front();
|
||||
const char* end = &item.subtitle.back() + 1;
|
||||
RenderText(pos + ImVec2{pos_x, pos_y}, begin, end, false);
|
||||
}
|
||||
pos_y += ctx.FontSize * text_spacing;
|
||||
}
|
||||
|
||||
{
|
||||
float width = 0.0f;
|
||||
if (!item.date.empty()) {
|
||||
const char* d_begin = &item.date.front();
|
||||
const char* d_end = &item.date.back() + 1;
|
||||
width = CalcTextSize(d_begin, d_end).x + 15.0f;
|
||||
RenderText(pos + ImVec2{pos_x, pos_y}, d_begin, d_end, false);
|
||||
}
|
||||
if (!item.size.empty()) {
|
||||
const char* s_begin = &item.size.front();
|
||||
const char* s_end = &item.size.back() + 1;
|
||||
RenderText(pos + ImVec2{pos_x + width, pos_y}, s_begin, s_end, false);
|
||||
}
|
||||
pos_y += ctx.FontSize * text_spacing;
|
||||
}
|
||||
|
||||
if (state->style == ItemStyle::TITLE_DATASIZE_SUBTITLE && !item.subtitle.empty()) {
|
||||
const char* begin = &item.subtitle.front();
|
||||
const char* end = &item.subtitle.back() + 1;
|
||||
RenderText(pos + ImVec2{pos_x, pos_y}, begin, end, false);
|
||||
}
|
||||
|
||||
SetWindowFontScale(1.0f);
|
||||
|
||||
if (hovered) {
|
||||
window.DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), 0.0f, 0, 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void SaveDialogUi::DrawList() {
|
||||
auto availableSize = GetContentRegionAvail();
|
||||
|
||||
constexpr auto footerHeight = 30.0f;
|
||||
availableSize.y -= footerHeight + 1.0f;
|
||||
|
||||
BeginChild("##ScrollingRegion", availableSize, ImGuiChildFlags_NavFlattened);
|
||||
int i = 0;
|
||||
if (state->new_item.has_value()) {
|
||||
DrawItem(i++, state->new_item.value());
|
||||
}
|
||||
for (const auto& item : state->save_list) {
|
||||
DrawItem(i++, item);
|
||||
}
|
||||
if (first_render) { // Make the initial focus
|
||||
if (std::holds_alternative<FocusPos>(state->focus_pos)) {
|
||||
auto pos = std::get<FocusPos>(state->focus_pos);
|
||||
if (pos == FocusPos::LISTHEAD || pos == FocusPos::DATAHEAD) {
|
||||
SetItemCurrentNavFocus(GetID(0));
|
||||
} else if (pos == FocusPos::LISTTAIL || pos == FocusPos::DATATAIL) {
|
||||
SetItemCurrentNavFocus(GetID(std::max(i - 1, 0)));
|
||||
} else { // Date
|
||||
int idx = 0;
|
||||
int max_idx = 0;
|
||||
bool is_min = pos == FocusPos::DATAOLDEST;
|
||||
std::filesystem::file_time_type max_write{};
|
||||
if (state->new_item.has_value()) {
|
||||
idx++;
|
||||
}
|
||||
for (const auto& item : state->save_list) {
|
||||
if (item.last_write > max_write ^ is_min) {
|
||||
max_write = item.last_write;
|
||||
max_idx = idx;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
SetItemCurrentNavFocus(GetID(max_idx));
|
||||
}
|
||||
} else if (std::holds_alternative<std::string>(state->focus_pos)) {
|
||||
auto dir_name = std::get<std::string>(state->focus_pos);
|
||||
if (dir_name.empty()) {
|
||||
SetItemCurrentNavFocus(GetID(0));
|
||||
} else {
|
||||
int idx = 0;
|
||||
if (state->new_item.has_value()) {
|
||||
if (dir_name == state->new_item->dir_name) {
|
||||
SetItemCurrentNavFocus(GetID(idx));
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
for (const auto& item : state->save_list) {
|
||||
if (item.dir_name == dir_name) {
|
||||
SetItemCurrentNavFocus(GetID(idx));
|
||||
break;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EndChild();
|
||||
|
||||
Separator();
|
||||
if (state->enable_back.value_or(true)) {
|
||||
constexpr auto back = "Back";
|
||||
constexpr float pad = 7.0f;
|
||||
const auto txt_size = CalcTextSize(back);
|
||||
const auto button_size = ImVec2{
|
||||
std::max(txt_size.x, 100.0f) + pad * 2.0f,
|
||||
footerHeight - pad,
|
||||
};
|
||||
SetCursorPosX(GetContentRegionAvail().x - button_size.x);
|
||||
if (Button(back, button_size)) {
|
||||
result->dir_name.clear();
|
||||
Finish(ButtonId::INVALID);
|
||||
}
|
||||
if (IsKeyPressed(ImGuiKey_GamepadFaceRight)) {
|
||||
SetItemCurrentNavFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SaveDialogUi::DrawUser() {
|
||||
const auto& user_state = state->GetState<SaveDialogState::UserState>();
|
||||
const auto btn_type = user_state.type;
|
||||
|
||||
const auto ws = GetWindowSize();
|
||||
|
||||
if (!state->save_list.empty()) {
|
||||
DrawItem(0, state->save_list.front(), false);
|
||||
}
|
||||
|
||||
auto has_btn = btn_type != ButtonType::NONE;
|
||||
ImVec2 btn_space;
|
||||
if (has_btn) {
|
||||
btn_space = ImVec2{0.0f, FOOTER_HEIGHT};
|
||||
}
|
||||
|
||||
const auto& msg = user_state.msg;
|
||||
if (!msg.empty()) {
|
||||
const char* begin = &msg.front();
|
||||
const char* end = &msg.back() + 1;
|
||||
if (user_state.msg_type == UserMessageType::ERROR) {
|
||||
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f));
|
||||
// Maybe make the text bold?
|
||||
}
|
||||
DrawCenteredText(begin, end, GetContentRegionAvail() - btn_space);
|
||||
if (user_state.msg_type == UserMessageType::ERROR) {
|
||||
PopStyleColor();
|
||||
}
|
||||
}
|
||||
|
||||
if (has_btn) {
|
||||
int count = 1;
|
||||
if (btn_type == ButtonType::YESNO || btn_type == ButtonType::ONCANCEL) {
|
||||
++count;
|
||||
}
|
||||
|
||||
SetCursorPos({
|
||||
ws.x / 2.0f - BUTTON_SIZE.x / 2.0f * static_cast<float>(count),
|
||||
ws.y - FOOTER_HEIGHT + 5.0f,
|
||||
});
|
||||
|
||||
BeginGroup();
|
||||
if (btn_type == ButtonType::YESNO) {
|
||||
if (Button("Yes", BUTTON_SIZE)) {
|
||||
Finish(ButtonId::YES);
|
||||
}
|
||||
SameLine();
|
||||
if (Button("No", BUTTON_SIZE)) {
|
||||
Finish(ButtonId::NO);
|
||||
}
|
||||
if (first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) {
|
||||
SetItemCurrentNavFocus();
|
||||
}
|
||||
} else {
|
||||
if (Button("OK", BUTTON_SIZE)) {
|
||||
Finish(ButtonId::OK);
|
||||
}
|
||||
if (first_render) {
|
||||
SetItemCurrentNavFocus();
|
||||
}
|
||||
if (btn_type == ButtonType::ONCANCEL) {
|
||||
SameLine();
|
||||
if (Button("Cancel", BUTTON_SIZE)) {
|
||||
Finish(ButtonId::INVALID, Result::USER_CANCELED);
|
||||
}
|
||||
if (IsKeyPressed(ImGuiKey_GamepadFaceRight)) {
|
||||
SetItemCurrentNavFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
EndGroup();
|
||||
}
|
||||
}
|
||||
|
||||
void SaveDialogUi::DrawSystemMessage() {
|
||||
const auto& sys_state = state->GetState<SaveDialogState::SystemState>();
|
||||
|
||||
if (!state->save_list.empty()) {
|
||||
DrawItem(0, state->save_list.front(), false);
|
||||
}
|
||||
|
||||
const auto ws = GetWindowSize();
|
||||
const auto& msg = sys_state.msg;
|
||||
if (!msg.empty()) {
|
||||
const char* begin = &msg.front();
|
||||
const char* end = &msg.back() + 1;
|
||||
DrawCenteredText(begin, end, GetContentRegionAvail() - ImVec2{0.0f, FOOTER_HEIGHT});
|
||||
}
|
||||
int count = 1;
|
||||
if (sys_state.hide_ok) {
|
||||
--count;
|
||||
}
|
||||
if (sys_state.show_no || sys_state.show_cancel) {
|
||||
++count;
|
||||
}
|
||||
|
||||
SetCursorPos({
|
||||
ws.x / 2.0f - BUTTON_SIZE.x / 2.0f * static_cast<float>(count),
|
||||
ws.y - FOOTER_HEIGHT + 5.0f,
|
||||
});
|
||||
BeginGroup();
|
||||
if (Button(sys_state.show_no ? "Yes" : "OK", BUTTON_SIZE)) {
|
||||
Finish(ButtonId::YES);
|
||||
}
|
||||
SameLine();
|
||||
if (sys_state.show_no) {
|
||||
if (Button("No", BUTTON_SIZE)) {
|
||||
Finish(ButtonId::NO);
|
||||
}
|
||||
} else if (sys_state.show_cancel) {
|
||||
if (Button("Cancel", BUTTON_SIZE)) {
|
||||
Finish(ButtonId::INVALID, Result::USER_CANCELED);
|
||||
}
|
||||
}
|
||||
if (first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) {
|
||||
SetItemCurrentNavFocus();
|
||||
}
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
void SaveDialogUi::DrawErrorCode() {
|
||||
const auto& err_state = state->GetState<SaveDialogState::ErrorCodeState>();
|
||||
|
||||
if (!state->save_list.empty()) {
|
||||
DrawItem(0, state->save_list.front(), false);
|
||||
}
|
||||
|
||||
const auto ws = GetWindowSize();
|
||||
const auto& msg = err_state.error_msg;
|
||||
if (!msg.empty()) {
|
||||
const char* begin = &msg.front();
|
||||
const char* end = &msg.back() + 1;
|
||||
DrawCenteredText(begin, end, GetContentRegionAvail() - ImVec2{0.0f, FOOTER_HEIGHT});
|
||||
}
|
||||
|
||||
SetCursorPos({
|
||||
ws.x / 2.0f - BUTTON_SIZE.x / 2.0f,
|
||||
ws.y - FOOTER_HEIGHT + 5.0f,
|
||||
});
|
||||
if (Button("OK", BUTTON_SIZE)) {
|
||||
Finish(ButtonId::OK);
|
||||
}
|
||||
if (first_render) {
|
||||
SetItemCurrentNavFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void SaveDialogUi::DrawProgressBar() {
|
||||
const auto& bar_state = state->GetState<SaveDialogState::ProgressBarState>();
|
||||
|
||||
const auto ws = GetWindowSize();
|
||||
|
||||
if (!state->save_list.empty()) {
|
||||
DrawItem(0, state->save_list.front(), false);
|
||||
}
|
||||
|
||||
const auto& msg = bar_state.msg;
|
||||
if (!msg.empty()) {
|
||||
const char* begin = &msg.front();
|
||||
const char* end = &msg.back() + 1;
|
||||
DrawCenteredText(begin, end, GetContentRegionAvail() - ImVec2{0.0f, FOOTER_HEIGHT});
|
||||
}
|
||||
|
||||
SetCursorPos({
|
||||
ws.x * ((1 - PROGRESS_BAR_WIDTH) / 2.0f),
|
||||
ws.y - FOOTER_HEIGHT + 5.0f,
|
||||
});
|
||||
|
||||
ProgressBar(static_cast<float>(bar_state.progress) / 100.0f,
|
||||
{PROGRESS_BAR_WIDTH * ws.x, BUTTON_SIZE.y});
|
||||
}
|
||||
}; // namespace Libraries::SaveData::Dialog
|
317
src/core/libraries/save_data/dialog/savedatadialog_ui.h
Normal file
317
src/core/libraries/save_data/dialog/savedatadialog_ui.h
Normal file
@ -0,0 +1,317 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "core/file_format/psf.h"
|
||||
#include "core/libraries/save_data/savedata.h"
|
||||
#include "core/libraries/system/commondialog.h"
|
||||
#include "imgui/imgui_layer.h"
|
||||
#include "imgui/imgui_texture.h"
|
||||
|
||||
namespace Libraries::SaveData::Dialog {
|
||||
|
||||
using OrbisUserServiceUserId = s32;
|
||||
|
||||
enum class SaveDataDialogMode : u32 {
|
||||
INVALID = 0,
|
||||
LIST = 1,
|
||||
USER_MSG = 2,
|
||||
SYSTEM_MSG = 3,
|
||||
ERROR_CODE = 4,
|
||||
PROGRESS_BAR = 5,
|
||||
};
|
||||
|
||||
enum class DialogType : u32 {
|
||||
SAVE = 1,
|
||||
LOAD = 2,
|
||||
DELETE = 3,
|
||||
};
|
||||
|
||||
enum class DialogAnimation : u32 {
|
||||
ON = 0,
|
||||
OFF = 1,
|
||||
};
|
||||
|
||||
enum class ButtonId : u32 {
|
||||
INVALID = 0,
|
||||
OK = 1,
|
||||
YES = 1,
|
||||
NO = 2,
|
||||
};
|
||||
|
||||
enum class ButtonType : u32 {
|
||||
OK = 0,
|
||||
YESNO = 1,
|
||||
NONE = 2,
|
||||
ONCANCEL = 3,
|
||||
};
|
||||
|
||||
enum class UserMessageType : u32 {
|
||||
NORMAL = 0,
|
||||
ERROR = 1,
|
||||
};
|
||||
|
||||
enum class FocusPos : u32 {
|
||||
LISTHEAD = 0,
|
||||
LISTTAIL = 1,
|
||||
DATAHEAD = 2,
|
||||
DATATAIL = 3,
|
||||
DATALTATEST = 4,
|
||||
DATAOLDEST = 5,
|
||||
DIRNAME = 6,
|
||||
};
|
||||
|
||||
enum class ItemStyle : u32 {
|
||||
TITLE_DATASIZE_SUBTITLE = 0,
|
||||
TITLE_SUBTITLE_DATESIZE = 1,
|
||||
TITLE_DATESIZE = 2,
|
||||
};
|
||||
|
||||
enum class SystemMessageType : u32 {
|
||||
NODATA = 1,
|
||||
CONFIRM = 2,
|
||||
OVERWRITE = 3,
|
||||
NOSPACE = 4,
|
||||
PROGRESS = 5,
|
||||
FILE_CORRUPTED = 6,
|
||||
FINISHED = 7,
|
||||
NOSPACE_CONTINUABLE = 8,
|
||||
CORRUPTED_AND_DELETED = 10,
|
||||
CORRUPTED_AND_CREATED = 11,
|
||||
CORRUPTED_AND_RESTORE = 13,
|
||||
TOTAL_SIZE_EXCEEDED = 14,
|
||||
};
|
||||
|
||||
enum class ProgressBarType : u32 {
|
||||
PERCENTAGE = 0,
|
||||
};
|
||||
|
||||
enum class ProgressSystemMessageType : u32 {
|
||||
INVALID = 0,
|
||||
PROGRESS = 1,
|
||||
RESTORE = 2,
|
||||
};
|
||||
|
||||
enum class OptionBack : u32 {
|
||||
ENABLE = 0,
|
||||
DISABLE = 1,
|
||||
};
|
||||
|
||||
enum class OrbisSaveDataDialogProgressBarTarget : u32 {
|
||||
DEFAULT = 0,
|
||||
};
|
||||
|
||||
struct AnimationParam {
|
||||
DialogAnimation userOK;
|
||||
DialogAnimation userCancel;
|
||||
std::array<u8, 32> _reserved;
|
||||
};
|
||||
|
||||
struct SaveDialogNewItem {
|
||||
const char* title;
|
||||
void* iconBuf;
|
||||
size_t iconSize;
|
||||
std::array<u8, 32> _reserved;
|
||||
};
|
||||
|
||||
struct SaveDialogItems {
|
||||
OrbisUserServiceUserId userId;
|
||||
s32 : 32;
|
||||
const OrbisSaveDataTitleId* titleId;
|
||||
const OrbisSaveDataDirName* dirName;
|
||||
u32 dirNameNum;
|
||||
s32 : 32;
|
||||
const SaveDialogNewItem* newItem;
|
||||
FocusPos focusPos;
|
||||
s32 : 32;
|
||||
const OrbisSaveDataDirName* focusPosDirName;
|
||||
ItemStyle itemStyle;
|
||||
std::array<u8, 32> _reserved;
|
||||
};
|
||||
|
||||
struct UserMessageParam {
|
||||
ButtonType buttonType;
|
||||
UserMessageType msgType;
|
||||
const char* msg;
|
||||
std::array<u8, 32> _reserved;
|
||||
};
|
||||
|
||||
struct SystemMessageParam {
|
||||
SystemMessageType msgType;
|
||||
s32 : 32;
|
||||
u64 value;
|
||||
std::array<u8, 32> _reserved;
|
||||
};
|
||||
|
||||
struct ErrorCodeParam {
|
||||
u32 errorCode;
|
||||
std::array<u8, 32> _reserved;
|
||||
};
|
||||
|
||||
struct ProgressBarParam {
|
||||
ProgressBarType barType;
|
||||
s32 : 32;
|
||||
const char* msg;
|
||||
ProgressSystemMessageType sysMsgType;
|
||||
std::array<u8, 28> _reserved;
|
||||
};
|
||||
|
||||
struct OptionParam {
|
||||
OptionBack back;
|
||||
std::array<u8, 32> _reserved;
|
||||
};
|
||||
|
||||
struct OrbisSaveDataDialogParam {
|
||||
CommonDialog::BaseParam baseParam;
|
||||
s32 size;
|
||||
SaveDataDialogMode mode;
|
||||
DialogType dispType;
|
||||
s32 : 32;
|
||||
AnimationParam* animParam;
|
||||
SaveDialogItems* items;
|
||||
UserMessageParam* userMsgParam;
|
||||
SystemMessageParam* sysMsgParam;
|
||||
ErrorCodeParam* errorCodeParam;
|
||||
ProgressBarParam* progressBarParam;
|
||||
void* userData;
|
||||
OptionParam* optionParam;
|
||||
std::array<u8, 24> _reserved;
|
||||
};
|
||||
|
||||
struct OrbisSaveDataDialogResult {
|
||||
SaveDataDialogMode mode{};
|
||||
CommonDialog::Result result{};
|
||||
ButtonId buttonId{};
|
||||
s32 : 32;
|
||||
OrbisSaveDataDirName* dirName;
|
||||
OrbisSaveDataParam* param;
|
||||
void* userData;
|
||||
std::array<u8, 32> _reserved;
|
||||
};
|
||||
|
||||
struct SaveDialogResult {
|
||||
SaveDataDialogMode mode{};
|
||||
CommonDialog::Result result{CommonDialog::Result::OK};
|
||||
ButtonId button_id{ButtonId::INVALID};
|
||||
std::string dir_name{};
|
||||
PSF param{};
|
||||
void* user_data{};
|
||||
|
||||
void CopyTo(OrbisSaveDataDialogResult& result) const;
|
||||
};
|
||||
|
||||
class SaveDialogState {
|
||||
friend class SaveDialogUi;
|
||||
|
||||
public:
|
||||
struct UserState {
|
||||
ButtonType type{};
|
||||
UserMessageType msg_type{};
|
||||
std::string msg{};
|
||||
|
||||
UserState(const OrbisSaveDataDialogParam& param);
|
||||
};
|
||||
struct SystemState {
|
||||
std::string msg{};
|
||||
bool hide_ok{};
|
||||
bool show_no{}; // Yes instead of OK
|
||||
bool show_cancel{};
|
||||
|
||||
SystemState(const SaveDialogState& state, const OrbisSaveDataDialogParam& param);
|
||||
};
|
||||
struct ErrorCodeState {
|
||||
std::string error_msg{};
|
||||
|
||||
ErrorCodeState(const OrbisSaveDataDialogParam& param);
|
||||
};
|
||||
struct ProgressBarState {
|
||||
std::string msg{};
|
||||
u32 progress{};
|
||||
|
||||
ProgressBarState(const SaveDialogState& state, const OrbisSaveDataDialogParam& param);
|
||||
};
|
||||
|
||||
struct Item {
|
||||
std::string dir_name{};
|
||||
ImGui::RefCountedTexture icon{};
|
||||
|
||||
std::string title{};
|
||||
std::string subtitle{};
|
||||
std::string details{};
|
||||
std::string date{};
|
||||
std::string size{};
|
||||
|
||||
std::filesystem::file_time_type last_write{};
|
||||
PSF pfo{};
|
||||
bool is_corrupted{};
|
||||
};
|
||||
|
||||
private:
|
||||
SaveDataDialogMode mode{};
|
||||
DialogType type{};
|
||||
void* user_data{};
|
||||
std::optional<bool> enable_back{};
|
||||
|
||||
OrbisUserServiceUserId user_id{};
|
||||
std::string title_id{};
|
||||
std::vector<Item> save_list{};
|
||||
std::variant<FocusPos, std::string, std::monostate> focus_pos{std::monostate{}};
|
||||
ItemStyle style{};
|
||||
|
||||
std::optional<Item> new_item{};
|
||||
|
||||
std::variant<UserState, SystemState, ErrorCodeState, ProgressBarState, std::monostate> state{
|
||||
std::monostate{}};
|
||||
|
||||
public:
|
||||
explicit SaveDialogState(const OrbisSaveDataDialogParam& param);
|
||||
|
||||
SaveDialogState() = default;
|
||||
|
||||
[[nodiscard]] SaveDataDialogMode GetMode() const {
|
||||
return mode;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] T& GetState() {
|
||||
return std::get<T>(state);
|
||||
}
|
||||
};
|
||||
|
||||
class SaveDialogUi final : public ImGui::Layer {
|
||||
bool first_render{false};
|
||||
SaveDialogState* state{};
|
||||
CommonDialog::Status* status{};
|
||||
SaveDialogResult* result{};
|
||||
|
||||
std::recursive_mutex draw_mutex{};
|
||||
|
||||
public:
|
||||
explicit SaveDialogUi(SaveDialogState* state = nullptr, CommonDialog::Status* status = nullptr,
|
||||
SaveDialogResult* result = nullptr);
|
||||
|
||||
~SaveDialogUi() override;
|
||||
SaveDialogUi(const SaveDialogUi& other) = delete;
|
||||
SaveDialogUi(SaveDialogUi&& other) noexcept;
|
||||
SaveDialogUi& operator=(SaveDialogUi other);
|
||||
|
||||
void Finish(ButtonId buttonId, CommonDialog::Result r = CommonDialog::Result::OK);
|
||||
|
||||
void Draw() override;
|
||||
|
||||
private:
|
||||
void DrawItem(int id, const SaveDialogState::Item& item, bool clickable = true);
|
||||
|
||||
void DrawList();
|
||||
void DrawUser();
|
||||
void DrawSystemMessage();
|
||||
void DrawErrorCode();
|
||||
void DrawProgressBar();
|
||||
};
|
||||
|
||||
}; // namespace Libraries::SaveData::Dialog
|
@ -1,28 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_PARAMETER = 0x809F0000;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_NOT_INITIALIZED = 0x809F0001;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_OUT_OF_MEMORY = 0x809F0002;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_BUSY = 0x809F0003;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED = 0x809F0004;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_NO_PERMISSION = 0x809F0005;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_FINGERPRINT_MISMATCH = 0x809F0006;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_EXISTS = 0x809F0007;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_NOT_FOUND = 0x809F0008;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_NO_SPACE_FS = 0x809F000A;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_INTERNAL = 0x809F000B;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_MOUNT_FULL = 0x809F000C;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_BAD_MOUNTED = 0x809F000D;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_FILE_NOT_FOUND = 0x809F000E;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_BROKEN = 0x809F000F;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_INVALID_LOGIN_USER = 0x809F0011;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_MEMORY_NOT_READY = 0x809F0012;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_BACKUP_BUSY = 0x809F0013;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_NOT_REGIST_CALLBACK = 0x809F0015;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_BUSY_FOR_SAVING = 0x809F0016;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_LIMITATION_OVER = 0x809F0017;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_EVENT_BUSY = 0x809F0018;
|
||||
constexpr int ORBIS_SAVE_DATA_ERROR_PARAMSFO_TRANSFER_TITLE_ID_NOT_FOUND = 0x809F0019;
|
207
src/core/libraries/save_data/save_backup.cpp
Normal file
207
src/core/libraries/save_data/save_backup.cpp
Normal file
@ -0,0 +1,207 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <semaphore>
|
||||
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
#include "save_backup.h"
|
||||
#include "save_instance.h"
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging/log_entry.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/thread.h"
|
||||
|
||||
constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save
|
||||
constexpr std::string_view backup_dir = "sce_backup"; // backup folder
|
||||
constexpr std::string_view backup_dir_tmp = "sce_backup_tmp"; // in-progress backup folder
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Libraries::SaveData::Backup {
|
||||
|
||||
static std::jthread g_backup_thread;
|
||||
static std::counting_semaphore g_backup_thread_semaphore{0};
|
||||
|
||||
static std::mutex g_backup_queue_mutex;
|
||||
static std::deque<BackupRequest> g_backup_queue;
|
||||
static std::deque<BackupRequest> g_result_queue;
|
||||
|
||||
static std::atomic_int g_backup_progress = 0;
|
||||
static std::atomic g_backup_status = WorkerStatus::NotStarted;
|
||||
|
||||
static void backup(const std::filesystem::path& dir_name) {
|
||||
if (!fs::exists(dir_name)) {
|
||||
return;
|
||||
}
|
||||
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 && filename != backup_dir_tmp) {
|
||||
backup_files.push_back(entry.path());
|
||||
}
|
||||
}
|
||||
const auto backup_dir = dir_name / ::backup_dir;
|
||||
const auto backup_dir_tmp = dir_name / ::backup_dir_tmp;
|
||||
|
||||
g_backup_progress = 0;
|
||||
|
||||
int total_count = static_cast<int>(backup_files.size());
|
||||
int current_count = 0;
|
||||
|
||||
fs::remove_all(backup_dir_tmp);
|
||||
fs::create_directory(backup_dir_tmp);
|
||||
for (const auto& file : backup_files) {
|
||||
fs::copy(file, backup_dir_tmp / file.filename(), fs::copy_options::recursive);
|
||||
current_count++;
|
||||
g_backup_progress = current_count * 100 / total_count;
|
||||
}
|
||||
bool has_existing = fs::exists(backup_dir);
|
||||
if (has_existing) {
|
||||
fs::rename(backup_dir, dir_name / "sce_backup_old");
|
||||
}
|
||||
fs::rename(backup_dir_tmp, backup_dir);
|
||||
if (has_existing) {
|
||||
fs::remove_all(dir_name / "sce_backup_old");
|
||||
}
|
||||
}
|
||||
|
||||
static void BackupThreadBody() {
|
||||
Common::SetCurrentThreadName("SaveData_BackupThread");
|
||||
while (true) {
|
||||
g_backup_status = WorkerStatus::Waiting;
|
||||
g_backup_thread_semaphore.acquire();
|
||||
BackupRequest req;
|
||||
{
|
||||
std::scoped_lock lk{g_backup_queue_mutex};
|
||||
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());
|
||||
backup(req.save_path);
|
||||
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.pop_front();
|
||||
g_result_queue.push_back(std::move(req));
|
||||
if (g_result_queue.size() > 20) {
|
||||
g_result_queue.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
g_backup_status = WorkerStatus::NotStarted;
|
||||
}
|
||||
|
||||
void StartThread() {
|
||||
if (g_backup_status != WorkerStatus::NotStarted) {
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG(Lib_SaveData, "Starting backup thread");
|
||||
g_backup_thread = std::jthread{BackupThreadBody};
|
||||
g_backup_status = WorkerStatus::Waiting;
|
||||
}
|
||||
|
||||
void StopThread() {
|
||||
if (g_backup_status == WorkerStatus::NotStarted || g_backup_status == WorkerStatus::Stopping) {
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG(Lib_SaveData, "Stopping backup thread");
|
||||
{
|
||||
std::scoped_lock lk{g_backup_queue_mutex};
|
||||
g_backup_queue.emplace_back(BackupRequest{});
|
||||
}
|
||||
g_backup_thread_semaphore.release();
|
||||
g_backup_status = WorkerStatus::Stopping;
|
||||
}
|
||||
|
||||
bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id,
|
||||
std::string_view dir_name, OrbisSaveDataEventType origin) {
|
||||
auto save_path = SaveInstance::MakeDirSavePath(user_id, title_id, dir_name);
|
||||
|
||||
if (g_backup_status != WorkerStatus::Waiting && g_backup_status != WorkerStatus::Running) {
|
||||
LOG_ERROR(Lib_SaveData, "Called backup while status is {}. Backup request to {} ignored",
|
||||
magic_enum::enum_name(g_backup_status.load()), save_path.string());
|
||||
return false;
|
||||
}
|
||||
{
|
||||
std::scoped_lock lk{g_backup_queue_mutex};
|
||||
g_backup_queue.push_back(BackupRequest{
|
||||
.user_id = user_id,
|
||||
.title_id = std::string{title_id},
|
||||
.dir_name = std::string{dir_name},
|
||||
.origin = origin,
|
||||
.save_path = std::move(save_path),
|
||||
});
|
||||
}
|
||||
g_backup_thread_semaphore.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Restore(const std::filesystem::path& save_path) {
|
||||
LOG_INFO(Lib_SaveData, "Restoring backup for {}", save_path.string());
|
||||
if (!fs::exists(save_path) || !fs::exists(save_path / backup_dir)) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& entry : fs::directory_iterator(save_path)) {
|
||||
const auto filename = entry.path().filename();
|
||||
if (filename != backup_dir) {
|
||||
fs::remove_all(entry.path());
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& entry : fs::directory_iterator(save_path / backup_dir)) {
|
||||
const auto filename = entry.path().filename();
|
||||
fs::copy(entry.path(), save_path / filename, fs::copy_options::recursive);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
WorkerStatus GetWorkerStatus() {
|
||||
return g_backup_status;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
std::filesystem::path MakeBackupPath(const std::filesystem::path& save_path) {
|
||||
return save_path / backup_dir;
|
||||
}
|
||||
|
||||
std::optional<BackupRequest> PopLastEvent() {
|
||||
std::scoped_lock lk{g_backup_queue_mutex};
|
||||
if (g_result_queue.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto req = std::move(g_result_queue.front());
|
||||
g_result_queue.pop_front();
|
||||
return req;
|
||||
}
|
||||
|
||||
void PushBackupEvent(BackupRequest&& req) {
|
||||
std::scoped_lock lk{g_backup_queue_mutex};
|
||||
g_result_queue.push_back(std::move(req));
|
||||
if (g_result_queue.size() > 20) {
|
||||
g_result_queue.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
float GetProgress() {
|
||||
return static_cast<float>(g_backup_progress) / 100.0f;
|
||||
}
|
||||
|
||||
void ClearProgress() {
|
||||
g_backup_progress = 0;
|
||||
}
|
||||
|
||||
} // namespace Libraries::SaveData::Backup
|
64
src/core/libraries/save_data/save_backup.h
Normal file
64
src/core/libraries/save_data/save_backup.h
Normal file
@ -0,0 +1,64 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Libraries::SaveData {
|
||||
|
||||
using OrbisUserServiceUserId = s32;
|
||||
|
||||
namespace Backup {
|
||||
|
||||
enum class WorkerStatus {
|
||||
NotStarted,
|
||||
Waiting,
|
||||
Running,
|
||||
Stopping,
|
||||
};
|
||||
|
||||
enum class OrbisSaveDataEventType : u32 {
|
||||
UMOUNT_BACKUP = 1,
|
||||
BACKUP = 2,
|
||||
SAVE_DATA_MEMORY_SYNC = 3,
|
||||
};
|
||||
|
||||
struct BackupRequest {
|
||||
OrbisUserServiceUserId user_id{};
|
||||
std::string title_id{};
|
||||
std::string dir_name{};
|
||||
OrbisSaveDataEventType origin{};
|
||||
|
||||
std::filesystem::path save_path{};
|
||||
};
|
||||
|
||||
// No problem calling this function if the backup thread is already running
|
||||
void StartThread();
|
||||
|
||||
void StopThread();
|
||||
|
||||
bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id,
|
||||
std::string_view dir_name, OrbisSaveDataEventType origin);
|
||||
|
||||
bool Restore(const std::filesystem::path& save_path);
|
||||
|
||||
WorkerStatus GetWorkerStatus();
|
||||
|
||||
bool IsBackupExecutingFor(const std::filesystem::path& save_path);
|
||||
|
||||
std::filesystem::path MakeBackupPath(const std::filesystem::path& save_path);
|
||||
|
||||
std::optional<BackupRequest> PopLastEvent();
|
||||
|
||||
void PushBackupEvent(BackupRequest&& req);
|
||||
|
||||
float GetProgress();
|
||||
|
||||
void ClearProgress();
|
||||
|
||||
} // namespace Backup
|
||||
|
||||
} // namespace Libraries::SaveData
|
228
src/core/libraries/save_data/save_instance.cpp
Normal file
228
src/core/libraries/save_data/save_instance.cpp
Normal file
@ -0,0 +1,228 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/path_util.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "save_instance.h"
|
||||
|
||||
constexpr u32 OrbisSaveDataBlocksMax = 32768; // 1 GiB
|
||||
constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save
|
||||
constexpr std::string_view max_block_file_name = "max_block.txt";
|
||||
|
||||
static Core::FileSys::MntPoints* g_mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// clang-format off
|
||||
static const std::unordered_map<std::string, std::string> default_title = {
|
||||
{"ja_JP", "セーブデータ"},
|
||||
{"en", "Saved Data"},
|
||||
{"fr", "Données sauvegardées"},
|
||||
{"es_ES", "Datos guardados"},
|
||||
{"de", "Gespeicherte Daten"},
|
||||
{"it", "Dati salvati"},
|
||||
{"nl", "Opgeslagen data"},
|
||||
{"pt_PT", "Dados guardados"},
|
||||
{"ru", "Сохраненные данные"},
|
||||
{"ko_KR", "저장 데이터"},
|
||||
{"zh_CN", "保存数据"},
|
||||
{"fi", "Tallennetut tiedot"},
|
||||
{"sv_SE", "Sparade data"},
|
||||
{"da_DK", "Gemte data"},
|
||||
{"no_NO", "Lagrede data"},
|
||||
{"pl_PL", "Zapisane dane"},
|
||||
{"pt_BR", "Dados salvos"},
|
||||
{"tr_TR", "Kayıtlı Veriler"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
namespace Libraries::SaveData {
|
||||
|
||||
std::filesystem::path SaveInstance::MakeTitleSavePath(OrbisUserServiceUserId user_id,
|
||||
std::string_view game_serial) {
|
||||
return Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / std::to_string(user_id) /
|
||||
game_serial;
|
||||
}
|
||||
|
||||
std::filesystem::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_id,
|
||||
std::string_view game_serial,
|
||||
std::string_view dir_name) {
|
||||
return Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / std::to_string(user_id) /
|
||||
game_serial / dir_name;
|
||||
}
|
||||
|
||||
int SaveInstance::GetMaxBlocks(const std::filesystem::path& save_path) {
|
||||
Common::FS::IOFile max_blocks_file{save_path / sce_sys / max_block_file_name,
|
||||
Common::FS::FileAccessMode::Read};
|
||||
int max_blocks = 0;
|
||||
if (max_blocks_file.IsOpen()) {
|
||||
max_blocks = std::atoi(max_blocks_file.ReadString(16).c_str());
|
||||
}
|
||||
if (max_blocks <= 0) {
|
||||
max_blocks = OrbisSaveDataBlocksMax;
|
||||
}
|
||||
|
||||
return max_blocks;
|
||||
}
|
||||
|
||||
std::filesystem::path SaveInstance::GetParamSFOPath(const std::filesystem::path& dir_path) {
|
||||
return dir_path / sce_sys / "param.sfo";
|
||||
}
|
||||
|
||||
void SaveInstance::SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name,
|
||||
std::string game_serial) {
|
||||
std::string locale = Config::getEmulatorLanguage();
|
||||
if (!default_title.contains(locale)) {
|
||||
locale = "en";
|
||||
}
|
||||
|
||||
#define P(type, key, ...) param_sfo.Add##type(std::string{key}, __VA_ARGS__)
|
||||
// TODO Link with user service
|
||||
P(Binary, SaveParams::ACCOUNT_ID, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||
P(String, SaveParams::MAINTITLE, default_title.at(locale));
|
||||
P(String, SaveParams::SUBTITLE, "");
|
||||
P(String, SaveParams::DETAIL, "");
|
||||
P(String, SaveParams::SAVEDATA_DIRECTORY, std::move(dir_name));
|
||||
P(Integer, SaveParams::SAVEDATA_LIST_PARAM, 0);
|
||||
P(String, SaveParams::TITLE_ID, std::move(game_serial));
|
||||
#undef P
|
||||
}
|
||||
|
||||
SaveInstance::SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::string _game_serial,
|
||||
std::string_view _dir_name, int max_blocks)
|
||||
: slot_num(slot_num), user_id(user_id), game_serial(std::move(_game_serial)),
|
||||
dir_name(_dir_name), max_blocks(max_blocks) {
|
||||
ASSERT(slot_num >= 0 && slot_num < 16);
|
||||
|
||||
save_path = MakeDirSavePath(user_id, game_serial, dir_name);
|
||||
|
||||
const auto sce_sys_path = save_path / sce_sys;
|
||||
param_sfo_path = sce_sys_path / "param.sfo";
|
||||
corrupt_file_path = sce_sys_path / "corrupted";
|
||||
|
||||
mount_point = "/savedata" + std::to_string(slot_num);
|
||||
|
||||
this->exists = fs::exists(param_sfo_path);
|
||||
this->mounted = g_mnt->GetMount(mount_point) != nullptr;
|
||||
}
|
||||
|
||||
SaveInstance::~SaveInstance() {
|
||||
if (mounted) {
|
||||
Umount();
|
||||
}
|
||||
}
|
||||
SaveInstance::SaveInstance(SaveInstance&& other) noexcept {
|
||||
if (this == &other)
|
||||
return;
|
||||
*this = std::move(other);
|
||||
}
|
||||
|
||||
SaveInstance& SaveInstance::operator=(SaveInstance&& other) noexcept {
|
||||
if (this == &other)
|
||||
return *this;
|
||||
slot_num = other.slot_num;
|
||||
user_id = other.user_id;
|
||||
game_serial = std::move(other.game_serial);
|
||||
dir_name = std::move(other.dir_name);
|
||||
save_path = std::move(other.save_path);
|
||||
param_sfo_path = std::move(other.param_sfo_path);
|
||||
corrupt_file_path = std::move(other.corrupt_file_path);
|
||||
corrupt_file = std::move(other.corrupt_file);
|
||||
param_sfo = std::move(other.param_sfo);
|
||||
mount_point = std::move(other.mount_point);
|
||||
max_blocks = other.max_blocks;
|
||||
exists = other.exists;
|
||||
mounted = other.mounted;
|
||||
read_only = other.read_only;
|
||||
|
||||
other.mounted = false;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_corrupt) {
|
||||
if (mounted) {
|
||||
UNREACHABLE_MSG("Save instance is already mounted");
|
||||
}
|
||||
this->exists = fs::exists(param_sfo_path); // check again just in case
|
||||
if (!exists) {
|
||||
CreateFiles();
|
||||
if (copy_icon) {
|
||||
const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png");
|
||||
if (fs::exists(src_icon)) {
|
||||
fs::copy_file(src_icon, GetIconPath());
|
||||
}
|
||||
}
|
||||
exists = true;
|
||||
} else {
|
||||
if (!ignore_corrupt && fs::exists(corrupt_file_path)) {
|
||||
throw std::filesystem::filesystem_error(
|
||||
"Corrupted save data", corrupt_file_path,
|
||||
std::make_error_code(std::errc::illegal_byte_sequence));
|
||||
}
|
||||
if (!param_sfo.Open(param_sfo_path)) {
|
||||
throw std::filesystem::filesystem_error(
|
||||
"Failed to read param.sfo", param_sfo_path,
|
||||
std::make_error_code(std::errc::illegal_byte_sequence));
|
||||
}
|
||||
}
|
||||
|
||||
if (!ignore_corrupt && !read_only) {
|
||||
int err = corrupt_file.Open(corrupt_file_path, Common::FS::FileAccessMode::Write);
|
||||
if (err != 0) {
|
||||
throw std::filesystem::filesystem_error(
|
||||
"Failed to open corrupted file", corrupt_file_path,
|
||||
std::make_error_code(std::errc::illegal_byte_sequence));
|
||||
}
|
||||
}
|
||||
|
||||
max_blocks = GetMaxBlocks(save_path);
|
||||
|
||||
g_mnt->Mount(save_path, mount_point, read_only);
|
||||
mounted = true;
|
||||
this->read_only = read_only;
|
||||
}
|
||||
|
||||
void SaveInstance::Umount() {
|
||||
if (!mounted) {
|
||||
UNREACHABLE_MSG("Save instance is not mounted");
|
||||
return;
|
||||
}
|
||||
mounted = false;
|
||||
const bool ok = param_sfo.Encode(param_sfo_path);
|
||||
if (!ok) {
|
||||
throw std::filesystem::filesystem_error("Failed to write param.sfo", param_sfo_path,
|
||||
std::make_error_code(std::errc::permission_denied));
|
||||
}
|
||||
param_sfo = PSF();
|
||||
|
||||
corrupt_file.Close();
|
||||
fs::remove(corrupt_file_path);
|
||||
g_mnt->Unmount(save_path, mount_point);
|
||||
}
|
||||
|
||||
void SaveInstance::CreateFiles() {
|
||||
const auto sce_sys_dir = save_path / sce_sys;
|
||||
fs::create_directories(sce_sys_dir);
|
||||
|
||||
SetupDefaultParamSFO(param_sfo, dir_name, game_serial);
|
||||
|
||||
const bool ok = param_sfo.Encode(param_sfo_path);
|
||||
if (!ok) {
|
||||
throw std::filesystem::filesystem_error("Failed to write param.sfo", param_sfo_path,
|
||||
std::make_error_code(std::errc::permission_denied));
|
||||
}
|
||||
|
||||
Common::FS::IOFile max_block{sce_sys_dir / max_block_file_name,
|
||||
Common::FS::FileAccessMode::Write};
|
||||
max_block.WriteString(std::to_string(max_blocks == 0 ? OrbisSaveDataBlocksMax : max_blocks));
|
||||
}
|
||||
|
||||
} // namespace Libraries::SaveData
|
138
src/core/libraries/save_data/save_instance.h
Normal file
138
src/core/libraries/save_data/save_instance.h
Normal file
@ -0,0 +1,138 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include "common/io_file.h"
|
||||
#include "core/file_format/psf.h"
|
||||
|
||||
namespace Core::FileSys {
|
||||
class MntPoints;
|
||||
}
|
||||
|
||||
namespace Libraries::SaveData {
|
||||
|
||||
// Used constexpr to easily use as string
|
||||
namespace SaveParams {
|
||||
constexpr std::string_view ACCOUNT_ID = "ACCOUNT_ID";
|
||||
constexpr std::string_view ATTRIBUTE = "ATTRIBUTE";
|
||||
constexpr std::string_view CATEGORY = "CATEGORY";
|
||||
constexpr std::string_view DETAIL = "DETAIL";
|
||||
constexpr std::string_view FORMAT = "FORMAT";
|
||||
constexpr std::string_view MAINTITLE = "MAINTITLE";
|
||||
constexpr std::string_view PARAMS = "PARAMS";
|
||||
constexpr std::string_view SAVEDATA_BLOCKS = "SAVEDATA_BLOCKS";
|
||||
constexpr std::string_view SAVEDATA_DIRECTORY = "SAVEDATA_DIRECTORY";
|
||||
constexpr std::string_view SAVEDATA_LIST_PARAM = "SAVEDATA_LIST_PARAM";
|
||||
constexpr std::string_view SUBTITLE = "SUBTITLE";
|
||||
constexpr std::string_view TITLE_ID = "TITLE_ID";
|
||||
} // namespace SaveParams
|
||||
|
||||
using OrbisUserServiceUserId = s32;
|
||||
|
||||
class SaveInstance {
|
||||
int slot_num{};
|
||||
int user_id{};
|
||||
std::string game_serial;
|
||||
std::string dir_name;
|
||||
|
||||
std::filesystem::path save_path;
|
||||
std::filesystem::path param_sfo_path;
|
||||
std::filesystem::path corrupt_file_path;
|
||||
|
||||
Common::FS::IOFile corrupt_file;
|
||||
|
||||
PSF param_sfo;
|
||||
std::string mount_point;
|
||||
|
||||
int max_blocks{};
|
||||
bool exists{};
|
||||
bool mounted{};
|
||||
bool read_only{};
|
||||
|
||||
public:
|
||||
// Location of all save data for a title
|
||||
static std::filesystem::path MakeTitleSavePath(OrbisUserServiceUserId user_id,
|
||||
std::string_view game_serial);
|
||||
|
||||
// Location of a specific save data directory
|
||||
static std::filesystem::path MakeDirSavePath(OrbisUserServiceUserId user_id,
|
||||
std::string_view game_serial,
|
||||
std::string_view dir_name);
|
||||
|
||||
static int GetMaxBlocks(const std::filesystem::path& save_path);
|
||||
|
||||
// Get param.sfo path from a dir_path generated by MakeDirSavePath
|
||||
static std::filesystem::path GetParamSFOPath(const std::filesystem::path& dir_path);
|
||||
|
||||
static void SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name, std::string game_serial);
|
||||
|
||||
explicit SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::string game_serial,
|
||||
std::string_view dir_name, int max_blocks = 0);
|
||||
|
||||
~SaveInstance();
|
||||
|
||||
SaveInstance(const SaveInstance& other) = delete;
|
||||
SaveInstance(SaveInstance&& other) noexcept;
|
||||
|
||||
SaveInstance& operator=(const SaveInstance& other) = delete;
|
||||
SaveInstance& operator=(SaveInstance&& other) noexcept;
|
||||
|
||||
void SetupAndMount(bool read_only = false, bool copy_icon = false, bool ignore_corrupt = false);
|
||||
|
||||
void Umount();
|
||||
|
||||
[[nodiscard]] std::filesystem::path GetIconPath() const noexcept {
|
||||
return save_path / "sce_sys" / "icon0.png";
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Exists() const noexcept {
|
||||
return exists;
|
||||
}
|
||||
|
||||
[[nodiscard]] OrbisUserServiceUserId GetUserId() const noexcept {
|
||||
return user_id;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string_view GetTitleId() const noexcept {
|
||||
return game_serial;
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::string& GetDirName() const noexcept {
|
||||
return dir_name;
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::filesystem::path& GetSavePath() const noexcept {
|
||||
return save_path;
|
||||
}
|
||||
|
||||
[[nodiscard]] const PSF& GetParamSFO() const noexcept {
|
||||
return param_sfo;
|
||||
}
|
||||
|
||||
[[nodiscard]] PSF& GetParamSFO() noexcept {
|
||||
return param_sfo;
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::string& GetMountPoint() const noexcept {
|
||||
return mount_point;
|
||||
}
|
||||
|
||||
[[nodiscard]] int GetMaxBlocks() const noexcept {
|
||||
return max_blocks;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Mounted() const noexcept {
|
||||
return mounted;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsReadOnly() const noexcept {
|
||||
return read_only;
|
||||
}
|
||||
|
||||
void CreateFiles();
|
||||
};
|
||||
|
||||
} // namespace Libraries::SaveData
|
287
src/core/libraries/save_data/save_memory.cpp
Normal file
287
src/core/libraries/save_data/save_memory.cpp
Normal file
@ -0,0 +1,287 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "save_memory.h"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <core/libraries/system/msgdialog_ui.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "save_instance.h"
|
||||
|
||||
using Common::FS::IOFile;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save
|
||||
constexpr std::string_view DirnameSaveDataMemory = "sce_sdmemory";
|
||||
constexpr std::string_view FilenameSaveDataMemory = "memory.dat";
|
||||
|
||||
namespace Libraries::SaveData::SaveMemory {
|
||||
|
||||
static Core::FileSys::MntPoints* g_mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
|
||||
static OrbisUserServiceUserId g_user_id{};
|
||||
static std::string g_game_serial{};
|
||||
static std::filesystem::path g_save_path{};
|
||||
static std::filesystem::path g_param_sfo_path{};
|
||||
static PSF g_param_sfo;
|
||||
|
||||
static bool g_save_memory_initialized = false;
|
||||
static std::mutex g_saving_memory_mutex;
|
||||
static std::vector<u8> g_save_memory;
|
||||
|
||||
static std::filesystem::path g_icon_path;
|
||||
static std::vector<u8> g_icon_memory;
|
||||
|
||||
static std::condition_variable g_trigger_save_memory;
|
||||
static std::atomic_bool g_saving_memory = false;
|
||||
static std::atomic_bool g_save_event = false;
|
||||
static std::jthread g_save_memory_thread;
|
||||
|
||||
static std::atomic_bool g_memory_dirty = false;
|
||||
static std::atomic_bool g_param_dirty = false;
|
||||
static std::atomic_bool g_icon_dirty = false;
|
||||
|
||||
static void SaveFileSafe(void* buf, size_t count, const std::filesystem::path& path) {
|
||||
const auto& dir = path.parent_path();
|
||||
const auto& name = path.filename();
|
||||
const auto tmp_path = dir / (name.string() + ".tmp");
|
||||
|
||||
IOFile file(tmp_path, Common::FS::FileAccessMode::Write);
|
||||
file.WriteRaw<u8>(buf, count);
|
||||
file.Close();
|
||||
|
||||
fs::remove(path);
|
||||
fs::rename(tmp_path, path);
|
||||
}
|
||||
|
||||
[[noreturn]] void SaveThreadLoop() {
|
||||
Common::SetCurrentThreadName("SaveData_SaveDataMemoryThread");
|
||||
std::mutex mtx;
|
||||
while (true) {
|
||||
{
|
||||
std::unique_lock lk{mtx};
|
||||
g_trigger_save_memory.wait(lk);
|
||||
}
|
||||
// Save the memory
|
||||
g_saving_memory = true;
|
||||
std::scoped_lock lk{g_saving_memory_mutex};
|
||||
try {
|
||||
LOG_DEBUG(Lib_SaveData, "Saving save data memory {}", g_save_path.string());
|
||||
|
||||
if (g_memory_dirty) {
|
||||
g_memory_dirty = false;
|
||||
SaveFileSafe(g_save_memory.data(), g_save_memory.size(),
|
||||
g_save_path / FilenameSaveDataMemory);
|
||||
}
|
||||
if (g_param_dirty) {
|
||||
g_param_dirty = false;
|
||||
static std::vector<u8> buf;
|
||||
g_param_sfo.Encode(buf);
|
||||
SaveFileSafe(buf.data(), buf.size(), g_param_sfo_path);
|
||||
}
|
||||
if (g_icon_dirty) {
|
||||
g_icon_dirty = false;
|
||||
SaveFileSafe(g_icon_memory.data(), g_icon_memory.size(), g_icon_path);
|
||||
}
|
||||
|
||||
if (g_save_event) {
|
||||
Backup::PushBackupEvent(Backup::BackupRequest{
|
||||
.user_id = g_user_id,
|
||||
.title_id = g_game_serial,
|
||||
.dir_name = std::string{DirnameSaveDataMemory},
|
||||
.origin = Backup::OrbisSaveDataEventType::SAVE_DATA_MEMORY_SYNC,
|
||||
.save_path = g_save_path,
|
||||
});
|
||||
g_save_event = false;
|
||||
}
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
LOG_ERROR(Lib_SaveData, "Failed to save save data memory: {}", e.what());
|
||||
MsgDialog::ShowMsgDialog(MsgDialog::MsgDialogState{
|
||||
MsgDialog::MsgDialogState::UserState{
|
||||
.type = MsgDialog::ButtonType::OK,
|
||||
.msg = fmt::format("Failed to save save data memory.\nCode: <{}>\n{}",
|
||||
e.code().message(), e.what()),
|
||||
},
|
||||
});
|
||||
}
|
||||
g_saving_memory = false;
|
||||
}
|
||||
}
|
||||
|
||||
void SetDirectories(OrbisUserServiceUserId user_id, std::string _game_serial) {
|
||||
g_user_id = user_id;
|
||||
g_game_serial = std::move(_game_serial);
|
||||
g_save_path = SaveInstance::MakeDirSavePath(user_id, g_game_serial, DirnameSaveDataMemory);
|
||||
g_param_sfo_path = SaveInstance::GetParamSFOPath(g_save_path);
|
||||
g_param_sfo = PSF();
|
||||
g_icon_path = g_save_path / sce_sys / "icon0.png";
|
||||
}
|
||||
|
||||
const std::filesystem::path& GetSavePath() {
|
||||
return g_save_path;
|
||||
}
|
||||
|
||||
size_t CreateSaveMemory(size_t memory_size) {
|
||||
size_t existed_size = 0;
|
||||
|
||||
static std::once_flag init_save_thread_flag;
|
||||
std::call_once(init_save_thread_flag,
|
||||
[] { g_save_memory_thread = std::jthread{SaveThreadLoop}; });
|
||||
|
||||
g_save_memory.resize(memory_size);
|
||||
SaveInstance::SetupDefaultParamSFO(g_param_sfo, std::string{DirnameSaveDataMemory},
|
||||
g_game_serial);
|
||||
|
||||
g_save_memory_initialized = true;
|
||||
|
||||
if (!fs::exists(g_param_sfo_path)) {
|
||||
// Create save memory
|
||||
fs::create_directories(g_save_path / sce_sys);
|
||||
|
||||
IOFile memory_file{g_save_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Write};
|
||||
bool ok = memory_file.SetSize(memory_size);
|
||||
if (!ok) {
|
||||
LOG_ERROR(Lib_SaveData, "Failed to set memory size");
|
||||
throw std::filesystem::filesystem_error(
|
||||
"Failed to set save memory size", g_save_path / FilenameSaveDataMemory,
|
||||
std::make_error_code(std::errc::no_space_on_device));
|
||||
}
|
||||
memory_file.Close();
|
||||
} else {
|
||||
// Load save memory
|
||||
|
||||
bool ok = g_param_sfo.Open(g_param_sfo_path);
|
||||
if (!ok) {
|
||||
LOG_ERROR(Lib_SaveData, "Failed to open SFO at {}", g_param_sfo_path.string());
|
||||
throw std::filesystem::filesystem_error(
|
||||
"failed to open SFO", g_param_sfo_path,
|
||||
std::make_error_code(std::errc::illegal_byte_sequence));
|
||||
}
|
||||
|
||||
IOFile memory_file{g_save_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Read};
|
||||
if (!memory_file.IsOpen()) {
|
||||
LOG_ERROR(Lib_SaveData, "Failed to open save memory");
|
||||
throw std::filesystem::filesystem_error(
|
||||
"failed to open save memory", g_save_path / FilenameSaveDataMemory,
|
||||
std::make_error_code(std::errc::permission_denied));
|
||||
}
|
||||
size_t save_size = memory_file.GetSize();
|
||||
existed_size = save_size;
|
||||
memory_file.Seek(0);
|
||||
memory_file.ReadRaw<u8>(g_save_memory.data(), std::min(save_size, memory_size));
|
||||
memory_file.Close();
|
||||
}
|
||||
|
||||
return existed_size;
|
||||
}
|
||||
|
||||
void SetIcon(void* buf, size_t buf_size) {
|
||||
if (buf == nullptr) {
|
||||
const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png");
|
||||
if (fs::exists(src_icon)) {
|
||||
fs::copy_file(src_icon, g_icon_path);
|
||||
}
|
||||
IOFile file(g_icon_path, Common::FS::FileAccessMode::Read);
|
||||
size_t size = file.GetSize();
|
||||
file.Seek(0);
|
||||
g_icon_memory.resize(size);
|
||||
file.ReadRaw<u8>(g_icon_memory.data(), size);
|
||||
file.Close();
|
||||
} else {
|
||||
g_icon_memory.resize(buf_size);
|
||||
std::memcpy(g_icon_memory.data(), buf, buf_size);
|
||||
IOFile file(g_icon_path, Common::FS::FileAccessMode::Append);
|
||||
file.Seek(0);
|
||||
file.WriteRaw<u8>(g_icon_memory.data(), buf_size);
|
||||
file.Close();
|
||||
}
|
||||
}
|
||||
|
||||
void WriteIcon(void* buf, size_t buf_size) {
|
||||
if (buf_size != g_icon_memory.size()) {
|
||||
g_icon_memory.resize(buf_size);
|
||||
}
|
||||
std::memcpy(g_icon_memory.data(), buf, buf_size);
|
||||
g_icon_dirty = true;
|
||||
}
|
||||
|
||||
bool IsSaveMemoryInitialized() {
|
||||
return g_save_memory_initialized;
|
||||
}
|
||||
|
||||
PSF& GetParamSFO() {
|
||||
return g_param_sfo;
|
||||
}
|
||||
|
||||
std::span<u8> GetIcon() {
|
||||
return {g_icon_memory};
|
||||
}
|
||||
|
||||
void SaveSFO(bool sync) {
|
||||
if (!sync) {
|
||||
g_param_dirty = true;
|
||||
return;
|
||||
}
|
||||
const bool ok = g_param_sfo.Encode(g_param_sfo_path);
|
||||
if (!ok) {
|
||||
LOG_ERROR(Lib_SaveData, "Failed to encode param.sfo");
|
||||
throw std::filesystem::filesystem_error("Failed to write param.sfo", g_param_sfo_path,
|
||||
std::make_error_code(std::errc::permission_denied));
|
||||
}
|
||||
}
|
||||
bool IsSaving() {
|
||||
return g_saving_memory;
|
||||
}
|
||||
|
||||
bool TriggerSaveWithoutEvent() {
|
||||
if (g_saving_memory) {
|
||||
return false;
|
||||
}
|
||||
g_trigger_save_memory.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TriggerSave() {
|
||||
if (g_saving_memory) {
|
||||
return false;
|
||||
}
|
||||
g_save_event = true;
|
||||
g_trigger_save_memory.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReadMemory(void* buf, size_t buf_size, int64_t offset) {
|
||||
std::scoped_lock lk{g_saving_memory_mutex};
|
||||
if (offset > g_save_memory.size()) {
|
||||
UNREACHABLE_MSG("ReadMemory out of bounds");
|
||||
}
|
||||
if (offset + buf_size > g_save_memory.size()) {
|
||||
UNREACHABLE_MSG("ReadMemory out of bounds");
|
||||
}
|
||||
std::memcpy(buf, g_save_memory.data() + offset, buf_size);
|
||||
}
|
||||
|
||||
void WriteMemory(void* buf, size_t buf_size, int64_t offset) {
|
||||
std::scoped_lock lk{g_saving_memory_mutex};
|
||||
if (offset > g_save_memory.size()) {
|
||||
UNREACHABLE_MSG("WriteMemory out of bounds");
|
||||
}
|
||||
if (offset + buf_size > g_save_memory.size()) {
|
||||
UNREACHABLE_MSG("WriteMemory out of bounds");
|
||||
}
|
||||
std::memcpy(g_save_memory.data() + offset, buf, buf_size);
|
||||
g_memory_dirty = true;
|
||||
}
|
||||
|
||||
} // namespace Libraries::SaveData::SaveMemory
|
49
src/core/libraries/save_data/save_memory.h
Normal file
49
src/core/libraries/save_data/save_memory.h
Normal file
@ -0,0 +1,49 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include "save_backup.h"
|
||||
|
||||
class PSF;
|
||||
|
||||
namespace Libraries::SaveData {
|
||||
using OrbisUserServiceUserId = s32;
|
||||
}
|
||||
|
||||
namespace Libraries::SaveData::SaveMemory {
|
||||
|
||||
void SetDirectories(OrbisUserServiceUserId user_id, std::string game_serial);
|
||||
|
||||
[[nodiscard]] const std::filesystem::path& GetSavePath();
|
||||
|
||||
// returns the size of the existed save memory
|
||||
size_t CreateSaveMemory(size_t memory_size);
|
||||
|
||||
// Initialize the icon. Set buf to null to read the standard icon.
|
||||
void SetIcon(void* buf, size_t buf_size);
|
||||
|
||||
// Update the icon
|
||||
void WriteIcon(void* buf, size_t buf_size);
|
||||
|
||||
[[nodiscard]] bool IsSaveMemoryInitialized();
|
||||
|
||||
[[nodiscard]] PSF& GetParamSFO();
|
||||
|
||||
[[nodiscard]] std::span<u8> GetIcon();
|
||||
|
||||
// Save now or wait for the background thread to save
|
||||
void SaveSFO(bool sync = false);
|
||||
|
||||
[[nodiscard]] bool IsSaving();
|
||||
|
||||
bool TriggerSaveWithoutEvent();
|
||||
|
||||
bool TriggerSave();
|
||||
|
||||
void ReadMemory(void* buf, size_t buf_size, int64_t offset);
|
||||
|
||||
void WriteMemory(void* buf, size_t buf_size, int64_t offset);
|
||||
|
||||
} // namespace Libraries::SaveData::SaveMemory
|
File diff suppressed because it is too large
Load Diff
@ -3,259 +3,81 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/cstring.h"
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
class PSF;
|
||||
|
||||
namespace Libraries::SaveData {
|
||||
|
||||
constexpr int ORBIS_SAVE_DATA_DIRNAME_DATA_MAXSIZE =
|
||||
32; // Maximum size for a save data directory name
|
||||
constexpr int ORBIS_SAVE_DATA_MOUNT_POINT_DATA_MAXSIZE = 16; // Maximum size for a mount point name
|
||||
constexpr size_t OrbisSaveDataTitleMaxsize = 128; // Maximum title name size
|
||||
constexpr size_t OrbisSaveDataSubtitleMaxsize = 128; // Maximum subtitle name size
|
||||
constexpr size_t OrbisSaveDataDetailMaxsize = 1024; // Maximum detail name size
|
||||
|
||||
enum class Error : u32;
|
||||
enum class OrbisSaveDataParamType : u32;
|
||||
|
||||
using OrbisUserServiceUserId = s32;
|
||||
|
||||
// Maximum size for a title ID (4 uppercase letters + 5 digits)
|
||||
constexpr int OrbisSaveDataTitleIdDataSize = 10;
|
||||
// Maximum save directory name size
|
||||
constexpr int OrbisSaveDataDirnameDataMaxsize = 32;
|
||||
|
||||
struct OrbisSaveDataTitleId {
|
||||
Common::CString<OrbisSaveDataTitleIdDataSize> data;
|
||||
std::array<char, 6> _pad;
|
||||
};
|
||||
|
||||
struct OrbisSaveDataDirName {
|
||||
char data[ORBIS_SAVE_DATA_DIRNAME_DATA_MAXSIZE];
|
||||
Common::CString<OrbisSaveDataDirnameDataMaxsize> data;
|
||||
};
|
||||
|
||||
struct OrbisSaveDataMount2 {
|
||||
s32 user_id;
|
||||
s32 unk1;
|
||||
const OrbisSaveDataDirName* dir_name;
|
||||
u64 blocks;
|
||||
u32 mount_mode;
|
||||
u8 reserved[32];
|
||||
s32 unk2;
|
||||
};
|
||||
|
||||
struct OrbisSaveDataMountPoint {
|
||||
char data[ORBIS_SAVE_DATA_MOUNT_POINT_DATA_MAXSIZE];
|
||||
};
|
||||
|
||||
struct OrbisSaveDataMountResult {
|
||||
OrbisSaveDataMountPoint mount_point;
|
||||
u64 required_blocks;
|
||||
u32 unused;
|
||||
u32 mount_status;
|
||||
u8 reserved[28];
|
||||
s32 unk1;
|
||||
};
|
||||
|
||||
constexpr int ORBIS_SAVE_DATA_TITLE_ID_DATA_SIZE = 10;
|
||||
struct OrbisSaveDataTitleId {
|
||||
char data[ORBIS_SAVE_DATA_TITLE_ID_DATA_SIZE];
|
||||
char padding[6];
|
||||
};
|
||||
|
||||
constexpr int ORBIS_SAVE_DATA_FINGERPRINT_DATA_SIZE = 65;
|
||||
struct OrbisSaveDataFingerprint {
|
||||
char data[ORBIS_SAVE_DATA_FINGERPRINT_DATA_SIZE];
|
||||
char padding[15];
|
||||
};
|
||||
|
||||
struct OrbisSaveDataMount {
|
||||
s32 user_id;
|
||||
s32 pad;
|
||||
const OrbisSaveDataTitleId* titleId;
|
||||
const OrbisSaveDataDirName* dir_name;
|
||||
const OrbisSaveDataFingerprint* fingerprint;
|
||||
u64 blocks;
|
||||
u32 mount_mode;
|
||||
u8 reserved[32];
|
||||
};
|
||||
|
||||
typedef u32 OrbisSaveDataParamType;
|
||||
|
||||
constexpr int ORBIS_SAVE_DATA_TITLE_MAXSIZE = 128;
|
||||
constexpr int ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE = 128;
|
||||
constexpr int ORBIS_SAVE_DATA_DETAIL_MAXSIZE = 1024;
|
||||
struct OrbisSaveDataParam {
|
||||
char title[ORBIS_SAVE_DATA_TITLE_MAXSIZE];
|
||||
char subTitle[ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE];
|
||||
char detail[ORBIS_SAVE_DATA_DETAIL_MAXSIZE];
|
||||
Common::CString<OrbisSaveDataTitleMaxsize> title;
|
||||
Common::CString<OrbisSaveDataSubtitleMaxsize> subTitle;
|
||||
Common::CString<OrbisSaveDataDetailMaxsize> detail;
|
||||
u32 userParam;
|
||||
int : 32;
|
||||
time_t mtime;
|
||||
u8 reserved[32];
|
||||
std::array<u8, 32> _reserved;
|
||||
|
||||
void FromSFO(const PSF& sfo);
|
||||
|
||||
void ToSFO(PSF& sfo) const;
|
||||
};
|
||||
|
||||
struct OrbisSaveDataIcon {
|
||||
void* buf;
|
||||
size_t bufSize;
|
||||
size_t dataSize;
|
||||
u8 reserved[32];
|
||||
};
|
||||
|
||||
typedef u32 OrbisSaveDataSaveDataMemoryOption;
|
||||
#define ORBIS_SAVE_DATA_MEMORY_OPTION_NONE (0x00000000)
|
||||
#define ORBIS_SAVE_DATA_MEMORY_OPTION_SET_PARAM (0x00000001 << 0)
|
||||
#define ORBIS_SAVE_DATA_MEMORY_OPTION_DOUBLE_BUFFER (0x00000001 << 1)
|
||||
|
||||
struct OrbisSaveDataMemorySetup2 {
|
||||
OrbisSaveDataSaveDataMemoryOption option;
|
||||
s32 userId;
|
||||
size_t memorySize;
|
||||
size_t iconMemorySize;
|
||||
const OrbisSaveDataParam* initParam;
|
||||
const OrbisSaveDataIcon* initIcon;
|
||||
u32 slotId;
|
||||
u8 reserved[20];
|
||||
};
|
||||
|
||||
struct OrbisSaveDataMemorySetupResult {
|
||||
size_t existedMemorySize;
|
||||
u8 reserved[16];
|
||||
};
|
||||
|
||||
typedef u32 OrbisSaveDataEventType;
|
||||
#define SCE_SAVE_DATA_EVENT_TYPE_INVALID (0)
|
||||
#define SCE_SAVE_DATA_EVENT_TYPE_UMOUNT_BACKUP_END (1)
|
||||
#define SCE_SAVE_DATA_EVENT_TYPE_BACKUP_END (2)
|
||||
#define SCE_SAVE_DATA_EVENT_TYPE_SAVE_DATA_MEMORY_SYNC_END (3)
|
||||
|
||||
struct OrbisSaveDataEvent {
|
||||
OrbisSaveDataEventType type;
|
||||
s32 errorCode;
|
||||
s32 userId;
|
||||
u8 padding[4];
|
||||
OrbisSaveDataTitleId titleId;
|
||||
OrbisSaveDataDirName dirName;
|
||||
u8 reserved[40];
|
||||
};
|
||||
|
||||
struct OrbisSaveDataMemoryData {
|
||||
void* buf;
|
||||
size_t bufSize;
|
||||
off_t offset;
|
||||
u8 reserved[40];
|
||||
};
|
||||
|
||||
struct OrbisSaveDataMemoryGet2 {
|
||||
s32 userId;
|
||||
u8 padding[4];
|
||||
OrbisSaveDataMemoryData* data;
|
||||
OrbisSaveDataParam* param;
|
||||
OrbisSaveDataIcon* icon;
|
||||
u32 slotId;
|
||||
u8 reserved[28];
|
||||
};
|
||||
|
||||
struct OrbisSaveDataMemorySet2 {
|
||||
s32 userId;
|
||||
u8 padding[4];
|
||||
const OrbisSaveDataMemoryData* data;
|
||||
const OrbisSaveDataParam* param;
|
||||
const OrbisSaveDataIcon* icon;
|
||||
u32 dataNum;
|
||||
u8 slotId;
|
||||
u8 reserved[24];
|
||||
};
|
||||
|
||||
struct OrbisSaveDataCheckBackupData {
|
||||
s32 userId;
|
||||
int : 32;
|
||||
const OrbisSaveDataTitleId* titleId;
|
||||
const OrbisSaveDataDirName* dirName;
|
||||
OrbisSaveDataParam* param;
|
||||
OrbisSaveDataIcon* icon;
|
||||
u8 reserved[32];
|
||||
};
|
||||
|
||||
struct OrbisSaveDataMountInfo {
|
||||
u64 blocks;
|
||||
u64 freeBlocks;
|
||||
u8 reserved[32];
|
||||
};
|
||||
|
||||
#define ORBIS_SAVE_DATA_BLOCK_SIZE (32768)
|
||||
#define ORBIS_SAVE_DATA_BLOCKS_MIN2 (96)
|
||||
#define ORBIS_SAVE_DATA_BLOCKS_MAX (32768)
|
||||
|
||||
// savedataMount2 mountModes (ORed values)
|
||||
constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_RDONLY = 1;
|
||||
constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_RDWR = 2;
|
||||
constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_CREATE = 4;
|
||||
constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF = 8;
|
||||
constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON = 16;
|
||||
constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_CREATE2 = 32;
|
||||
typedef struct _OrbisSaveDataEventParam OrbisSaveDataEventParam;
|
||||
|
||||
typedef u32 OrbisSaveDataSortKey;
|
||||
#define ORBIS_SAVE_DATA_SORT_KEY_DIRNAME (0)
|
||||
#define ORBIS_SAVE_DATA_SORT_KEY_USER_PARAM (1)
|
||||
#define ORBIS_SAVE_DATA_SORT_KEY_BLOCKS (2)
|
||||
#define ORBIS_SAVE_DATA_SORT_KEY_MTIME (3)
|
||||
#define ORBIS_SAVE_DATA_SORT_KEY_FREE_BLOCKS (5)
|
||||
|
||||
typedef u32 OrbisSaveDataSortOrder;
|
||||
#define ORBIS_SAVE_DATA_SORT_ORDER_ASCENT (0)
|
||||
#define ORBIS_SAVE_DATA_SORT_ORDER_DESCENT (1)
|
||||
|
||||
struct OrbisSaveDataDirNameSearchCond {
|
||||
s32 userId;
|
||||
int : 32;
|
||||
const OrbisSaveDataTitleId* titleId;
|
||||
const OrbisSaveDataDirName* dirName;
|
||||
OrbisSaveDataSortKey key;
|
||||
OrbisSaveDataSortOrder order;
|
||||
u8 reserved[32];
|
||||
};
|
||||
|
||||
struct OrbisSaveDataSearchInfo {
|
||||
u64 blocks;
|
||||
u64 freeBlocks;
|
||||
u8 reserved[32];
|
||||
};
|
||||
|
||||
struct OrbisSaveDataDirNameSearchResult {
|
||||
u32 hitNum;
|
||||
int : 32;
|
||||
OrbisSaveDataDirName* dirNames;
|
||||
u32 dirNamesNum;
|
||||
u32 setNum;
|
||||
OrbisSaveDataParam* params;
|
||||
OrbisSaveDataSearchInfo* infos;
|
||||
u8 reserved[12];
|
||||
int : 32;
|
||||
};
|
||||
|
||||
struct OrbisSaveDataDelete {
|
||||
s32 userId;
|
||||
int : 32;
|
||||
const OrbisSaveDataTitleId* titleId;
|
||||
const OrbisSaveDataDirName* dirName;
|
||||
u32 unused;
|
||||
u8 reserved[32];
|
||||
int : 32;
|
||||
};
|
||||
|
||||
typedef u32 OrbisSaveDataMemorySyncOption;
|
||||
|
||||
#define SCE_SAVE_DATA_MEMORY_SYNC_OPTION_NONE (0x00000000)
|
||||
#define SCE_SAVE_DATA_MEMORY_SYNC_OPTION_BLOCKING (0x00000001 << 0)
|
||||
|
||||
struct OrbisSaveDataMemorySync {
|
||||
s32 userId;
|
||||
u32 slotId;
|
||||
OrbisSaveDataMemorySyncOption option;
|
||||
u8 reserved[28];
|
||||
};
|
||||
|
||||
constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_ALL = 0;
|
||||
constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_TITLE = 1;
|
||||
constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_SUB_TITLE = 2;
|
||||
constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_DETAIL = 3;
|
||||
constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_USER_PARAM = 4;
|
||||
constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_MTIME = 5;
|
||||
struct OrbisSaveDataBackup;
|
||||
struct OrbisSaveDataCheckBackupData;
|
||||
struct OrbisSaveDataDelete;
|
||||
struct OrbisSaveDataDirNameSearchCond;
|
||||
struct OrbisSaveDataDirNameSearchResult;
|
||||
struct OrbisSaveDataEvent;
|
||||
struct OrbisSaveDataEventParam;
|
||||
struct OrbisSaveDataIcon;
|
||||
struct OrbisSaveDataMemoryGet2;
|
||||
struct OrbisSaveDataMemorySet2;
|
||||
struct OrbisSaveDataMemorySetup2;
|
||||
struct OrbisSaveDataMemorySetupResult;
|
||||
struct OrbisSaveDataMemorySync;
|
||||
struct OrbisSaveDataMount2;
|
||||
struct OrbisSaveDataMount;
|
||||
struct OrbisSaveDataMountInfo;
|
||||
struct OrbisSaveDataMountPoint;
|
||||
struct OrbisSaveDataMountResult;
|
||||
struct OrbisSaveDataRestoreBackupData;
|
||||
|
||||
int PS4_SYSV_ABI sceSaveDataAbort();
|
||||
int PS4_SYSV_ABI sceSaveDataBackup();
|
||||
Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup);
|
||||
int PS4_SYSV_ABI sceSaveDataBindPsnAccount();
|
||||
int PS4_SYSV_ABI sceSaveDataBindPsnAccountForSystemBackup();
|
||||
int PS4_SYSV_ABI sceSaveDataChangeDatabase();
|
||||
int PS4_SYSV_ABI sceSaveDataChangeInternal();
|
||||
int PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check);
|
||||
Error PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check);
|
||||
int PS4_SYSV_ABI sceSaveDataCheckBackupDataForCdlg();
|
||||
int PS4_SYSV_ABI sceSaveDataCheckBackupDataInternal();
|
||||
int PS4_SYSV_ABI sceSaveDataCheckCloudData();
|
||||
@ -263,7 +85,7 @@ int PS4_SYSV_ABI sceSaveDataCheckIpmiIfSize();
|
||||
int PS4_SYSV_ABI sceSaveDataCheckSaveDataBroken();
|
||||
int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersion();
|
||||
int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersionLatest();
|
||||
int PS4_SYSV_ABI sceSaveDataClearProgress();
|
||||
Error PS4_SYSV_ABI sceSaveDataClearProgress();
|
||||
int PS4_SYSV_ABI sceSaveDataCopy5();
|
||||
int PS4_SYSV_ABI sceSaveDataCreateUploadData();
|
||||
int PS4_SYSV_ABI sceSaveDataDebug();
|
||||
@ -273,12 +95,12 @@ int PS4_SYSV_ABI sceSaveDataDebugCreateSaveDataRoot();
|
||||
int PS4_SYSV_ABI sceSaveDataDebugGetThreadId();
|
||||
int PS4_SYSV_ABI sceSaveDataDebugRemoveSaveDataRoot();
|
||||
int PS4_SYSV_ABI sceSaveDataDebugTarget();
|
||||
int PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del);
|
||||
Error PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del);
|
||||
int PS4_SYSV_ABI sceSaveDataDelete5();
|
||||
int PS4_SYSV_ABI sceSaveDataDeleteAllUser();
|
||||
int PS4_SYSV_ABI sceSaveDataDeleteCloudData();
|
||||
int PS4_SYSV_ABI sceSaveDataDeleteUser();
|
||||
int PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond,
|
||||
Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond,
|
||||
OrbisSaveDataDirNameSearchResult* result);
|
||||
int PS4_SYSV_ABI sceSaveDataDirNameSearchInternal();
|
||||
int PS4_SYSV_ABI sceSaveDataDownload();
|
||||
@ -292,36 +114,36 @@ int PS4_SYSV_ABI sceSaveDataGetClientThreadPriority();
|
||||
int PS4_SYSV_ABI sceSaveDataGetCloudQuotaInfo();
|
||||
int PS4_SYSV_ABI sceSaveDataGetDataBaseFilePath();
|
||||
int PS4_SYSV_ABI sceSaveDataGetEventInfo();
|
||||
int PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam* eventParam,
|
||||
Error PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam* eventParam,
|
||||
OrbisSaveDataEvent* event);
|
||||
int PS4_SYSV_ABI sceSaveDataGetFormat();
|
||||
int PS4_SYSV_ABI sceSaveDataGetMountedSaveDataCount();
|
||||
int PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint,
|
||||
Error PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint,
|
||||
OrbisSaveDataMountInfo* info);
|
||||
int PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint,
|
||||
const OrbisSaveDataParamType paramType, void* paramBuf,
|
||||
const size_t paramBufSize, size_t* gotSize);
|
||||
int PS4_SYSV_ABI sceSaveDataGetProgress();
|
||||
Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint,
|
||||
OrbisSaveDataParamType paramType, void* paramBuf,
|
||||
size_t paramBufSize, size_t* gotSize);
|
||||
Error PS4_SYSV_ABI sceSaveDataGetProgress(float* progress);
|
||||
int PS4_SYSV_ABI sceSaveDataGetSaveDataCount();
|
||||
int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const u32 userId, void* buf, const size_t bufSize,
|
||||
const int64_t offset);
|
||||
int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam);
|
||||
Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(OrbisUserServiceUserId userId, void* buf,
|
||||
size_t bufSize, int64_t offset);
|
||||
Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam);
|
||||
int PS4_SYSV_ABI sceSaveDataGetSaveDataRootDir();
|
||||
int PS4_SYSV_ABI sceSaveDataGetSaveDataRootPath();
|
||||
int PS4_SYSV_ABI sceSaveDataGetSaveDataRootUsbPath();
|
||||
int PS4_SYSV_ABI sceSaveDataGetSavePoint();
|
||||
int PS4_SYSV_ABI sceSaveDataGetUpdatedDataCount();
|
||||
int PS4_SYSV_ABI sceSaveDataInitialize();
|
||||
int PS4_SYSV_ABI sceSaveDataInitialize2();
|
||||
int PS4_SYSV_ABI sceSaveDataInitialize3();
|
||||
Error PS4_SYSV_ABI sceSaveDataInitialize(void*);
|
||||
Error PS4_SYSV_ABI sceSaveDataInitialize2(void*);
|
||||
Error PS4_SYSV_ABI sceSaveDataInitialize3(void*);
|
||||
int PS4_SYSV_ABI sceSaveDataInitializeForCdlg();
|
||||
int PS4_SYSV_ABI sceSaveDataIsDeletingUsbDb();
|
||||
int PS4_SYSV_ABI sceSaveDataIsMounted();
|
||||
int PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint,
|
||||
Error PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint,
|
||||
OrbisSaveDataIcon* icon);
|
||||
int PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount,
|
||||
Error PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount,
|
||||
OrbisSaveDataMountResult* mount_result);
|
||||
s32 PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount,
|
||||
Error PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount,
|
||||
OrbisSaveDataMountResult* mount_result);
|
||||
int PS4_SYSV_ABI sceSaveDataMount5();
|
||||
int PS4_SYSV_ABI sceSaveDataMountInternal();
|
||||
@ -329,33 +151,33 @@ int PS4_SYSV_ABI sceSaveDataMountSys();
|
||||
int PS4_SYSV_ABI sceSaveDataPromote5();
|
||||
int PS4_SYSV_ABI sceSaveDataRebuildDatabase();
|
||||
int PS4_SYSV_ABI sceSaveDataRegisterEventCallback();
|
||||
int PS4_SYSV_ABI sceSaveDataRestoreBackupData();
|
||||
Error PS4_SYSV_ABI sceSaveDataRestoreBackupData(const OrbisSaveDataRestoreBackupData* restore);
|
||||
int PS4_SYSV_ABI sceSaveDataRestoreBackupDataForCdlg();
|
||||
int PS4_SYSV_ABI sceSaveDataRestoreLoadSaveDataMemory();
|
||||
int PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint,
|
||||
Error PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint,
|
||||
const OrbisSaveDataIcon* icon);
|
||||
int PS4_SYSV_ABI sceSaveDataSetAutoUploadSetting();
|
||||
int PS4_SYSV_ABI sceSaveDataSetEventInfo();
|
||||
int PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint,
|
||||
Error PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint,
|
||||
OrbisSaveDataParamType paramType, const void* paramBuf,
|
||||
size_t paramBufSize);
|
||||
int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser();
|
||||
int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(const u32 userId, const void* buf,
|
||||
const size_t bufSize, const int64_t offset);
|
||||
int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam);
|
||||
int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(u32 userId, size_t memorySize,
|
||||
OrbisSaveDataParam* param);
|
||||
int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam,
|
||||
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 sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam,
|
||||
OrbisSaveDataMemorySetupResult* result);
|
||||
int PS4_SYSV_ABI sceSaveDataShutdownStart();
|
||||
int PS4_SYSV_ABI sceSaveDataSupportedFakeBrokenStatus();
|
||||
int PS4_SYSV_ABI sceSaveDataSyncCloudList();
|
||||
int PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam);
|
||||
int PS4_SYSV_ABI sceSaveDataTerminate();
|
||||
Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam);
|
||||
Error PS4_SYSV_ABI sceSaveDataTerminate();
|
||||
int PS4_SYSV_ABI sceSaveDataTransferringMount();
|
||||
int PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint);
|
||||
Error PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint);
|
||||
int PS4_SYSV_ABI sceSaveDataUmountSys();
|
||||
int PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint);
|
||||
Error PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint);
|
||||
int PS4_SYSV_ABI sceSaveDataUnregisterEventCallback();
|
||||
int PS4_SYSV_ABI sceSaveDataUpload();
|
||||
int PS4_SYSV_ABI Func_02E4C4D201716422();
|
||||
|
@ -39,11 +39,6 @@ Error PS4_SYSV_ABI sceMsgDialogGetResult(DialogResult* result) {
|
||||
if (result == nullptr) {
|
||||
return Error::ARG_NULL;
|
||||
}
|
||||
for (const auto v : result->reserved) {
|
||||
if (v != 0) {
|
||||
return Error::PARAM_INVALID;
|
||||
}
|
||||
}
|
||||
*result = g_result;
|
||||
return Error::OK;
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <imgui.h>
|
||||
#include "common/assert.h"
|
||||
#include "imgui/imgui_std.h"
|
||||
@ -31,18 +33,6 @@ struct {
|
||||
};
|
||||
static_assert(std::size(user_button_texts) == static_cast<int>(ButtonType::TWO_BUTTONS) + 1);
|
||||
|
||||
static void DrawCenteredText(const char* text) {
|
||||
const auto ws = GetWindowSize();
|
||||
const auto text_size = CalcTextSize(text, nullptr, false, ws.x - 40.0f);
|
||||
PushTextWrapPos(ws.x - 30.0f);
|
||||
SetCursorPos({
|
||||
(ws.x - text_size.x) / 2.0f,
|
||||
(ws.y - text_size.y) / 2.0f - 50.0f,
|
||||
});
|
||||
Text("%s", text);
|
||||
PopTextWrapPos();
|
||||
}
|
||||
|
||||
MsgDialogState::MsgDialogState(const OrbisParam& param) {
|
||||
this->mode = param.mode;
|
||||
switch (mode) {
|
||||
@ -81,11 +71,29 @@ MsgDialogState::MsgDialogState(const OrbisParam& param) {
|
||||
}
|
||||
}
|
||||
|
||||
MsgDialogState::MsgDialogState(UserState mode) {
|
||||
this->mode = MsgDialogMode::USER_MSG;
|
||||
this->state = mode;
|
||||
}
|
||||
|
||||
MsgDialogState::MsgDialogState(ProgressState mode) {
|
||||
this->mode = MsgDialogMode::PROGRESS_BAR;
|
||||
this->state = mode;
|
||||
}
|
||||
|
||||
MsgDialogState::MsgDialogState(SystemState mode) {
|
||||
this->mode = MsgDialogMode::SYSTEM_MSG;
|
||||
this->state = mode;
|
||||
}
|
||||
|
||||
void MsgDialogUi::DrawUser() {
|
||||
const auto& [button_type, msg, btn_param1, btn_param2] =
|
||||
state->GetState<MsgDialogState::UserState>();
|
||||
const auto ws = GetWindowSize();
|
||||
DrawCenteredText(msg.c_str());
|
||||
if (!msg.empty()) {
|
||||
DrawCenteredText(&msg.front(), &msg.back() + 1,
|
||||
GetContentRegionAvail() - ImVec2{0.0f, 15.0f + BUTTON_SIZE.y});
|
||||
}
|
||||
ASSERT(button_type <= ButtonType::TWO_BUTTONS);
|
||||
auto [count, text1, text2] = user_button_texts[static_cast<u32>(button_type)];
|
||||
if (count == 0xFF) { // TWO_BUTTONS -> User defined message
|
||||
@ -115,7 +123,7 @@ void MsgDialogUi::DrawUser() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (first_render && !focus_first) {
|
||||
if ((first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) && !focus_first) {
|
||||
SetItemCurrentNavFocus();
|
||||
}
|
||||
PopID();
|
||||
@ -125,7 +133,7 @@ void MsgDialogUi::DrawUser() {
|
||||
if (Button(text1, BUTTON_SIZE)) {
|
||||
Finish(ButtonId::BUTTON1);
|
||||
}
|
||||
if (first_render && focus_first) {
|
||||
if ((first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) && focus_first) {
|
||||
SetItemCurrentNavFocus();
|
||||
}
|
||||
PopID();
|
||||
@ -249,11 +257,13 @@ void MsgDialogUi::Draw() {
|
||||
|
||||
CentralizeWindow();
|
||||
SetNextWindowSize(window_size);
|
||||
SetNextWindowFocus();
|
||||
SetNextWindowCollapsed(false);
|
||||
if (first_render || !io.NavActive) {
|
||||
SetNextWindowFocus();
|
||||
}
|
||||
KeepNavHighlight();
|
||||
// Hack to allow every dialog to have a unique window
|
||||
if (Begin("Message Dialog##MessageDialog", nullptr, ImGuiWindowFlags_NoSavedSettings)) {
|
||||
if (Begin("Message Dialog##MessageDialog", nullptr,
|
||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) {
|
||||
switch (state->GetMode()) {
|
||||
case MsgDialogMode::USER_MSG:
|
||||
DrawUser();
|
||||
@ -270,3 +280,15 @@ void MsgDialogUi::Draw() {
|
||||
|
||||
first_render = false;
|
||||
}
|
||||
|
||||
DialogResult Libraries::MsgDialog::ShowMsgDialog(MsgDialogState state, bool block) {
|
||||
DialogResult result{};
|
||||
Status status = Status::RUNNING;
|
||||
MsgDialogUi dialog(&state, &status, &result);
|
||||
if (block) {
|
||||
while (status == Status::RUNNING) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
#include "common/fixed_value.h"
|
||||
@ -129,6 +130,11 @@ private:
|
||||
|
||||
public:
|
||||
explicit MsgDialogState(const OrbisParam& param);
|
||||
|
||||
explicit MsgDialogState(UserState mode);
|
||||
explicit MsgDialogState(ProgressState mode);
|
||||
explicit MsgDialogState(SystemState mode);
|
||||
|
||||
MsgDialogState() = default;
|
||||
|
||||
[[nodiscard]] OrbisUserServiceUserId GetUserId() const {
|
||||
@ -165,13 +171,11 @@ public:
|
||||
|
||||
void Finish(ButtonId buttonId, CommonDialog::Result r = CommonDialog::Result::OK);
|
||||
|
||||
void SetProgressBarValue(u32 value, bool increment);
|
||||
|
||||
void Draw() override;
|
||||
|
||||
bool ShouldGrabGamepad() override {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Utility function to show a message dialog
|
||||
// !!! This function can block !!!
|
||||
DialogResult ShowMsgDialog(MsgDialogState state, bool block = true);
|
||||
|
||||
}; // namespace Libraries::MsgDialog
|
@ -1,84 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/system/savedatadialog.h"
|
||||
|
||||
namespace Libraries::SaveDataDialog {
|
||||
|
||||
int PS4_SYSV_ABI sceSaveDataDialogClose() {
|
||||
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSaveDataDialogGetResult() {
|
||||
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSaveDataDialogGetStatus() {
|
||||
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSaveDataDialogInitialize() {
|
||||
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay() {
|
||||
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSaveDataDialogOpen() {
|
||||
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSaveDataDialogProgressBarInc() {
|
||||
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSaveDataDialogProgressBarSetValue() {
|
||||
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSaveDataDialogTerminate() {
|
||||
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSaveDataDialogUpdateStatus() {
|
||||
LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called");
|
||||
return 3; // SCE_COMMON_DIALOG_STATUS_FINISHED
|
||||
}
|
||||
|
||||
void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("fH46Lag88XY", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
|
||||
sceSaveDataDialogClose);
|
||||
LIB_FUNCTION("yEiJ-qqr6Cg", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
|
||||
sceSaveDataDialogGetResult);
|
||||
LIB_FUNCTION("ERKzksauAJA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
|
||||
sceSaveDataDialogGetStatus);
|
||||
LIB_FUNCTION("s9e3+YpRnzw", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
|
||||
sceSaveDataDialogInitialize);
|
||||
LIB_FUNCTION("en7gNVnh878", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
|
||||
sceSaveDataDialogIsReadyToDisplay);
|
||||
LIB_FUNCTION("4tPhsP6FpDI", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
|
||||
sceSaveDataDialogOpen);
|
||||
LIB_FUNCTION("V-uEeFKARJU", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
|
||||
sceSaveDataDialogProgressBarInc);
|
||||
LIB_FUNCTION("hay1CfTmLyA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
|
||||
sceSaveDataDialogProgressBarSetValue);
|
||||
LIB_FUNCTION("YuH2FA7azqQ", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
|
||||
sceSaveDataDialogTerminate);
|
||||
LIB_FUNCTION("KK3Bdg1RWK0", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1,
|
||||
sceSaveDataDialogUpdateStatus);
|
||||
};
|
||||
|
||||
} // namespace Libraries::SaveDataDialog
|
@ -1,26 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::SaveDataDialog {
|
||||
|
||||
int PS4_SYSV_ABI sceSaveDataDialogClose();
|
||||
int PS4_SYSV_ABI sceSaveDataDialogGetResult();
|
||||
int PS4_SYSV_ABI sceSaveDataDialogGetStatus();
|
||||
int PS4_SYSV_ABI sceSaveDataDialogInitialize();
|
||||
int PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay();
|
||||
int PS4_SYSV_ABI sceSaveDataDialogOpen();
|
||||
int PS4_SYSV_ABI sceSaveDataDialogProgressBarInc();
|
||||
int PS4_SYSV_ABI sceSaveDataDialogProgressBarSetValue();
|
||||
int PS4_SYSV_ABI sceSaveDataDialogTerminate();
|
||||
int PS4_SYSV_ABI sceSaveDataDialogUpdateStatus();
|
||||
|
||||
void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::SaveDataDialog
|
@ -10,6 +10,7 @@
|
||||
#ifdef ENABLE_QT_GUI
|
||||
#include "common/memory_patcher.h"
|
||||
#endif
|
||||
#include "common/assert.h"
|
||||
#include "common/ntapi.h"
|
||||
#include "common/path_util.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
@ -99,8 +100,9 @@ void Emulator::Run(const std::filesystem::path& file) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(sce_sys_folder)) {
|
||||
if (entry.path().filename() == "param.sfo") {
|
||||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
||||
param_sfo->open(sce_sys_folder.string() + "/param.sfo", {});
|
||||
id = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9);
|
||||
const bool success = param_sfo->Open(sce_sys_folder / "param.sfo");
|
||||
ASSERT_MSG(success, "Failed to open param.sfo");
|
||||
id = std::string(*param_sfo->GetString("CONTENT_ID"), 7, 9);
|
||||
Libraries::NpTrophy::game_serial = id;
|
||||
const auto trophyDir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / id / "TrophyFiles";
|
||||
@ -113,10 +115,10 @@ void Emulator::Run(const std::filesystem::path& file) {
|
||||
#ifdef ENABLE_QT_GUI
|
||||
MemoryPatcher::g_game_serial = id;
|
||||
#endif
|
||||
title = param_sfo->GetString("TITLE");
|
||||
title = *param_sfo->GetString("TITLE");
|
||||
LOG_INFO(Loader, "Game id: {} Title: {}", id, title);
|
||||
u32 fw_version = param_sfo->GetInteger("SYSTEM_VER");
|
||||
app_version = param_sfo->GetString("APP_VER");
|
||||
u32 fw_version = param_sfo->GetInteger("SYSTEM_VER").value_or(0x4700000);
|
||||
app_version = *param_sfo->GetString("APP_VER");
|
||||
LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version);
|
||||
} else if (entry.path().filename() == "playgo-chunk.dat") {
|
||||
auto* playgo = Common::Singleton<PlaygoFile>::Instance();
|
||||
|
@ -27,3 +27,6 @@ extern void assert_fail_debug_msg(const char* msg);
|
||||
|
||||
#define IM_VEC2_CLASS_EXTRA \
|
||||
constexpr ImVec2(float _v) : x(_v), y(_v) {}
|
||||
|
||||
#define IM_VEC4_CLASS_EXTRA \
|
||||
constexpr ImVec4(float _v) : x(_v), y(_v), z(_v), w(_v) {}
|
@ -12,10 +12,6 @@ public:
|
||||
static void RemoveLayer(Layer* layer);
|
||||
|
||||
virtual void Draw() = 0;
|
||||
|
||||
virtual bool ShouldGrabGamepad() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ImGui
|
@ -3,12 +3,25 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <imgui.h>
|
||||
|
||||
#include "imgui_internal.h"
|
||||
|
||||
#define IM_COL32_GRAY(x) IM_COL32(x, x, x, 0xFF)
|
||||
|
||||
namespace ImGui {
|
||||
|
||||
namespace Easing {
|
||||
|
||||
inline float FastInFastOutCubic(float x) {
|
||||
constexpr float c4 = 1.587401f; // 4^(1/3)
|
||||
constexpr float c05 = 0.7937f; // 0.5^(1/3)
|
||||
return std::pow(c4 * x - c05, 3.0f) + 0.5f;
|
||||
}
|
||||
|
||||
} // namespace Easing
|
||||
|
||||
inline void CentralizeWindow() {
|
||||
const auto display_size = GetIO().DisplaySize;
|
||||
SetNextWindowPos(display_size / 2.0f, ImGuiCond_Always, {0.5f});
|
||||
@ -18,10 +31,39 @@ inline void KeepNavHighlight() {
|
||||
GetCurrentContext()->NavDisableHighlight = false;
|
||||
}
|
||||
|
||||
inline void SetItemCurrentNavFocus() {
|
||||
inline void SetItemCurrentNavFocus(const ImGuiID id = -1) {
|
||||
const auto ctx = GetCurrentContext();
|
||||
SetFocusID(ctx->LastItemData.ID, ctx->CurrentWindow);
|
||||
SetFocusID(id == -1 ? ctx->LastItemData.ID : id, ctx->CurrentWindow);
|
||||
ctx->NavInitResult.Clear();
|
||||
ctx->NavDisableHighlight = false;
|
||||
}
|
||||
|
||||
inline void DrawPrettyBackground() {
|
||||
const double time = GetTime() / 1.5f;
|
||||
const float x = ((float)std::cos(time) + 1.0f) / 2.0f;
|
||||
const float d = Easing::FastInFastOutCubic(x);
|
||||
u8 top_left = ImLerp(0x13, 0x05, d);
|
||||
u8 top_right = ImLerp(0x00, 0x07, d);
|
||||
u8 bottom_right = ImLerp(0x03, 0x27, d);
|
||||
u8 bottom_left = ImLerp(0x05, 0x00, d);
|
||||
|
||||
auto& window = *GetCurrentWindowRead();
|
||||
auto inner_pos = window.DC.CursorPos - window.WindowPadding;
|
||||
auto inner_size = GetContentRegionAvail() + window.WindowPadding * 2.0f;
|
||||
GetWindowDrawList()->AddRectFilledMultiColor(
|
||||
inner_pos, inner_pos + inner_size, IM_COL32_GRAY(top_left), IM_COL32_GRAY(top_right),
|
||||
IM_COL32_GRAY(bottom_right), IM_COL32_GRAY(bottom_left));
|
||||
}
|
||||
|
||||
static void DrawCenteredText(const char* text, const char* text_end = nullptr,
|
||||
ImVec2 content = GetContentRegionAvail()) {
|
||||
auto pos = GetCursorPos();
|
||||
const auto text_size = CalcTextSize(text, text_end, false, content.x - 40.0f);
|
||||
PushTextWrapPos(content.x);
|
||||
SetCursorPos(pos + (content - text_size) / 2.0f);
|
||||
TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
|
||||
PopTextWrapPos();
|
||||
SetCursorPos(pos + content);
|
||||
}
|
||||
|
||||
} // namespace ImGui
|
||||
|
45
src/imgui/imgui_texture.h
Normal file
45
src/imgui/imgui_texture.h
Normal file
@ -0,0 +1,45 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <imgui.h>
|
||||
|
||||
namespace ImGui {
|
||||
|
||||
namespace Core::TextureManager {
|
||||
struct Inner;
|
||||
} // namespace Core::TextureManager
|
||||
|
||||
class RefCountedTexture {
|
||||
Core::TextureManager::Inner* inner;
|
||||
|
||||
explicit RefCountedTexture(Core::TextureManager::Inner* inner);
|
||||
|
||||
public:
|
||||
struct Image {
|
||||
ImTextureID im_id;
|
||||
u32 width;
|
||||
u32 height;
|
||||
};
|
||||
|
||||
static RefCountedTexture DecodePngTexture(std::vector<u8> data);
|
||||
|
||||
static RefCountedTexture DecodePngFile(std::filesystem::path path);
|
||||
|
||||
RefCountedTexture();
|
||||
|
||||
RefCountedTexture(const RefCountedTexture& other);
|
||||
RefCountedTexture(RefCountedTexture&& other) noexcept;
|
||||
RefCountedTexture& operator=(const RefCountedTexture& other);
|
||||
RefCountedTexture& operator=(RefCountedTexture&& other) noexcept;
|
||||
|
||||
virtual ~RefCountedTexture();
|
||||
|
||||
[[nodiscard]] Image GetTexture() const;
|
||||
|
||||
explicit(false) operator bool() const;
|
||||
};
|
||||
|
||||
}; // namespace ImGui
|
@ -10,7 +10,7 @@ void ImGui::Layers::VideoInfo::Draw() {
|
||||
m_show = IsKeyPressed(ImGuiKey_F10, false) ^ m_show;
|
||||
|
||||
if (m_show) {
|
||||
if (Begin("Video Info")) {
|
||||
if (Begin("Video Info", 0, ImGuiWindowFlags_NoNav)) {
|
||||
Text("Frame time: %.3f ms (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
|
||||
}
|
||||
End();
|
||||
|
@ -9,7 +9,9 @@
|
||||
#include "imgui_core.h"
|
||||
#include "imgui_impl_sdl3.h"
|
||||
#include "imgui_impl_vulkan.h"
|
||||
#include "imgui_internal.h"
|
||||
#include "sdl_window.h"
|
||||
#include "texture_manager.h"
|
||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||
|
||||
static void CheckVkResult(const vk::Result err) {
|
||||
@ -68,6 +70,8 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w
|
||||
.check_vk_result_fn = &CheckVkResult,
|
||||
};
|
||||
Vulkan::Init(vk_info);
|
||||
|
||||
TextureManager::StartWorker();
|
||||
}
|
||||
|
||||
void OnResize() {
|
||||
@ -77,6 +81,8 @@ void OnResize() {
|
||||
void Shutdown(const vk::Device& device) {
|
||||
device.waitIdle();
|
||||
|
||||
TextureManager::StopWorker();
|
||||
|
||||
const ImGuiIO& io = GetIO();
|
||||
const auto ini_filename = (void*)io.IniFilename;
|
||||
const auto log_filename = (void*)io.LogFilename;
|
||||
@ -92,24 +98,19 @@ void Shutdown(const vk::Device& device) {
|
||||
bool ProcessEvent(SDL_Event* event) {
|
||||
Sdl::ProcessEvent(event);
|
||||
switch (event->type) {
|
||||
// Don't block release/up events
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||
return GetIO().WantCaptureMouse;
|
||||
case SDL_EVENT_TEXT_INPUT:
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
case SDL_EVENT_KEY_UP:
|
||||
return GetIO().WantCaptureKeyboard;
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
||||
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
||||
case SDL_EVENT_GAMEPAD_ADDED:
|
||||
case SDL_EVENT_GAMEPAD_REMOVED:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
|
||||
return (GetIO().BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
|
||||
return GetIO().NavActive;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -130,21 +131,11 @@ void NewFrame() {
|
||||
}
|
||||
}
|
||||
|
||||
Vulkan::NewFrame();
|
||||
Sdl::NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
bool capture_gamepad = false;
|
||||
for (auto* layer : layers) {
|
||||
layer->Draw();
|
||||
if (layer->ShouldGrabGamepad()) {
|
||||
capture_gamepad = true;
|
||||
}
|
||||
}
|
||||
if (capture_gamepad) {
|
||||
GetIO().BackendFlags |= ImGuiBackendFlags_HasGamepad;
|
||||
} else {
|
||||
GetIO().BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,8 @@
|
||||
// Based on imgui_impl_vulkan.cpp from Dear ImGui repository
|
||||
|
||||
#include <cstdio>
|
||||
#include <mutex>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "imgui_impl_vulkan.h"
|
||||
@ -47,13 +49,15 @@ struct VkData {
|
||||
vk::ShaderModule shader_module_vert{};
|
||||
vk::ShaderModule shader_module_frag{};
|
||||
|
||||
std::mutex command_pool_mutex;
|
||||
vk::CommandPool command_pool{};
|
||||
vk::Sampler simple_sampler{};
|
||||
|
||||
// Font data
|
||||
vk::Sampler font_sampler{};
|
||||
vk::DeviceMemory font_memory{};
|
||||
vk::Image font_image{};
|
||||
vk::ImageView font_view{};
|
||||
vk::DescriptorSet font_descriptor_set{};
|
||||
vk::CommandPool font_command_pool{};
|
||||
vk::CommandBuffer font_command_buffer{};
|
||||
|
||||
// Render buffers
|
||||
@ -222,12 +226,53 @@ static inline vk::DeviceSize AlignBufferSize(vk::DeviceSize size, vk::DeviceSize
|
||||
return (size + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
// Register a texture
|
||||
vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view,
|
||||
vk::ImageLayout image_layout) {
|
||||
void UploadTextureData::Upload() {
|
||||
VkData* bd = GetBackendData();
|
||||
const InitInfo& v = bd->init_info;
|
||||
|
||||
vk::SubmitInfo submit_info{
|
||||
.commandBufferCount = 1,
|
||||
.pCommandBuffers = &command_buffer,
|
||||
};
|
||||
CheckVkErr(v.queue.submit({submit_info}));
|
||||
CheckVkErr(v.queue.waitIdle());
|
||||
|
||||
v.device.destroyBuffer(upload_buffer, v.allocator);
|
||||
v.device.freeMemory(upload_buffer_memory, v.allocator);
|
||||
{
|
||||
std::unique_lock lk(bd->command_pool_mutex);
|
||||
v.device.freeCommandBuffers(bd->command_pool, {command_buffer});
|
||||
}
|
||||
upload_buffer = VK_NULL_HANDLE;
|
||||
upload_buffer_memory = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void UploadTextureData::Destroy() {
|
||||
VkData* bd = GetBackendData();
|
||||
const InitInfo& v = bd->init_info;
|
||||
|
||||
CheckVkErr(v.device.waitIdle());
|
||||
RemoveTexture(descriptor_set);
|
||||
descriptor_set = VK_NULL_HANDLE;
|
||||
|
||||
v.device.destroyImageView(image_view, v.allocator);
|
||||
image_view = VK_NULL_HANDLE;
|
||||
v.device.destroyImage(image, v.allocator);
|
||||
image = VK_NULL_HANDLE;
|
||||
v.device.freeMemory(image_memory, v.allocator);
|
||||
image_memory = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
// Register a texture
|
||||
vk::DescriptorSet AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout,
|
||||
vk::Sampler sampler) {
|
||||
VkData* bd = GetBackendData();
|
||||
const InitInfo& v = bd->init_info;
|
||||
|
||||
if (sampler == VK_NULL_HANDLE) {
|
||||
sampler = bd->simple_sampler;
|
||||
}
|
||||
|
||||
// Create Descriptor Set:
|
||||
vk::DescriptorSet descriptor_set;
|
||||
{
|
||||
@ -262,6 +307,166 @@ vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view,
|
||||
}
|
||||
return descriptor_set;
|
||||
}
|
||||
UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, u32 height,
|
||||
size_t size) {
|
||||
ImGuiIO& io = GetIO();
|
||||
VkData* bd = GetBackendData();
|
||||
const InitInfo& v = bd->init_info;
|
||||
|
||||
UploadTextureData info{};
|
||||
{
|
||||
std::unique_lock lk(bd->command_pool_mutex);
|
||||
info.command_buffer =
|
||||
CheckVkResult(v.device.allocateCommandBuffers(vk::CommandBufferAllocateInfo{
|
||||
.commandPool = bd->command_pool,
|
||||
.commandBufferCount = 1,
|
||||
}))
|
||||
.front();
|
||||
CheckVkErr(info.command_buffer.begin(vk::CommandBufferBeginInfo{
|
||||
.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit,
|
||||
}));
|
||||
}
|
||||
|
||||
// Create Image
|
||||
{
|
||||
vk::ImageCreateInfo image_info{
|
||||
.imageType = vk::ImageType::e2D,
|
||||
.format = format,
|
||||
.extent{
|
||||
.width = width,
|
||||
.height = height,
|
||||
.depth = 1,
|
||||
},
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = 1,
|
||||
.samples = vk::SampleCountFlagBits::e1,
|
||||
.tiling = vk::ImageTiling::eOptimal,
|
||||
.usage = vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled,
|
||||
.sharingMode = vk::SharingMode::eExclusive,
|
||||
.initialLayout = vk::ImageLayout::eUndefined,
|
||||
};
|
||||
info.image = CheckVkResult(v.device.createImage(image_info, v.allocator));
|
||||
auto req = v.device.getImageMemoryRequirements(info.image);
|
||||
vk::MemoryAllocateInfo alloc_info{
|
||||
.allocationSize = IM_MAX(v.min_allocation_size, req.size),
|
||||
.memoryTypeIndex =
|
||||
FindMemoryType(vk::MemoryPropertyFlagBits::eDeviceLocal, req.memoryTypeBits),
|
||||
};
|
||||
info.image_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator));
|
||||
CheckVkErr(v.device.bindImageMemory(info.image, info.image_memory, 0));
|
||||
}
|
||||
|
||||
// Create Image View
|
||||
{
|
||||
vk::ImageViewCreateInfo view_info{
|
||||
.image = info.image,
|
||||
.viewType = vk::ImageViewType::e2D,
|
||||
.format = format,
|
||||
.subresourceRange{
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.levelCount = 1,
|
||||
.layerCount = 1,
|
||||
},
|
||||
};
|
||||
info.image_view = CheckVkResult(v.device.createImageView(view_info, v.allocator));
|
||||
}
|
||||
|
||||
// Create descriptor set (ImTextureID)
|
||||
info.descriptor_set = AddTexture(info.image_view, vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||
|
||||
// Create Upload Buffer
|
||||
{
|
||||
vk::BufferCreateInfo buffer_info{
|
||||
.size = size,
|
||||
.usage = vk::BufferUsageFlagBits::eTransferSrc,
|
||||
.sharingMode = vk::SharingMode::eExclusive,
|
||||
};
|
||||
info.upload_buffer = CheckVkResult(v.device.createBuffer(buffer_info, v.allocator));
|
||||
auto req = v.device.getBufferMemoryRequirements(info.upload_buffer);
|
||||
auto alignemtn = IM_MAX(bd->buffer_memory_alignment, req.alignment);
|
||||
vk::MemoryAllocateInfo alloc_info{
|
||||
.allocationSize = IM_MAX(v.min_allocation_size, req.size),
|
||||
.memoryTypeIndex =
|
||||
FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits),
|
||||
};
|
||||
info.upload_buffer_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator));
|
||||
CheckVkErr(v.device.bindBufferMemory(info.upload_buffer, info.upload_buffer_memory, 0));
|
||||
}
|
||||
|
||||
// Upload to Buffer
|
||||
{
|
||||
char* map = (char*)CheckVkResult(v.device.mapMemory(info.upload_buffer_memory, 0, size));
|
||||
memcpy(map, data, size);
|
||||
vk::MappedMemoryRange range[1]{
|
||||
{
|
||||
.memory = info.upload_buffer_memory,
|
||||
.size = size,
|
||||
},
|
||||
};
|
||||
CheckVkErr(v.device.flushMappedMemoryRanges(range));
|
||||
v.device.unmapMemory(info.upload_buffer_memory);
|
||||
}
|
||||
|
||||
// Copy to Image
|
||||
{
|
||||
vk::ImageMemoryBarrier copy_barrier[1]{
|
||||
{
|
||||
.sType = vk::StructureType::eImageMemoryBarrier,
|
||||
.dstAccessMask = vk::AccessFlagBits::eTransferWrite,
|
||||
.oldLayout = vk::ImageLayout::eUndefined,
|
||||
.newLayout = vk::ImageLayout::eTransferDstOptimal,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = info.image,
|
||||
.subresourceRange{
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.levelCount = 1,
|
||||
.layerCount = 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
info.command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eHost,
|
||||
vk::PipelineStageFlagBits::eTransfer, {}, {}, {},
|
||||
{copy_barrier});
|
||||
|
||||
vk::BufferImageCopy region{
|
||||
.imageSubresource{
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.layerCount = 1,
|
||||
},
|
||||
.imageExtent{
|
||||
.width = width,
|
||||
.height = height,
|
||||
.depth = 1,
|
||||
},
|
||||
};
|
||||
info.command_buffer.copyBufferToImage(info.upload_buffer, info.image,
|
||||
vk::ImageLayout::eTransferDstOptimal, {region});
|
||||
|
||||
vk::ImageMemoryBarrier use_barrier[1]{{
|
||||
.sType = vk::StructureType::eImageMemoryBarrier,
|
||||
.srcAccessMask = vk::AccessFlagBits::eTransferWrite,
|
||||
.dstAccessMask = vk::AccessFlagBits::eShaderRead,
|
||||
.oldLayout = vk::ImageLayout::eTransferDstOptimal,
|
||||
.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = info.image,
|
||||
.subresourceRange{
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.levelCount = 1,
|
||||
.layerCount = 1,
|
||||
},
|
||||
}};
|
||||
info.command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {},
|
||||
{use_barrier});
|
||||
}
|
||||
|
||||
CheckVkErr(info.command_buffer.end());
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
void RemoveTexture(vk::DescriptorSet descriptor_set) {
|
||||
VkData* bd = GetBackendData();
|
||||
@ -517,27 +722,20 @@ static bool CreateFontsTexture() {
|
||||
DestroyFontsTexture();
|
||||
}
|
||||
|
||||
// Create command pool/buffer
|
||||
if (bd->font_command_pool == VK_NULL_HANDLE) {
|
||||
vk::CommandPoolCreateInfo info{
|
||||
.sType = vk::StructureType::eCommandPoolCreateInfo,
|
||||
.flags = vk::CommandPoolCreateFlags{},
|
||||
.queueFamilyIndex = v.queue_family,
|
||||
};
|
||||
bd->font_command_pool = CheckVkResult(v.device.createCommandPool(info, v.allocator));
|
||||
}
|
||||
// Create command buffer
|
||||
if (bd->font_command_buffer == VK_NULL_HANDLE) {
|
||||
vk::CommandBufferAllocateInfo info{
|
||||
.sType = vk::StructureType::eCommandBufferAllocateInfo,
|
||||
.commandPool = bd->font_command_pool,
|
||||
.commandPool = bd->command_pool,
|
||||
.commandBufferCount = 1,
|
||||
};
|
||||
std::unique_lock lk(bd->command_pool_mutex);
|
||||
bd->font_command_buffer = CheckVkResult(v.device.allocateCommandBuffers(info)).front();
|
||||
}
|
||||
|
||||
// Start command buffer
|
||||
{
|
||||
CheckVkErr(v.device.resetCommandPool(bd->font_command_pool, vk::CommandPoolResetFlags{}));
|
||||
CheckVkErr(bd->font_command_buffer.reset());
|
||||
vk::CommandBufferBeginInfo begin_info{};
|
||||
begin_info.sType = vk::StructureType::eCommandBufferBeginInfo;
|
||||
begin_info.flags |= vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
|
||||
@ -597,8 +795,7 @@ static bool CreateFontsTexture() {
|
||||
}
|
||||
|
||||
// Create the Descriptor Set:
|
||||
bd->font_descriptor_set =
|
||||
AddTexture(bd->font_sampler, bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||
bd->font_descriptor_set = AddTexture(bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||
|
||||
// Create the Upload Buffer:
|
||||
vk::DeviceMemory upload_buffer_memory{};
|
||||
@ -956,25 +1153,6 @@ bool CreateDeviceObjects() {
|
||||
bd->descriptor_pool = CheckVkResult(v.device.createDescriptorPool(pool_info));
|
||||
}
|
||||
|
||||
if (!bd->font_sampler) {
|
||||
// Bilinear sampling is required by default. Set 'io.Fonts->Flags |=
|
||||
// ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow
|
||||
// point/nearest sampling.
|
||||
vk::SamplerCreateInfo info{
|
||||
.sType = vk::StructureType::eSamplerCreateInfo,
|
||||
.magFilter = vk::Filter::eLinear,
|
||||
.minFilter = vk::Filter::eLinear,
|
||||
.mipmapMode = vk::SamplerMipmapMode::eLinear,
|
||||
.addressModeU = vk::SamplerAddressMode::eRepeat,
|
||||
.addressModeV = vk::SamplerAddressMode::eRepeat,
|
||||
.addressModeW = vk::SamplerAddressMode::eRepeat,
|
||||
.maxAnisotropy = 1.0f,
|
||||
.minLod = -1000,
|
||||
.maxLod = 1000,
|
||||
};
|
||||
bd->font_sampler = CheckVkResult(v.device.createSampler(info, v.allocator));
|
||||
}
|
||||
|
||||
if (!bd->descriptor_set_layout) {
|
||||
vk::DescriptorSetLayoutBinding binding[1]{
|
||||
{
|
||||
@ -1016,6 +1194,35 @@ bool CreateDeviceObjects() {
|
||||
|
||||
CreatePipeline(v.device, v.allocator, v.pipeline_cache, nullptr, &bd->pipeline, v.subpass);
|
||||
|
||||
if (bd->command_pool == VK_NULL_HANDLE) {
|
||||
vk::CommandPoolCreateInfo info{
|
||||
.sType = vk::StructureType::eCommandPoolCreateInfo,
|
||||
.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
|
||||
.queueFamilyIndex = v.queue_family,
|
||||
};
|
||||
std::unique_lock lk(bd->command_pool_mutex);
|
||||
bd->command_pool = CheckVkResult(v.device.createCommandPool(info, v.allocator));
|
||||
}
|
||||
|
||||
if (!bd->simple_sampler) {
|
||||
// Bilinear sampling is required by default. Set 'io.Fonts->Flags |=
|
||||
// ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow
|
||||
// point/nearest sampling.
|
||||
vk::SamplerCreateInfo info{
|
||||
.sType = vk::StructureType::eSamplerCreateInfo,
|
||||
.magFilter = vk::Filter::eLinear,
|
||||
.minFilter = vk::Filter::eLinear,
|
||||
.mipmapMode = vk::SamplerMipmapMode::eLinear,
|
||||
.addressModeU = vk::SamplerAddressMode::eRepeat,
|
||||
.addressModeV = vk::SamplerAddressMode::eRepeat,
|
||||
.addressModeW = vk::SamplerAddressMode::eRepeat,
|
||||
.maxAnisotropy = 1.0f,
|
||||
.minLod = -1000,
|
||||
.maxLod = 1000,
|
||||
};
|
||||
bd->simple_sampler = CheckVkResult(v.device.createSampler(info, v.allocator));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1026,12 +1233,14 @@ void ImGuiImplVulkanDestroyDeviceObjects() {
|
||||
DestroyFontsTexture();
|
||||
|
||||
if (bd->font_command_buffer) {
|
||||
v.device.freeCommandBuffers(bd->font_command_pool, {bd->font_command_buffer});
|
||||
std::unique_lock lk(bd->command_pool_mutex);
|
||||
v.device.freeCommandBuffers(bd->command_pool, {bd->font_command_buffer});
|
||||
bd->font_command_buffer = VK_NULL_HANDLE;
|
||||
}
|
||||
if (bd->font_command_pool) {
|
||||
v.device.destroyCommandPool(bd->font_command_pool, v.allocator);
|
||||
bd->font_command_pool = VK_NULL_HANDLE;
|
||||
if (bd->command_pool) {
|
||||
std::unique_lock lk(bd->command_pool_mutex);
|
||||
v.device.destroyCommandPool(bd->command_pool, v.allocator);
|
||||
bd->command_pool = VK_NULL_HANDLE;
|
||||
}
|
||||
if (bd->shader_module_vert) {
|
||||
v.device.destroyShaderModule(bd->shader_module_vert, v.allocator);
|
||||
@ -1041,9 +1250,9 @@ void ImGuiImplVulkanDestroyDeviceObjects() {
|
||||
v.device.destroyShaderModule(bd->shader_module_frag, v.allocator);
|
||||
bd->shader_module_frag = VK_NULL_HANDLE;
|
||||
}
|
||||
if (bd->font_sampler) {
|
||||
v.device.destroySampler(bd->font_sampler, v.allocator);
|
||||
bd->font_sampler = VK_NULL_HANDLE;
|
||||
if (bd->simple_sampler) {
|
||||
v.device.destroySampler(bd->simple_sampler, v.allocator);
|
||||
bd->simple_sampler = VK_NULL_HANDLE;
|
||||
}
|
||||
if (bd->descriptor_set_layout) {
|
||||
v.device.destroyDescriptorSetLayout(bd->descriptor_set_layout, v.allocator);
|
||||
@ -1095,13 +1304,4 @@ void Shutdown() {
|
||||
IM_DELETE(bd);
|
||||
}
|
||||
|
||||
void NewFrame() {
|
||||
VkData* bd = GetBackendData();
|
||||
IM_ASSERT(bd != nullptr &&
|
||||
"Context or backend not initialized! Did you call ImGuiImplVulkanInit()?");
|
||||
|
||||
if (!bd->font_descriptor_set)
|
||||
CreateFontsTexture();
|
||||
}
|
||||
|
||||
} // namespace ImGui::Vulkan
|
||||
|
@ -6,6 +6,7 @@
|
||||
#pragma once
|
||||
|
||||
#define VULKAN_HPP_NO_EXCEPTIONS
|
||||
#include "common/types.h"
|
||||
#include "video_core/renderer_vulkan/vk_common.h"
|
||||
|
||||
struct ImDrawData;
|
||||
@ -29,14 +30,33 @@ struct InitInfo {
|
||||
void (*check_vk_result_fn)(vk::Result err);
|
||||
};
|
||||
|
||||
vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view,
|
||||
vk::ImageLayout image_layout);
|
||||
// Prepare all resources needed for uploading textures
|
||||
// Caller should clean up the returned data.
|
||||
struct UploadTextureData {
|
||||
vk::Image image;
|
||||
vk::ImageView image_view;
|
||||
vk::DescriptorSet descriptor_set;
|
||||
vk::DeviceMemory image_memory;
|
||||
|
||||
vk::CommandBuffer command_buffer; // Submit to the queue
|
||||
vk::Buffer upload_buffer;
|
||||
vk::DeviceMemory upload_buffer_memory;
|
||||
|
||||
void Upload();
|
||||
|
||||
void Destroy();
|
||||
};
|
||||
|
||||
vk::DescriptorSet AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout,
|
||||
vk::Sampler sampler = VK_NULL_HANDLE);
|
||||
|
||||
UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, u32 height,
|
||||
size_t size);
|
||||
|
||||
void RemoveTexture(vk::DescriptorSet descriptor_set);
|
||||
|
||||
bool Init(InitInfo info);
|
||||
void Shutdown();
|
||||
void NewFrame();
|
||||
void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer,
|
||||
vk::Pipeline pipeline = VK_NULL_HANDLE);
|
||||
|
||||
|
243
src/imgui/renderer/texture_manager.cpp
Normal file
243
src/imgui/renderer/texture_manager.cpp
Normal file
@ -0,0 +1,243 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
|
||||
#include <externals/stb_image.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/io_file.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "imgui_impl_vulkan.h"
|
||||
#include "texture_manager.h"
|
||||
|
||||
namespace ImGui {
|
||||
|
||||
namespace Core::TextureManager {
|
||||
struct Inner {
|
||||
std::atomic_int count = 0;
|
||||
ImTextureID texture_id = nullptr;
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
|
||||
Vulkan::UploadTextureData upload_data;
|
||||
|
||||
~Inner();
|
||||
};
|
||||
} // namespace Core::TextureManager
|
||||
|
||||
using namespace Core::TextureManager;
|
||||
|
||||
RefCountedTexture::RefCountedTexture(Inner* inner) : inner(inner) {
|
||||
++inner->count;
|
||||
}
|
||||
|
||||
RefCountedTexture RefCountedTexture::DecodePngTexture(std::vector<u8> data) {
|
||||
const auto core = new Inner;
|
||||
Core::TextureManager::DecodePngTexture(std::move(data), core);
|
||||
return RefCountedTexture(core);
|
||||
}
|
||||
|
||||
RefCountedTexture RefCountedTexture::DecodePngFile(std::filesystem::path path) {
|
||||
const auto core = new Inner;
|
||||
Core::TextureManager::DecodePngFile(std::move(path), core);
|
||||
return RefCountedTexture(core);
|
||||
}
|
||||
|
||||
RefCountedTexture::RefCountedTexture() : inner(nullptr) {}
|
||||
|
||||
RefCountedTexture::RefCountedTexture(const RefCountedTexture& other) : inner(other.inner) {
|
||||
if (inner != nullptr) {
|
||||
++inner->count;
|
||||
}
|
||||
}
|
||||
|
||||
RefCountedTexture::RefCountedTexture(RefCountedTexture&& other) noexcept : inner(other.inner) {
|
||||
other.inner = nullptr;
|
||||
}
|
||||
|
||||
RefCountedTexture& RefCountedTexture::operator=(const RefCountedTexture& other) {
|
||||
if (this == &other)
|
||||
return *this;
|
||||
inner = other.inner;
|
||||
if (inner != nullptr) {
|
||||
++inner->count;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
RefCountedTexture& RefCountedTexture::operator=(RefCountedTexture&& other) noexcept {
|
||||
if (this == &other)
|
||||
return *this;
|
||||
std::swap(inner, other.inner);
|
||||
return *this;
|
||||
}
|
||||
|
||||
RefCountedTexture::~RefCountedTexture() {
|
||||
if (inner != nullptr) {
|
||||
if (inner->count.fetch_sub(1) == 1) {
|
||||
delete inner;
|
||||
}
|
||||
}
|
||||
}
|
||||
RefCountedTexture::Image RefCountedTexture::GetTexture() const {
|
||||
if (inner == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return Image{
|
||||
.im_id = inner->texture_id,
|
||||
.width = inner->width,
|
||||
.height = inner->height,
|
||||
};
|
||||
}
|
||||
RefCountedTexture::operator bool() const {
|
||||
return inner != nullptr && inner->texture_id != nullptr;
|
||||
}
|
||||
|
||||
struct Job {
|
||||
Inner* core;
|
||||
std::vector<u8> data;
|
||||
std::filesystem::path path;
|
||||
};
|
||||
|
||||
struct UploadJob {
|
||||
Inner* core = nullptr;
|
||||
Vulkan::UploadTextureData data;
|
||||
int tick = 0; // Used to skip the first frame when destroying to await the current frame to draw
|
||||
};
|
||||
|
||||
static bool g_is_worker_running = false;
|
||||
static std::jthread g_worker_thread;
|
||||
static std::condition_variable g_worker_cv;
|
||||
|
||||
static std::mutex g_job_list_mtx;
|
||||
static std::deque<Job> g_job_list;
|
||||
|
||||
static std::mutex g_upload_mtx;
|
||||
static std::deque<UploadJob> g_upload_list;
|
||||
|
||||
namespace Core::TextureManager {
|
||||
|
||||
Inner::~Inner() {
|
||||
if (upload_data.descriptor_set != nullptr) {
|
||||
std::unique_lock lk{g_upload_mtx};
|
||||
g_upload_list.emplace_back(UploadJob{
|
||||
.data = this->upload_data,
|
||||
.tick = 2,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void WorkerLoop() {
|
||||
std::mutex mtx;
|
||||
while (g_is_worker_running) {
|
||||
std::unique_lock lk{mtx};
|
||||
g_worker_cv.wait(lk);
|
||||
if (!g_is_worker_running) {
|
||||
break;
|
||||
}
|
||||
while (true) {
|
||||
g_job_list_mtx.lock();
|
||||
if (g_job_list.empty()) {
|
||||
g_job_list_mtx.unlock();
|
||||
break;
|
||||
}
|
||||
auto [core, png_raw, path] = std::move(g_job_list.front());
|
||||
g_job_list.pop_front();
|
||||
g_job_list_mtx.unlock();
|
||||
|
||||
if (!path.empty()) { // Decode PNG from file
|
||||
Common::FS::IOFile file(path, Common::FS::FileAccessMode::Read);
|
||||
if (!file.IsOpen()) {
|
||||
LOG_ERROR(ImGui, "Failed to open PNG file: {}", path.string());
|
||||
continue;
|
||||
}
|
||||
png_raw.resize(file.GetSize());
|
||||
file.Seek(0);
|
||||
file.ReadRaw<u8>(png_raw.data(), png_raw.size());
|
||||
file.Close();
|
||||
}
|
||||
|
||||
int width, height;
|
||||
const stbi_uc* pixels =
|
||||
stbi_load_from_memory(png_raw.data(), png_raw.size(), &width, &height, nullptr, 4);
|
||||
|
||||
auto texture = Vulkan::UploadTexture(pixels, vk::Format::eR8G8B8A8Unorm, width, height,
|
||||
width * height * 4 * sizeof(stbi_uc));
|
||||
|
||||
core->upload_data = texture;
|
||||
core->width = width;
|
||||
core->height = height;
|
||||
|
||||
std::unique_lock upload_lk{g_upload_mtx};
|
||||
g_upload_list.emplace_back(UploadJob{
|
||||
.core = core,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StartWorker() {
|
||||
ASSERT(!g_is_worker_running);
|
||||
g_worker_thread = std::jthread(WorkerLoop);
|
||||
g_is_worker_running = true;
|
||||
}
|
||||
|
||||
void StopWorker() {
|
||||
ASSERT(g_is_worker_running);
|
||||
g_is_worker_running = false;
|
||||
g_worker_cv.notify_one();
|
||||
}
|
||||
|
||||
void DecodePngTexture(std::vector<u8> data, Inner* core) {
|
||||
++core->count;
|
||||
Job job{
|
||||
.core = core,
|
||||
.data = std::move(data),
|
||||
};
|
||||
std::unique_lock lk{g_job_list_mtx};
|
||||
g_job_list.push_back(std::move(job));
|
||||
g_worker_cv.notify_one();
|
||||
}
|
||||
|
||||
void DecodePngFile(std::filesystem::path path, Inner* core) {
|
||||
++core->count;
|
||||
Job job{
|
||||
.core = core,
|
||||
.path = std::move(path),
|
||||
};
|
||||
std::unique_lock lk{g_job_list_mtx};
|
||||
g_job_list.push_back(std::move(job));
|
||||
g_worker_cv.notify_one();
|
||||
}
|
||||
|
||||
void Submit() {
|
||||
UploadJob upload;
|
||||
{
|
||||
std::unique_lock lk{g_upload_mtx};
|
||||
if (g_upload_list.empty()) {
|
||||
return;
|
||||
}
|
||||
// Upload one texture at a time to avoid slow down
|
||||
upload = g_upload_list.front();
|
||||
g_upload_list.pop_front();
|
||||
if (upload.tick > 0) {
|
||||
--upload.tick;
|
||||
g_upload_list.emplace_back(upload);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (upload.core != nullptr) {
|
||||
upload.core->upload_data.Upload();
|
||||
upload.core->texture_id = upload.core->upload_data.descriptor_set;
|
||||
if (upload.core->count.fetch_sub(1) == 1) {
|
||||
delete upload.core;
|
||||
}
|
||||
} else {
|
||||
upload.data.Destroy();
|
||||
}
|
||||
}
|
||||
} // namespace Core::TextureManager
|
||||
|
||||
} // namespace ImGui
|
30
src/imgui/renderer/texture_manager.h
Normal file
30
src/imgui/renderer/texture_manager.h
Normal file
@ -0,0 +1,30 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#include "common/types.h"
|
||||
#include "imgui/imgui_texture.h"
|
||||
|
||||
namespace vk {
|
||||
class CommandBuffer;
|
||||
}
|
||||
|
||||
namespace ImGui::Core::TextureManager {
|
||||
|
||||
struct Inner;
|
||||
|
||||
void StartWorker();
|
||||
|
||||
void StopWorker();
|
||||
|
||||
void DecodePngTexture(std::vector<u8> data, Inner* core);
|
||||
|
||||
void DecodePngFile(std::filesystem::path path, Inner* core);
|
||||
|
||||
void Submit();
|
||||
|
||||
}; // namespace ImGui::Core::TextureManager
|
@ -27,20 +27,21 @@ public:
|
||||
game.path = filePath;
|
||||
|
||||
PSF psf;
|
||||
if (psf.open(game.path + "/sce_sys/param.sfo", {})) {
|
||||
if (psf.Open(std::filesystem::path(game.path) / "sce_sys" / "param.sfo")) {
|
||||
game.icon_path = game.path + "/sce_sys/icon0.png";
|
||||
QString iconpath = QString::fromStdString(game.icon_path);
|
||||
game.icon = QImage(iconpath);
|
||||
game.pic_path = game.path + "/sce_sys/pic1.png";
|
||||
game.name = psf.GetString("TITLE");
|
||||
game.serial = psf.GetString("TITLE_ID");
|
||||
game.region = GameListUtils::GetRegion(psf.GetString("CONTENT_ID").at(0)).toStdString();
|
||||
u32 fw_int = psf.GetInteger("SYSTEM_VER");
|
||||
game.name = *psf.GetString("TITLE");
|
||||
game.serial = *psf.GetString("TITLE_ID");
|
||||
game.region =
|
||||
GameListUtils::GetRegion(psf.GetString("CONTENT_ID")->at(0)).toStdString();
|
||||
u32 fw_int = *psf.GetInteger("SYSTEM_VER");
|
||||
QString fw = QString::number(fw_int, 16);
|
||||
QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.')
|
||||
: fw.left(3).insert(1, '.');
|
||||
game.fw = (fw_int == 0) ? "0.00" : fw_.toStdString();
|
||||
game.version = psf.GetString("APP_VER");
|
||||
game.version = *psf.GetString("APP_VER");
|
||||
}
|
||||
return game;
|
||||
}
|
||||
|
@ -80,8 +80,8 @@ public:
|
||||
|
||||
if (selected == &openSfoViewer) {
|
||||
PSF psf;
|
||||
if (psf.open(m_games[itemID].path + "/sce_sys/param.sfo", {})) {
|
||||
int rows = psf.map_strings.size() + psf.map_integers.size();
|
||||
if (psf.Open(std::filesystem::path(m_games[itemID].path) / "sce_sys" / "param.sfo")) {
|
||||
int rows = psf.GetEntries().size();
|
||||
QTableWidget* tableWidget = new QTableWidget(rows, 2);
|
||||
tableWidget->setAttribute(Qt::WA_DeleteOnClose);
|
||||
connect(widget->parent(), &QWidget::destroyed, tableWidget,
|
||||
@ -90,23 +90,33 @@ public:
|
||||
tableWidget->verticalHeader()->setVisible(false); // Hide vertical header
|
||||
int row = 0;
|
||||
|
||||
for (const auto& pair : psf.map_strings) {
|
||||
for (const auto& entry : psf.GetEntries()) {
|
||||
QTableWidgetItem* keyItem =
|
||||
new QTableWidgetItem(QString::fromStdString(pair.first));
|
||||
QTableWidgetItem* valueItem =
|
||||
new QTableWidgetItem(QString::fromStdString(pair.second));
|
||||
new QTableWidgetItem(QString::fromStdString(entry.key));
|
||||
QTableWidgetItem* valueItem;
|
||||
switch (entry.param_fmt) {
|
||||
case PSFEntryFmt::Binary: {
|
||||
|
||||
tableWidget->setItem(row, 0, keyItem);
|
||||
tableWidget->setItem(row, 1, valueItem);
|
||||
keyItem->setFlags(keyItem->flags() & ~Qt::ItemIsEditable);
|
||||
valueItem->setFlags(valueItem->flags() & ~Qt::ItemIsEditable);
|
||||
row++;
|
||||
const auto bin = *psf.GetBinary(entry.key);
|
||||
std::string text;
|
||||
text.reserve(bin.size() * 2);
|
||||
for (const auto& c : bin) {
|
||||
static constexpr char hex[] = "0123456789ABCDEF";
|
||||
text.push_back(hex[c >> 4 & 0xF]);
|
||||
text.push_back(hex[c & 0xF]);
|
||||
}
|
||||
valueItem = new QTableWidgetItem(QString::fromStdString(text));
|
||||
} break;
|
||||
case PSFEntryFmt::Text: {
|
||||
auto text = *psf.GetString(entry.key);
|
||||
valueItem = new QTableWidgetItem(QString::fromStdString(std::string{text}));
|
||||
} break;
|
||||
case PSFEntryFmt::Integer: {
|
||||
auto integer = *psf.GetInteger(entry.key);
|
||||
valueItem =
|
||||
new QTableWidgetItem(QString("0x") + QString::number(integer, 16));
|
||||
} break;
|
||||
}
|
||||
for (const auto& pair : psf.map_integers) {
|
||||
QTableWidgetItem* keyItem =
|
||||
new QTableWidgetItem(QString::fromStdString(pair.first));
|
||||
QTableWidgetItem* valueItem = new QTableWidgetItem(
|
||||
QString("0x").append(QString::number(pair.second, 16)));
|
||||
|
||||
tableWidget->setItem(row, 0, keyItem);
|
||||
tableWidget->setItem(row, 1, valueItem);
|
||||
|
@ -636,9 +636,9 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
|
||||
QMessageBox msgBox;
|
||||
msgBox.setWindowTitle(tr("PKG Extraction"));
|
||||
|
||||
psf.open("", pkg.sfo);
|
||||
psf.Open(pkg.sfo);
|
||||
|
||||
std::string content_id = psf.GetString("CONTENT_ID");
|
||||
std::string content_id{*psf.GetString("CONTENT_ID")};
|
||||
std::string entitlement_label = Common::SplitString(content_id, '-')[2];
|
||||
|
||||
auto addon_extract_path = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir) /
|
||||
@ -647,9 +647,11 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
|
||||
auto category = psf.GetString("CATEGORY");
|
||||
|
||||
if (pkgType.contains("PATCH")) {
|
||||
QString pkg_app_version = QString::fromStdString(psf.GetString("APP_VER"));
|
||||
psf.open(extract_path.string() + "/sce_sys/param.sfo", {});
|
||||
QString game_app_version = QString::fromStdString(psf.GetString("APP_VER"));
|
||||
QString pkg_app_version =
|
||||
QString::fromStdString(std::string{*psf.GetString("APP_VER")});
|
||||
psf.Open(extract_path / "sce_sys" / "param.sfo");
|
||||
QString game_app_version =
|
||||
QString::fromStdString(std::string{*psf.GetString("APP_VER")});
|
||||
double appD = game_app_version.toDouble();
|
||||
double pkgD = pkg_app_version.toDouble();
|
||||
if (pkgD == appD) {
|
||||
|
@ -109,12 +109,12 @@ void PKGViewer::ProcessPKGInfo() {
|
||||
path = std::filesystem::path(m_pkg_list[i].toStdWString());
|
||||
#endif
|
||||
package.Open(path);
|
||||
psf.open("", package.sfo);
|
||||
QString title_name = QString::fromStdString(psf.GetString("TITLE"));
|
||||
QString title_id = QString::fromStdString(psf.GetString("TITLE_ID"));
|
||||
QString app_type = game_list_util.GetAppType(psf.GetInteger("APP_TYPE"));
|
||||
QString app_version = QString::fromStdString(psf.GetString("APP_VER"));
|
||||
QString title_category = QString::fromStdString(psf.GetString("CATEGORY"));
|
||||
psf.Open(package.sfo);
|
||||
QString title_name = QString::fromStdString(std::string{*psf.GetString("TITLE")});
|
||||
QString title_id = QString::fromStdString(std::string{*psf.GetString("TITLE_ID")});
|
||||
QString app_type = game_list_util.GetAppType(*psf.GetInteger("APP_TYPE"));
|
||||
QString app_version = QString::fromStdString(std::string{*psf.GetString("APP_VER")});
|
||||
QString title_category = QString::fromStdString(std::string{*psf.GetString("CATEGORY")});
|
||||
QString pkg_size = game_list_util.FormatSize(package.GetPkgHeader().pkg_size);
|
||||
pkg_content_flag = package.GetPkgHeader().pkg_content_flags;
|
||||
QString flagss = "";
|
||||
@ -126,7 +126,7 @@ void PKGViewer::ProcessPKGInfo() {
|
||||
}
|
||||
}
|
||||
|
||||
u32 fw_int = psf.GetInteger("SYSTEM_VER");
|
||||
u32 fw_int = *psf.GetInteger("SYSTEM_VER");
|
||||
QString fw = QString::number(fw_int, 16);
|
||||
QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.')
|
||||
: fw.left(3).insert(1, '.');
|
||||
|
@ -33,7 +33,6 @@ private:
|
||||
PKGHeader pkgheader;
|
||||
PKGEntry entry;
|
||||
PSFHeader header;
|
||||
PSFEntry psfentry;
|
||||
char pkgTitleID[9];
|
||||
std::vector<u8> pkg;
|
||||
u64 pkgSize = 0;
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <mutex>
|
||||
#include "common/assert.h"
|
||||
#include "common/debug.h"
|
||||
#include "imgui/renderer/texture_manager.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
|
||||
@ -190,6 +191,7 @@ void Scheduler::SubmitExecution(SubmitInfo& info) {
|
||||
};
|
||||
|
||||
try {
|
||||
ImGui::Core::TextureManager::Submit();
|
||||
instance.GetGraphicsQueue().submit(submit_info, info.fence);
|
||||
} catch (vk::DeviceLostError& err) {
|
||||
UNREACHABLE_MSG("Device lost during submit: {}", err.what());
|
||||
|
Loading…
Reference in New Issue
Block a user