mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2024-12-26 17:37:03 +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/msgdialog_ui.cpp
|
||||||
src/core/libraries/system/posix.cpp
|
src/core/libraries/system/posix.cpp
|
||||||
src/core/libraries/system/posix.h
|
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.cpp
|
||||||
src/core/libraries/save_data/savedata.h
|
src/core/libraries/save_data/savedata.h
|
||||||
src/core/libraries/system/savedatadialog.cpp
|
src/core/libraries/save_data/dialog/savedatadialog.cpp
|
||||||
src/core/libraries/system/savedatadialog.h
|
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.cpp
|
||||||
src/core/libraries/system/sysmodule.h
|
src/core/libraries/system/sysmodule.h
|
||||||
src/core/libraries/system/systemservice.cpp
|
src/core/libraries/system/systemservice.cpp
|
||||||
|
@ -349,6 +356,7 @@ set(COMMON src/common/logging/backend.cpp
|
||||||
src/common/concepts.h
|
src/common/concepts.h
|
||||||
src/common/config.cpp
|
src/common/config.cpp
|
||||||
src/common/config.h
|
src/common/config.h
|
||||||
|
src/common/cstring.h
|
||||||
src/common/debug.h
|
src/common/debug.h
|
||||||
src/common/disassembler.cpp
|
src/common/disassembler.cpp
|
||||||
src/common/disassembler.h
|
src/common/disassembler.h
|
||||||
|
@ -607,6 +615,7 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
|
||||||
set(IMGUI src/imgui/imgui_config.h
|
set(IMGUI src/imgui/imgui_config.h
|
||||||
src/imgui/imgui_layer.h
|
src/imgui/imgui_layer.h
|
||||||
src/imgui/imgui_std.h
|
src/imgui/imgui_std.h
|
||||||
|
src/imgui/imgui_texture.h
|
||||||
src/imgui/layer/video_info.cpp
|
src/imgui/layer/video_info.cpp
|
||||||
src/imgui/layer/video_info.h
|
src/imgui/layer/video_info.h
|
||||||
src/imgui/renderer/imgui_core.cpp
|
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_sdl3.h
|
||||||
src/imgui/renderer/imgui_impl_vulkan.cpp
|
src/imgui/renderer/imgui_impl_vulkan.cpp
|
||||||
src/imgui/renderer/imgui_impl_vulkan.h
|
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
|
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);
|
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
|
} // namespace Common::FS
|
||||||
|
|
|
@ -219,4 +219,6 @@ private:
|
||||||
uintptr_t file_mapping = 0;
|
uintptr_t file_mapping = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
u64 GetDirectorySize(const std::filesystem::path& path);
|
||||||
|
|
||||||
} // namespace Common::FS
|
} // namespace Common::FS
|
||||||
|
|
|
@ -14,12 +14,17 @@
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
std::string ToLower(std::string str) {
|
std::string ToLower(std::string_view input) {
|
||||||
std::transform(str.begin(), str.end(), str.begin(),
|
std::string str;
|
||||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
str.resize(input.size());
|
||||||
|
std::ranges::transform(input, str.begin(), tolower);
|
||||||
return str;
|
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::vector<std::string> SplitString(const std::string& str, char delimiter) {
|
||||||
std::istringstream iss(str);
|
std::istringstream iss(str);
|
||||||
std::vector<std::string> output(1);
|
std::vector<std::string> output(1);
|
||||||
|
|
|
@ -10,7 +10,9 @@
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
/// Make a string lowercase
|
/// 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);
|
std::vector<std::string> SplitString(const std::string& str, char delimiter);
|
||||||
|
|
||||||
|
|
|
@ -2,61 +2,275 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
#include "common/io_file.h"
|
#include "common/io_file.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
#include "core/file_format/psf.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)) {
|
||||||
bool PSF::open(const std::string& filepath, const std::vector<u8>& psfBuffer) {
|
last_write = std::filesystem::last_write_time(filepath);
|
||||||
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);
|
|
||||||
file.Seek(0);
|
|
||||||
file.Read(psf);
|
|
||||||
file.Close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse file contents
|
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
|
||||||
PSFHeader header;
|
if (!file.IsOpen()) {
|
||||||
std::memcpy(&header, psf.data(), sizeof(header));
|
return false;
|
||||||
for (u32 i = 0; i < header.index_table_entries; i++) {
|
}
|
||||||
PSFEntry entry;
|
|
||||||
std::memcpy(&entry, &psf[sizeof(PSFHeader) + i * sizeof(PSFEntry)], sizeof(entry));
|
|
||||||
|
|
||||||
const std::string key = (char*)&psf[header.key_table_offset + entry.key_offset];
|
const u64 psfSize = file.GetSize();
|
||||||
if (entry.param_fmt == PSFEntry::Fmt::TextRaw ||
|
std::vector<u8> psf(psfSize);
|
||||||
entry.param_fmt == PSFEntry::Fmt::TextNormal) {
|
file.Seek(0);
|
||||||
map_strings[key] = (char*)&psf[header.data_table_offset + entry.data_offset];
|
file.Read(psf);
|
||||||
}
|
file.Close();
|
||||||
if (entry.param_fmt == PSFEntry::Fmt::Integer) {
|
return Open(psf);
|
||||||
u32 value;
|
}
|
||||||
std::memcpy(&value, &psf[header.data_table_offset + entry.data_offset], sizeof(value));
|
|
||||||
map_integers[key] = value;
|
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));
|
||||||
|
|
||||||
|
if (header.magic != PSF_MAGIC) {
|
||||||
|
LOG_ERROR(Core, "Invalid PSF magic number");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string PSF::GetString(const std::string& key) {
|
bool PSF::Encode(const std::filesystem::path& filepath) const {
|
||||||
if (map_strings.find(key) != map_strings.end()) {
|
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Write);
|
||||||
return map_strings.at(key);
|
if (!file.IsOpen()) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return "";
|
|
||||||
|
last_write = std::filesystem::file_time_type::clock::now();
|
||||||
|
|
||||||
|
const auto psf_buffer = Encode();
|
||||||
|
return file.Write(psf_buffer) == psf_buffer.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 PSF::GetInteger(const std::string& key) {
|
std::vector<u8> PSF::Encode() const {
|
||||||
if (map_integers.find(key) != map_integers.end()) {
|
std::vector<u8> psf_buffer;
|
||||||
return map_integers.at(key);
|
Encode(psf_buffer);
|
||||||
}
|
return psf_buffer;
|
||||||
return -1;
|
}
|
||||||
|
|
||||||
|
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
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <span>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "common/endian.h"
|
#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 {
|
struct PSFHeader {
|
||||||
u32_be magic;
|
u32_be magic;
|
||||||
u32_le version;
|
u32_le version;
|
||||||
|
@ -15,34 +22,72 @@ struct PSFHeader {
|
||||||
u32_le data_table_offset;
|
u32_le data_table_offset;
|
||||||
u32_le index_table_entries;
|
u32_le index_table_entries;
|
||||||
};
|
};
|
||||||
|
static_assert(sizeof(PSFHeader) == 0x14);
|
||||||
|
|
||||||
struct PSFEntry {
|
struct PSFRawEntry {
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
u16_le key_offset;
|
u16_le key_offset;
|
||||||
u16_be param_fmt;
|
u16_be param_fmt;
|
||||||
u32_le param_len;
|
u32_le param_len;
|
||||||
u32_le param_max_len;
|
u32_le param_max_len;
|
||||||
u32_le data_offset;
|
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 {
|
class PSF {
|
||||||
|
struct Entry {
|
||||||
|
std::string key;
|
||||||
|
PSFEntryFmt param_fmt;
|
||||||
|
u32 max_len;
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PSF();
|
PSF() = default;
|
||||||
~PSF();
|
~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);
|
bool Open(const std::filesystem::path& filepath);
|
||||||
u32 GetInteger(const std::string& key);
|
bool Open(const std::vector<u8>& psf_buffer);
|
||||||
|
|
||||||
std::unordered_map<std::string, std::string> map_strings;
|
[[nodiscard]] std::vector<u8> Encode() const;
|
||||||
std::unordered_map<std::string, u32> map_integers;
|
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:
|
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
|
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};
|
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) {
|
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();
|
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
|
// Evil games like Turok2 pass double slashes e.g /app0//game.kpf
|
||||||
std::string corrected_path(guest_directory);
|
std::string corrected_path(guest_directory);
|
||||||
size_t pos = corrected_path.find("//");
|
size_t pos = corrected_path.find("//");
|
||||||
|
@ -40,6 +41,10 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_read_only) {
|
||||||
|
*is_read_only = mount->read_only;
|
||||||
|
}
|
||||||
|
|
||||||
// Nothing to do if getting the mount itself.
|
// Nothing to do if getting the mount itself.
|
||||||
if (corrected_path == mount->mount) {
|
if (corrected_path == mount->mount) {
|
||||||
return mount->host_path;
|
return mount->host_path;
|
||||||
|
|
|
@ -22,16 +22,19 @@ public:
|
||||||
struct MntPair {
|
struct MntPair {
|
||||||
std::filesystem::path host_path;
|
std::filesystem::path host_path;
|
||||||
std::string mount; // e.g /app0/
|
std::string mount; // e.g /app0/
|
||||||
|
bool read_only;
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit MntPoints() = default;
|
explicit MntPoints() = default;
|
||||||
~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 Unmount(const std::filesystem::path& host_folder, const std::string& guest_folder);
|
||||||
void UnmountAll();
|
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) {
|
const MntPair* GetMount(const std::string& guest_path) {
|
||||||
std::scoped_lock lock{m_mutex};
|
std::scoped_lock lock{m_mutex};
|
||||||
|
|
|
@ -90,37 +90,32 @@ int PS4_SYSV_ABI sceAppContentAddcontUnmount() {
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAppContentAppParamGetInt(OrbisAppContentAppParamId paramId, s32* value) {
|
int PS4_SYSV_ABI sceAppContentAppParamGetInt(OrbisAppContentAppParamId paramId, s32* out_value) {
|
||||||
if (value == nullptr)
|
if (out_value == nullptr)
|
||||||
return ORBIS_APP_CONTENT_ERROR_PARAMETER;
|
return ORBIS_APP_CONTENT_ERROR_PARAMETER;
|
||||||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
||||||
|
std::optional<s32> value;
|
||||||
switch (paramId) {
|
switch (paramId) {
|
||||||
case ORBIS_APP_CONTENT_APPPARAM_ID_SKU_FLAG:
|
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;
|
break;
|
||||||
case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_1:
|
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;
|
break;
|
||||||
case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_2:
|
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;
|
break;
|
||||||
case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_3:
|
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;
|
break;
|
||||||
case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_4:
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
LOG_ERROR(Lib_AppContent, " paramId = {}, value = {} paramId is not valid", paramId,
|
LOG_ERROR(Lib_AppContent, " paramId = {} 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);
|
|
||||||
return ORBIS_APP_CONTENT_ERROR_PARAMETER;
|
return ORBIS_APP_CONTENT_ERROR_PARAMETER;
|
||||||
}
|
}
|
||||||
|
*out_value = value.value_or(0);
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,7 +246,7 @@ int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initPar
|
||||||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
||||||
|
|
||||||
const auto addons_dir = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir);
|
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;
|
auto addon_path = addons_dir / title_id;
|
||||||
if (std::filesystem::exists(addon_path)) {
|
if (std::filesystem::exists(addon_path)) {
|
||||||
for (const auto& entry : std::filesystem::directory_iterator(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* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
|
||||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::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()) {
|
if (host_path.empty()) {
|
||||||
return SCE_KERNEL_ERROR_EACCES;
|
return SCE_KERNEL_ERROR_EACCES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ro) {
|
||||||
|
return SCE_KERNEL_ERROR_EROFS;
|
||||||
|
}
|
||||||
|
|
||||||
if (std::filesystem::is_directory(host_path)) {
|
if (std::filesystem::is_directory(host_path)) {
|
||||||
return SCE_KERNEL_ERROR_EPERM;
|
return SCE_KERNEL_ERROR_EPERM;
|
||||||
}
|
}
|
||||||
|
@ -270,11 +275,18 @@ int PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) {
|
||||||
return SCE_KERNEL_ERROR_EINVAL;
|
return SCE_KERNEL_ERROR_EINVAL;
|
||||||
}
|
}
|
||||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
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)) {
|
if (std::filesystem::exists(dir_name)) {
|
||||||
return SCE_KERNEL_ERROR_EEXIST;
|
return SCE_KERNEL_ERROR_EEXIST;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ro) {
|
||||||
|
return SCE_KERNEL_ERROR_EROFS;
|
||||||
|
}
|
||||||
|
|
||||||
// CUSA02456: path = /aotl after sceSaveDataMount(mode = 1)
|
// CUSA02456: path = /aotl after sceSaveDataMount(mode = 1)
|
||||||
if (dir_name.empty() || !std::filesystem::create_directory(dir_name)) {
|
if (dir_name.empty() || !std::filesystem::create_directory(dir_name)) {
|
||||||
return SCE_KERNEL_ERROR_EIO;
|
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) {
|
int PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) {
|
||||||
LOG_INFO(Kernel_Fs, "(PARTIAL) path = {}", path);
|
LOG_INFO(Kernel_Fs, "(PARTIAL) path = {}", path);
|
||||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
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));
|
std::memset(sb, 0, sizeof(OrbisKernelStat));
|
||||||
const bool is_dir = std::filesystem::is_directory(path_name);
|
const bool is_dir = std::filesystem::is_directory(path_name);
|
||||||
const bool is_file = std::filesystem::is_regular_file(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;
|
sb->st_blocks = (sb->st_size + 511) / 512;
|
||||||
// TODO incomplete
|
// TODO incomplete
|
||||||
}
|
}
|
||||||
|
if (ro) {
|
||||||
|
sb->st_mode &= ~0000555u;
|
||||||
|
}
|
||||||
|
|
||||||
return ORBIS_OK;
|
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) {
|
s32 PS4_SYSV_ABI sceKernelRename(const char* from, const char* to) {
|
||||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
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)) {
|
if (!std::filesystem::exists(src_path)) {
|
||||||
return ORBIS_KERNEL_ERROR_ENOENT;
|
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 src_is_dir = std::filesystem::is_directory(src_path);
|
||||||
const bool dst_is_dir = std::filesystem::is_directory(dst_path);
|
const bool dst_is_dir = std::filesystem::is_directory(dst_path);
|
||||||
if (src_is_dir && !dst_is_dir) {
|
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) {
|
int PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(int* ver) {
|
||||||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
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);
|
LOG_INFO(Kernel, "returned system version = {:#x}", version);
|
||||||
*ver = version;
|
*ver = version;
|
||||||
return (version > 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL;
|
return (version > 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL;
|
||||||
|
|
|
@ -27,12 +27,12 @@
|
||||||
#include "core/libraries/playgo/playgo.h"
|
#include "core/libraries/playgo/playgo.h"
|
||||||
#include "core/libraries/random/random.h"
|
#include "core/libraries/random/random.h"
|
||||||
#include "core/libraries/rtc/rtc.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/save_data/savedata.h"
|
||||||
#include "core/libraries/screenshot/screenshot.h"
|
#include "core/libraries/screenshot/screenshot.h"
|
||||||
#include "core/libraries/system/commondialog.h"
|
#include "core/libraries/system/commondialog.h"
|
||||||
#include "core/libraries/system/msgdialog.h"
|
#include "core/libraries/system/msgdialog.h"
|
||||||
#include "core/libraries/system/posix.h"
|
#include "core/libraries/system/posix.h"
|
||||||
#include "core/libraries/system/savedatadialog.h"
|
|
||||||
#include "core/libraries/system/sysmodule.h"
|
#include "core/libraries/system/sysmodule.h"
|
||||||
#include "core/libraries/system/systemservice.h"
|
#include "core/libraries/system/systemservice.h"
|
||||||
#include "core/libraries/system/userservice.h"
|
#include "core/libraries/system/userservice.h"
|
||||||
|
@ -57,11 +57,11 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
|
||||||
Libraries::Net::RegisterlibSceNet(sym);
|
Libraries::Net::RegisterlibSceNet(sym);
|
||||||
Libraries::NetCtl::RegisterlibSceNetCtl(sym);
|
Libraries::NetCtl::RegisterlibSceNetCtl(sym);
|
||||||
Libraries::SaveData::RegisterlibSceSaveData(sym);
|
Libraries::SaveData::RegisterlibSceSaveData(sym);
|
||||||
|
Libraries::SaveData::Dialog::RegisterlibSceSaveDataDialog(sym);
|
||||||
Libraries::Ssl::RegisterlibSceSsl(sym);
|
Libraries::Ssl::RegisterlibSceSsl(sym);
|
||||||
Libraries::SysModule::RegisterlibSceSysmodule(sym);
|
Libraries::SysModule::RegisterlibSceSysmodule(sym);
|
||||||
Libraries::Posix::Registerlibsceposix(sym);
|
Libraries::Posix::Registerlibsceposix(sym);
|
||||||
Libraries::AudioIn::RegisterlibSceAudioIn(sym);
|
Libraries::AudioIn::RegisterlibSceAudioIn(sym);
|
||||||
Libraries::SaveDataDialog::RegisterlibSceSaveDataDialog(sym);
|
|
||||||
Libraries::NpManager::RegisterlibSceNpManager(sym);
|
Libraries::NpManager::RegisterlibSceNpManager(sym);
|
||||||
Libraries::NpScore::RegisterlibSceNpScore(sym);
|
Libraries::NpScore::RegisterlibSceNpScore(sym);
|
||||||
Libraries::NpTrophy::RegisterlibSceNpTrophy(sym);
|
Libraries::NpTrophy::RegisterlibSceNpTrophy(sym);
|
||||||
|
|
|
@ -31,10 +31,6 @@ public:
|
||||||
void Finish();
|
void Finish();
|
||||||
|
|
||||||
void Draw() override;
|
void Draw() override;
|
||||||
|
|
||||||
bool ShouldGrabGamepad() override {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}; // namespace Libraries::NpTrophy
|
}; // 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
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/cstring.h"
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
|
|
||||||
namespace Core::Loader {
|
namespace Core::Loader {
|
||||||
class SymbolsResolver;
|
class SymbolsResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PSF;
|
||||||
|
|
||||||
namespace Libraries::SaveData {
|
namespace Libraries::SaveData {
|
||||||
|
|
||||||
constexpr int ORBIS_SAVE_DATA_DIRNAME_DATA_MAXSIZE =
|
constexpr size_t OrbisSaveDataTitleMaxsize = 128; // Maximum title name size
|
||||||
32; // Maximum size for a save data directory name
|
constexpr size_t OrbisSaveDataSubtitleMaxsize = 128; // Maximum subtitle name size
|
||||||
constexpr int ORBIS_SAVE_DATA_MOUNT_POINT_DATA_MAXSIZE = 16; // Maximum size for a mount point name
|
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 {
|
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 {
|
struct OrbisSaveDataParam {
|
||||||
char title[ORBIS_SAVE_DATA_TITLE_MAXSIZE];
|
Common::CString<OrbisSaveDataTitleMaxsize> title;
|
||||||
char subTitle[ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE];
|
Common::CString<OrbisSaveDataSubtitleMaxsize> subTitle;
|
||||||
char detail[ORBIS_SAVE_DATA_DETAIL_MAXSIZE];
|
Common::CString<OrbisSaveDataDetailMaxsize> detail;
|
||||||
u32 userParam;
|
u32 userParam;
|
||||||
int : 32;
|
int : 32;
|
||||||
time_t mtime;
|
time_t mtime;
|
||||||
u8 reserved[32];
|
std::array<u8, 32> _reserved;
|
||||||
|
|
||||||
|
void FromSFO(const PSF& sfo);
|
||||||
|
|
||||||
|
void ToSFO(PSF& sfo) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct OrbisSaveDataIcon {
|
struct OrbisSaveDataBackup;
|
||||||
void* buf;
|
struct OrbisSaveDataCheckBackupData;
|
||||||
size_t bufSize;
|
struct OrbisSaveDataDelete;
|
||||||
size_t dataSize;
|
struct OrbisSaveDataDirNameSearchCond;
|
||||||
u8 reserved[32];
|
struct OrbisSaveDataDirNameSearchResult;
|
||||||
};
|
struct OrbisSaveDataEvent;
|
||||||
|
struct OrbisSaveDataEventParam;
|
||||||
typedef u32 OrbisSaveDataSaveDataMemoryOption;
|
struct OrbisSaveDataIcon;
|
||||||
#define ORBIS_SAVE_DATA_MEMORY_OPTION_NONE (0x00000000)
|
struct OrbisSaveDataMemoryGet2;
|
||||||
#define ORBIS_SAVE_DATA_MEMORY_OPTION_SET_PARAM (0x00000001 << 0)
|
struct OrbisSaveDataMemorySet2;
|
||||||
#define ORBIS_SAVE_DATA_MEMORY_OPTION_DOUBLE_BUFFER (0x00000001 << 1)
|
struct OrbisSaveDataMemorySetup2;
|
||||||
|
struct OrbisSaveDataMemorySetupResult;
|
||||||
struct OrbisSaveDataMemorySetup2 {
|
struct OrbisSaveDataMemorySync;
|
||||||
OrbisSaveDataSaveDataMemoryOption option;
|
struct OrbisSaveDataMount2;
|
||||||
s32 userId;
|
struct OrbisSaveDataMount;
|
||||||
size_t memorySize;
|
struct OrbisSaveDataMountInfo;
|
||||||
size_t iconMemorySize;
|
struct OrbisSaveDataMountPoint;
|
||||||
const OrbisSaveDataParam* initParam;
|
struct OrbisSaveDataMountResult;
|
||||||
const OrbisSaveDataIcon* initIcon;
|
struct OrbisSaveDataRestoreBackupData;
|
||||||
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;
|
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceSaveDataAbort();
|
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 sceSaveDataBindPsnAccount();
|
||||||
int PS4_SYSV_ABI sceSaveDataBindPsnAccountForSystemBackup();
|
int PS4_SYSV_ABI sceSaveDataBindPsnAccountForSystemBackup();
|
||||||
int PS4_SYSV_ABI sceSaveDataChangeDatabase();
|
int PS4_SYSV_ABI sceSaveDataChangeDatabase();
|
||||||
int PS4_SYSV_ABI sceSaveDataChangeInternal();
|
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 sceSaveDataCheckBackupDataForCdlg();
|
||||||
int PS4_SYSV_ABI sceSaveDataCheckBackupDataInternal();
|
int PS4_SYSV_ABI sceSaveDataCheckBackupDataInternal();
|
||||||
int PS4_SYSV_ABI sceSaveDataCheckCloudData();
|
int PS4_SYSV_ABI sceSaveDataCheckCloudData();
|
||||||
|
@ -263,7 +85,7 @@ int PS4_SYSV_ABI sceSaveDataCheckIpmiIfSize();
|
||||||
int PS4_SYSV_ABI sceSaveDataCheckSaveDataBroken();
|
int PS4_SYSV_ABI sceSaveDataCheckSaveDataBroken();
|
||||||
int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersion();
|
int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersion();
|
||||||
int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersionLatest();
|
int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersionLatest();
|
||||||
int PS4_SYSV_ABI sceSaveDataClearProgress();
|
Error PS4_SYSV_ABI sceSaveDataClearProgress();
|
||||||
int PS4_SYSV_ABI sceSaveDataCopy5();
|
int PS4_SYSV_ABI sceSaveDataCopy5();
|
||||||
int PS4_SYSV_ABI sceSaveDataCreateUploadData();
|
int PS4_SYSV_ABI sceSaveDataCreateUploadData();
|
||||||
int PS4_SYSV_ABI sceSaveDataDebug();
|
int PS4_SYSV_ABI sceSaveDataDebug();
|
||||||
|
@ -273,13 +95,13 @@ int PS4_SYSV_ABI sceSaveDataDebugCreateSaveDataRoot();
|
||||||
int PS4_SYSV_ABI sceSaveDataDebugGetThreadId();
|
int PS4_SYSV_ABI sceSaveDataDebugGetThreadId();
|
||||||
int PS4_SYSV_ABI sceSaveDataDebugRemoveSaveDataRoot();
|
int PS4_SYSV_ABI sceSaveDataDebugRemoveSaveDataRoot();
|
||||||
int PS4_SYSV_ABI sceSaveDataDebugTarget();
|
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 sceSaveDataDelete5();
|
||||||
int PS4_SYSV_ABI sceSaveDataDeleteAllUser();
|
int PS4_SYSV_ABI sceSaveDataDeleteAllUser();
|
||||||
int PS4_SYSV_ABI sceSaveDataDeleteCloudData();
|
int PS4_SYSV_ABI sceSaveDataDeleteCloudData();
|
||||||
int PS4_SYSV_ABI sceSaveDataDeleteUser();
|
int PS4_SYSV_ABI sceSaveDataDeleteUser();
|
||||||
int PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond,
|
Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond,
|
||||||
OrbisSaveDataDirNameSearchResult* result);
|
OrbisSaveDataDirNameSearchResult* result);
|
||||||
int PS4_SYSV_ABI sceSaveDataDirNameSearchInternal();
|
int PS4_SYSV_ABI sceSaveDataDirNameSearchInternal();
|
||||||
int PS4_SYSV_ABI sceSaveDataDownload();
|
int PS4_SYSV_ABI sceSaveDataDownload();
|
||||||
int PS4_SYSV_ABI sceSaveDataGetAllSize();
|
int PS4_SYSV_ABI sceSaveDataGetAllSize();
|
||||||
|
@ -292,70 +114,70 @@ int PS4_SYSV_ABI sceSaveDataGetClientThreadPriority();
|
||||||
int PS4_SYSV_ABI sceSaveDataGetCloudQuotaInfo();
|
int PS4_SYSV_ABI sceSaveDataGetCloudQuotaInfo();
|
||||||
int PS4_SYSV_ABI sceSaveDataGetDataBaseFilePath();
|
int PS4_SYSV_ABI sceSaveDataGetDataBaseFilePath();
|
||||||
int PS4_SYSV_ABI sceSaveDataGetEventInfo();
|
int PS4_SYSV_ABI sceSaveDataGetEventInfo();
|
||||||
int PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam* eventParam,
|
Error PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam* eventParam,
|
||||||
OrbisSaveDataEvent* event);
|
OrbisSaveDataEvent* event);
|
||||||
int PS4_SYSV_ABI sceSaveDataGetFormat();
|
int PS4_SYSV_ABI sceSaveDataGetFormat();
|
||||||
int PS4_SYSV_ABI sceSaveDataGetMountedSaveDataCount();
|
int PS4_SYSV_ABI sceSaveDataGetMountedSaveDataCount();
|
||||||
int PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint,
|
Error PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint,
|
||||||
OrbisSaveDataMountInfo* info);
|
OrbisSaveDataMountInfo* info);
|
||||||
int PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint,
|
Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint,
|
||||||
const OrbisSaveDataParamType paramType, void* paramBuf,
|
OrbisSaveDataParamType paramType, void* paramBuf,
|
||||||
const size_t paramBufSize, size_t* gotSize);
|
size_t paramBufSize, size_t* gotSize);
|
||||||
int PS4_SYSV_ABI sceSaveDataGetProgress();
|
Error PS4_SYSV_ABI sceSaveDataGetProgress(float* progress);
|
||||||
int PS4_SYSV_ABI sceSaveDataGetSaveDataCount();
|
int PS4_SYSV_ABI sceSaveDataGetSaveDataCount();
|
||||||
int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const u32 userId, void* buf, const size_t bufSize,
|
Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(OrbisUserServiceUserId userId, void* buf,
|
||||||
const int64_t offset);
|
size_t bufSize, int64_t offset);
|
||||||
int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam);
|
Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam);
|
||||||
int PS4_SYSV_ABI sceSaveDataGetSaveDataRootDir();
|
int PS4_SYSV_ABI sceSaveDataGetSaveDataRootDir();
|
||||||
int PS4_SYSV_ABI sceSaveDataGetSaveDataRootPath();
|
int PS4_SYSV_ABI sceSaveDataGetSaveDataRootPath();
|
||||||
int PS4_SYSV_ABI sceSaveDataGetSaveDataRootUsbPath();
|
int PS4_SYSV_ABI sceSaveDataGetSaveDataRootUsbPath();
|
||||||
int PS4_SYSV_ABI sceSaveDataGetSavePoint();
|
int PS4_SYSV_ABI sceSaveDataGetSavePoint();
|
||||||
int PS4_SYSV_ABI sceSaveDataGetUpdatedDataCount();
|
int PS4_SYSV_ABI sceSaveDataGetUpdatedDataCount();
|
||||||
int PS4_SYSV_ABI sceSaveDataInitialize();
|
Error PS4_SYSV_ABI sceSaveDataInitialize(void*);
|
||||||
int PS4_SYSV_ABI sceSaveDataInitialize2();
|
Error PS4_SYSV_ABI sceSaveDataInitialize2(void*);
|
||||||
int PS4_SYSV_ABI sceSaveDataInitialize3();
|
Error PS4_SYSV_ABI sceSaveDataInitialize3(void*);
|
||||||
int PS4_SYSV_ABI sceSaveDataInitializeForCdlg();
|
int PS4_SYSV_ABI sceSaveDataInitializeForCdlg();
|
||||||
int PS4_SYSV_ABI sceSaveDataIsDeletingUsbDb();
|
int PS4_SYSV_ABI sceSaveDataIsDeletingUsbDb();
|
||||||
int PS4_SYSV_ABI sceSaveDataIsMounted();
|
int PS4_SYSV_ABI sceSaveDataIsMounted();
|
||||||
int PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint,
|
Error PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint,
|
||||||
OrbisSaveDataIcon* icon);
|
OrbisSaveDataIcon* icon);
|
||||||
int PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount,
|
Error PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount,
|
||||||
OrbisSaveDataMountResult* mount_result);
|
OrbisSaveDataMountResult* mount_result);
|
||||||
s32 PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount,
|
Error PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount,
|
||||||
OrbisSaveDataMountResult* mount_result);
|
OrbisSaveDataMountResult* mount_result);
|
||||||
int PS4_SYSV_ABI sceSaveDataMount5();
|
int PS4_SYSV_ABI sceSaveDataMount5();
|
||||||
int PS4_SYSV_ABI sceSaveDataMountInternal();
|
int PS4_SYSV_ABI sceSaveDataMountInternal();
|
||||||
int PS4_SYSV_ABI sceSaveDataMountSys();
|
int PS4_SYSV_ABI sceSaveDataMountSys();
|
||||||
int PS4_SYSV_ABI sceSaveDataPromote5();
|
int PS4_SYSV_ABI sceSaveDataPromote5();
|
||||||
int PS4_SYSV_ABI sceSaveDataRebuildDatabase();
|
int PS4_SYSV_ABI sceSaveDataRebuildDatabase();
|
||||||
int PS4_SYSV_ABI sceSaveDataRegisterEventCallback();
|
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 sceSaveDataRestoreBackupDataForCdlg();
|
||||||
int PS4_SYSV_ABI sceSaveDataRestoreLoadSaveDataMemory();
|
int PS4_SYSV_ABI sceSaveDataRestoreLoadSaveDataMemory();
|
||||||
int PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint,
|
Error PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint,
|
||||||
const OrbisSaveDataIcon* icon);
|
const OrbisSaveDataIcon* icon);
|
||||||
int PS4_SYSV_ABI sceSaveDataSetAutoUploadSetting();
|
int PS4_SYSV_ABI sceSaveDataSetAutoUploadSetting();
|
||||||
int PS4_SYSV_ABI sceSaveDataSetEventInfo();
|
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,
|
OrbisSaveDataParamType paramType, const void* paramBuf,
|
||||||
size_t paramBufSize);
|
size_t paramBufSize);
|
||||||
int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser();
|
int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser();
|
||||||
int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(const u32 userId, const void* buf,
|
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf,
|
||||||
const size_t bufSize, const int64_t offset);
|
size_t bufSize, int64_t offset);
|
||||||
int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam);
|
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam);
|
||||||
int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(u32 userId, size_t memorySize,
|
int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(/*u32 userId, size_t memorySize,
|
||||||
OrbisSaveDataParam* param);
|
OrbisSaveDataParam* param*/);
|
||||||
int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam,
|
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam,
|
||||||
OrbisSaveDataMemorySetupResult* result);
|
OrbisSaveDataMemorySetupResult* result);
|
||||||
int PS4_SYSV_ABI sceSaveDataShutdownStart();
|
int PS4_SYSV_ABI sceSaveDataShutdownStart();
|
||||||
int PS4_SYSV_ABI sceSaveDataSupportedFakeBrokenStatus();
|
int PS4_SYSV_ABI sceSaveDataSupportedFakeBrokenStatus();
|
||||||
int PS4_SYSV_ABI sceSaveDataSyncCloudList();
|
int PS4_SYSV_ABI sceSaveDataSyncCloudList();
|
||||||
int PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam);
|
Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam);
|
||||||
int PS4_SYSV_ABI sceSaveDataTerminate();
|
Error PS4_SYSV_ABI sceSaveDataTerminate();
|
||||||
int PS4_SYSV_ABI sceSaveDataTransferringMount();
|
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 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 sceSaveDataUnregisterEventCallback();
|
||||||
int PS4_SYSV_ABI sceSaveDataUpload();
|
int PS4_SYSV_ABI sceSaveDataUpload();
|
||||||
int PS4_SYSV_ABI Func_02E4C4D201716422();
|
int PS4_SYSV_ABI Func_02E4C4D201716422();
|
||||||
|
|
|
@ -39,11 +39,6 @@ Error PS4_SYSV_ABI sceMsgDialogGetResult(DialogResult* result) {
|
||||||
if (result == nullptr) {
|
if (result == nullptr) {
|
||||||
return Error::ARG_NULL;
|
return Error::ARG_NULL;
|
||||||
}
|
}
|
||||||
for (const auto v : result->reserved) {
|
|
||||||
if (v != 0) {
|
|
||||||
return Error::PARAM_INVALID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*result = g_result;
|
*result = g_result;
|
||||||
return Error::OK;
|
return Error::OK;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "imgui/imgui_std.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_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) {
|
MsgDialogState::MsgDialogState(const OrbisParam& param) {
|
||||||
this->mode = param.mode;
|
this->mode = param.mode;
|
||||||
switch (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() {
|
void MsgDialogUi::DrawUser() {
|
||||||
const auto& [button_type, msg, btn_param1, btn_param2] =
|
const auto& [button_type, msg, btn_param1, btn_param2] =
|
||||||
state->GetState<MsgDialogState::UserState>();
|
state->GetState<MsgDialogState::UserState>();
|
||||||
const auto ws = GetWindowSize();
|
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);
|
ASSERT(button_type <= ButtonType::TWO_BUTTONS);
|
||||||
auto [count, text1, text2] = user_button_texts[static_cast<u32>(button_type)];
|
auto [count, text1, text2] = user_button_texts[static_cast<u32>(button_type)];
|
||||||
if (count == 0xFF) { // TWO_BUTTONS -> User defined message
|
if (count == 0xFF) { // TWO_BUTTONS -> User defined message
|
||||||
|
@ -115,7 +123,7 @@ void MsgDialogUi::DrawUser() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (first_render && !focus_first) {
|
if ((first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) && !focus_first) {
|
||||||
SetItemCurrentNavFocus();
|
SetItemCurrentNavFocus();
|
||||||
}
|
}
|
||||||
PopID();
|
PopID();
|
||||||
|
@ -125,7 +133,7 @@ void MsgDialogUi::DrawUser() {
|
||||||
if (Button(text1, BUTTON_SIZE)) {
|
if (Button(text1, BUTTON_SIZE)) {
|
||||||
Finish(ButtonId::BUTTON1);
|
Finish(ButtonId::BUTTON1);
|
||||||
}
|
}
|
||||||
if (first_render && focus_first) {
|
if ((first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) && focus_first) {
|
||||||
SetItemCurrentNavFocus();
|
SetItemCurrentNavFocus();
|
||||||
}
|
}
|
||||||
PopID();
|
PopID();
|
||||||
|
@ -249,11 +257,13 @@ void MsgDialogUi::Draw() {
|
||||||
|
|
||||||
CentralizeWindow();
|
CentralizeWindow();
|
||||||
SetNextWindowSize(window_size);
|
SetNextWindowSize(window_size);
|
||||||
SetNextWindowFocus();
|
|
||||||
SetNextWindowCollapsed(false);
|
SetNextWindowCollapsed(false);
|
||||||
|
if (first_render || !io.NavActive) {
|
||||||
|
SetNextWindowFocus();
|
||||||
|
}
|
||||||
KeepNavHighlight();
|
KeepNavHighlight();
|
||||||
// Hack to allow every dialog to have a unique window
|
if (Begin("Message Dialog##MessageDialog", nullptr,
|
||||||
if (Begin("Message Dialog##MessageDialog", nullptr, ImGuiWindowFlags_NoSavedSettings)) {
|
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) {
|
||||||
switch (state->GetMode()) {
|
switch (state->GetMode()) {
|
||||||
case MsgDialogMode::USER_MSG:
|
case MsgDialogMode::USER_MSG:
|
||||||
DrawUser();
|
DrawUser();
|
||||||
|
@ -269,4 +279,16 @@ void MsgDialogUi::Draw() {
|
||||||
End();
|
End();
|
||||||
|
|
||||||
first_render = false;
|
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
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
#include "common/fixed_value.h"
|
#include "common/fixed_value.h"
|
||||||
|
@ -129,6 +130,11 @@ private:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MsgDialogState(const OrbisParam& param);
|
explicit MsgDialogState(const OrbisParam& param);
|
||||||
|
|
||||||
|
explicit MsgDialogState(UserState mode);
|
||||||
|
explicit MsgDialogState(ProgressState mode);
|
||||||
|
explicit MsgDialogState(SystemState mode);
|
||||||
|
|
||||||
MsgDialogState() = default;
|
MsgDialogState() = default;
|
||||||
|
|
||||||
[[nodiscard]] OrbisUserServiceUserId GetUserId() const {
|
[[nodiscard]] OrbisUserServiceUserId GetUserId() const {
|
||||||
|
@ -165,13 +171,11 @@ public:
|
||||||
|
|
||||||
void Finish(ButtonId buttonId, CommonDialog::Result r = CommonDialog::Result::OK);
|
void Finish(ButtonId buttonId, CommonDialog::Result r = CommonDialog::Result::OK);
|
||||||
|
|
||||||
void SetProgressBarValue(u32 value, bool increment);
|
|
||||||
|
|
||||||
void Draw() override;
|
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
|
}; // 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
|
#ifdef ENABLE_QT_GUI
|
||||||
#include "common/memory_patcher.h"
|
#include "common/memory_patcher.h"
|
||||||
#endif
|
#endif
|
||||||
|
#include "common/assert.h"
|
||||||
#include "common/ntapi.h"
|
#include "common/ntapi.h"
|
||||||
#include "common/path_util.h"
|
#include "common/path_util.h"
|
||||||
#include "common/polyfill_thread.h"
|
#include "common/polyfill_thread.h"
|
||||||
|
@ -99,8 +100,9 @@ void Emulator::Run(const std::filesystem::path& file) {
|
||||||
for (const auto& entry : std::filesystem::directory_iterator(sce_sys_folder)) {
|
for (const auto& entry : std::filesystem::directory_iterator(sce_sys_folder)) {
|
||||||
if (entry.path().filename() == "param.sfo") {
|
if (entry.path().filename() == "param.sfo") {
|
||||||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
||||||
param_sfo->open(sce_sys_folder.string() + "/param.sfo", {});
|
const bool success = param_sfo->Open(sce_sys_folder / "param.sfo");
|
||||||
id = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9);
|
ASSERT_MSG(success, "Failed to open param.sfo");
|
||||||
|
id = std::string(*param_sfo->GetString("CONTENT_ID"), 7, 9);
|
||||||
Libraries::NpTrophy::game_serial = id;
|
Libraries::NpTrophy::game_serial = id;
|
||||||
const auto trophyDir =
|
const auto trophyDir =
|
||||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / id / "TrophyFiles";
|
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
|
#ifdef ENABLE_QT_GUI
|
||||||
MemoryPatcher::g_game_serial = id;
|
MemoryPatcher::g_game_serial = id;
|
||||||
#endif
|
#endif
|
||||||
title = param_sfo->GetString("TITLE");
|
title = *param_sfo->GetString("TITLE");
|
||||||
LOG_INFO(Loader, "Game id: {} Title: {}", id, title);
|
LOG_INFO(Loader, "Game id: {} Title: {}", id, title);
|
||||||
u32 fw_version = param_sfo->GetInteger("SYSTEM_VER");
|
u32 fw_version = param_sfo->GetInteger("SYSTEM_VER").value_or(0x4700000);
|
||||||
app_version = param_sfo->GetString("APP_VER");
|
app_version = *param_sfo->GetString("APP_VER");
|
||||||
LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version);
|
LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version);
|
||||||
} else if (entry.path().filename() == "playgo-chunk.dat") {
|
} else if (entry.path().filename() == "playgo-chunk.dat") {
|
||||||
auto* playgo = Common::Singleton<PlaygoFile>::Instance();
|
auto* playgo = Common::Singleton<PlaygoFile>::Instance();
|
||||||
|
|
|
@ -26,4 +26,7 @@ extern void assert_fail_debug_msg(const char* msg);
|
||||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||||
|
|
||||||
#define IM_VEC2_CLASS_EXTRA \
|
#define IM_VEC2_CLASS_EXTRA \
|
||||||
constexpr ImVec2(float _v) : x(_v), y(_v) {}
|
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);
|
static void RemoveLayer(Layer* layer);
|
||||||
|
|
||||||
virtual void Draw() = 0;
|
virtual void Draw() = 0;
|
||||||
|
|
||||||
virtual bool ShouldGrabGamepad() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ImGui
|
} // namespace ImGui
|
|
@ -3,12 +3,25 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
#include "imgui_internal.h"
|
#include "imgui_internal.h"
|
||||||
|
|
||||||
|
#define IM_COL32_GRAY(x) IM_COL32(x, x, x, 0xFF)
|
||||||
|
|
||||||
namespace ImGui {
|
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() {
|
inline void CentralizeWindow() {
|
||||||
const auto display_size = GetIO().DisplaySize;
|
const auto display_size = GetIO().DisplaySize;
|
||||||
SetNextWindowPos(display_size / 2.0f, ImGuiCond_Always, {0.5f});
|
SetNextWindowPos(display_size / 2.0f, ImGuiCond_Always, {0.5f});
|
||||||
|
@ -18,10 +31,39 @@ inline void KeepNavHighlight() {
|
||||||
GetCurrentContext()->NavDisableHighlight = false;
|
GetCurrentContext()->NavDisableHighlight = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void SetItemCurrentNavFocus() {
|
inline void SetItemCurrentNavFocus(const ImGuiID id = -1) {
|
||||||
const auto ctx = GetCurrentContext();
|
const auto ctx = GetCurrentContext();
|
||||||
SetFocusID(ctx->LastItemData.ID, ctx->CurrentWindow);
|
SetFocusID(id == -1 ? ctx->LastItemData.ID : id, ctx->CurrentWindow);
|
||||||
ctx->NavInitResult.Clear();
|
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
|
} // 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;
|
m_show = IsKeyPressed(ImGuiKey_F10, false) ^ m_show;
|
||||||
|
|
||||||
if (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);
|
Text("Frame time: %.3f ms (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
|
||||||
}
|
}
|
||||||
End();
|
End();
|
||||||
|
|
|
@ -9,7 +9,9 @@
|
||||||
#include "imgui_core.h"
|
#include "imgui_core.h"
|
||||||
#include "imgui_impl_sdl3.h"
|
#include "imgui_impl_sdl3.h"
|
||||||
#include "imgui_impl_vulkan.h"
|
#include "imgui_impl_vulkan.h"
|
||||||
|
#include "imgui_internal.h"
|
||||||
#include "sdl_window.h"
|
#include "sdl_window.h"
|
||||||
|
#include "texture_manager.h"
|
||||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||||
|
|
||||||
static void CheckVkResult(const vk::Result err) {
|
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,
|
.check_vk_result_fn = &CheckVkResult,
|
||||||
};
|
};
|
||||||
Vulkan::Init(vk_info);
|
Vulkan::Init(vk_info);
|
||||||
|
|
||||||
|
TextureManager::StartWorker();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnResize() {
|
void OnResize() {
|
||||||
|
@ -77,6 +81,8 @@ void OnResize() {
|
||||||
void Shutdown(const vk::Device& device) {
|
void Shutdown(const vk::Device& device) {
|
||||||
device.waitIdle();
|
device.waitIdle();
|
||||||
|
|
||||||
|
TextureManager::StopWorker();
|
||||||
|
|
||||||
const ImGuiIO& io = GetIO();
|
const ImGuiIO& io = GetIO();
|
||||||
const auto ini_filename = (void*)io.IniFilename;
|
const auto ini_filename = (void*)io.IniFilename;
|
||||||
const auto log_filename = (void*)io.LogFilename;
|
const auto log_filename = (void*)io.LogFilename;
|
||||||
|
@ -92,24 +98,19 @@ void Shutdown(const vk::Device& device) {
|
||||||
bool ProcessEvent(SDL_Event* event) {
|
bool ProcessEvent(SDL_Event* event) {
|
||||||
Sdl::ProcessEvent(event);
|
Sdl::ProcessEvent(event);
|
||||||
switch (event->type) {
|
switch (event->type) {
|
||||||
|
// Don't block release/up events
|
||||||
case SDL_EVENT_MOUSE_MOTION:
|
case SDL_EVENT_MOUSE_MOTION:
|
||||||
case SDL_EVENT_MOUSE_WHEEL:
|
case SDL_EVENT_MOUSE_WHEEL:
|
||||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||||
case SDL_EVENT_MOUSE_BUTTON_UP:
|
|
||||||
return GetIO().WantCaptureMouse;
|
return GetIO().WantCaptureMouse;
|
||||||
case SDL_EVENT_TEXT_INPUT:
|
case SDL_EVENT_TEXT_INPUT:
|
||||||
case SDL_EVENT_KEY_DOWN:
|
case SDL_EVENT_KEY_DOWN:
|
||||||
case SDL_EVENT_KEY_UP:
|
|
||||||
return GetIO().WantCaptureKeyboard;
|
return GetIO().WantCaptureKeyboard;
|
||||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
||||||
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
|
||||||
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
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_DOWN:
|
||||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
|
|
||||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
|
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
|
||||||
return (GetIO().BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
|
return GetIO().NavActive;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -130,21 +131,11 @@ void NewFrame() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Vulkan::NewFrame();
|
|
||||||
Sdl::NewFrame();
|
Sdl::NewFrame();
|
||||||
ImGui::NewFrame();
|
ImGui::NewFrame();
|
||||||
|
|
||||||
bool capture_gamepad = false;
|
|
||||||
for (auto* layer : layers) {
|
for (auto* layer : layers) {
|
||||||
layer->Draw();
|
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
|
// Based on imgui_impl_vulkan.cpp from Dear ImGui repository
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
#include "imgui_impl_vulkan.h"
|
#include "imgui_impl_vulkan.h"
|
||||||
|
@ -47,13 +49,15 @@ struct VkData {
|
||||||
vk::ShaderModule shader_module_vert{};
|
vk::ShaderModule shader_module_vert{};
|
||||||
vk::ShaderModule shader_module_frag{};
|
vk::ShaderModule shader_module_frag{};
|
||||||
|
|
||||||
|
std::mutex command_pool_mutex;
|
||||||
|
vk::CommandPool command_pool{};
|
||||||
|
vk::Sampler simple_sampler{};
|
||||||
|
|
||||||
// Font data
|
// Font data
|
||||||
vk::Sampler font_sampler{};
|
|
||||||
vk::DeviceMemory font_memory{};
|
vk::DeviceMemory font_memory{};
|
||||||
vk::Image font_image{};
|
vk::Image font_image{};
|
||||||
vk::ImageView font_view{};
|
vk::ImageView font_view{};
|
||||||
vk::DescriptorSet font_descriptor_set{};
|
vk::DescriptorSet font_descriptor_set{};
|
||||||
vk::CommandPool font_command_pool{};
|
|
||||||
vk::CommandBuffer font_command_buffer{};
|
vk::CommandBuffer font_command_buffer{};
|
||||||
|
|
||||||
// Render buffers
|
// Render buffers
|
||||||
|
@ -222,12 +226,53 @@ static inline vk::DeviceSize AlignBufferSize(vk::DeviceSize size, vk::DeviceSize
|
||||||
return (size + alignment - 1) & ~(alignment - 1);
|
return (size + alignment - 1) & ~(alignment - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register a texture
|
void UploadTextureData::Upload() {
|
||||||
vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view,
|
|
||||||
vk::ImageLayout image_layout) {
|
|
||||||
VkData* bd = GetBackendData();
|
VkData* bd = GetBackendData();
|
||||||
const InitInfo& v = bd->init_info;
|
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:
|
// Create Descriptor Set:
|
||||||
vk::DescriptorSet descriptor_set;
|
vk::DescriptorSet descriptor_set;
|
||||||
{
|
{
|
||||||
|
@ -262,6 +307,166 @@ vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view,
|
||||||
}
|
}
|
||||||
return descriptor_set;
|
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) {
|
void RemoveTexture(vk::DescriptorSet descriptor_set) {
|
||||||
VkData* bd = GetBackendData();
|
VkData* bd = GetBackendData();
|
||||||
|
@ -517,27 +722,20 @@ static bool CreateFontsTexture() {
|
||||||
DestroyFontsTexture();
|
DestroyFontsTexture();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create command pool/buffer
|
// Create command 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));
|
|
||||||
}
|
|
||||||
if (bd->font_command_buffer == VK_NULL_HANDLE) {
|
if (bd->font_command_buffer == VK_NULL_HANDLE) {
|
||||||
vk::CommandBufferAllocateInfo info{
|
vk::CommandBufferAllocateInfo info{
|
||||||
.sType = vk::StructureType::eCommandBufferAllocateInfo,
|
.sType = vk::StructureType::eCommandBufferAllocateInfo,
|
||||||
.commandPool = bd->font_command_pool,
|
.commandPool = bd->command_pool,
|
||||||
.commandBufferCount = 1,
|
.commandBufferCount = 1,
|
||||||
};
|
};
|
||||||
|
std::unique_lock lk(bd->command_pool_mutex);
|
||||||
bd->font_command_buffer = CheckVkResult(v.device.allocateCommandBuffers(info)).front();
|
bd->font_command_buffer = CheckVkResult(v.device.allocateCommandBuffers(info)).front();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start command buffer
|
// Start command buffer
|
||||||
{
|
{
|
||||||
CheckVkErr(v.device.resetCommandPool(bd->font_command_pool, vk::CommandPoolResetFlags{}));
|
CheckVkErr(bd->font_command_buffer.reset());
|
||||||
vk::CommandBufferBeginInfo begin_info{};
|
vk::CommandBufferBeginInfo begin_info{};
|
||||||
begin_info.sType = vk::StructureType::eCommandBufferBeginInfo;
|
begin_info.sType = vk::StructureType::eCommandBufferBeginInfo;
|
||||||
begin_info.flags |= vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
|
begin_info.flags |= vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
|
||||||
|
@ -597,8 +795,7 @@ static bool CreateFontsTexture() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the Descriptor Set:
|
// Create the Descriptor Set:
|
||||||
bd->font_descriptor_set =
|
bd->font_descriptor_set = AddTexture(bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||||
AddTexture(bd->font_sampler, bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal);
|
|
||||||
|
|
||||||
// Create the Upload Buffer:
|
// Create the Upload Buffer:
|
||||||
vk::DeviceMemory upload_buffer_memory{};
|
vk::DeviceMemory upload_buffer_memory{};
|
||||||
|
@ -956,25 +1153,6 @@ bool CreateDeviceObjects() {
|
||||||
bd->descriptor_pool = CheckVkResult(v.device.createDescriptorPool(pool_info));
|
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) {
|
if (!bd->descriptor_set_layout) {
|
||||||
vk::DescriptorSetLayoutBinding binding[1]{
|
vk::DescriptorSetLayoutBinding binding[1]{
|
||||||
{
|
{
|
||||||
|
@ -1016,6 +1194,35 @@ bool CreateDeviceObjects() {
|
||||||
|
|
||||||
CreatePipeline(v.device, v.allocator, v.pipeline_cache, nullptr, &bd->pipeline, v.subpass);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1026,12 +1233,14 @@ void ImGuiImplVulkanDestroyDeviceObjects() {
|
||||||
DestroyFontsTexture();
|
DestroyFontsTexture();
|
||||||
|
|
||||||
if (bd->font_command_buffer) {
|
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;
|
bd->font_command_buffer = VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
if (bd->font_command_pool) {
|
if (bd->command_pool) {
|
||||||
v.device.destroyCommandPool(bd->font_command_pool, v.allocator);
|
std::unique_lock lk(bd->command_pool_mutex);
|
||||||
bd->font_command_pool = VK_NULL_HANDLE;
|
v.device.destroyCommandPool(bd->command_pool, v.allocator);
|
||||||
|
bd->command_pool = VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
if (bd->shader_module_vert) {
|
if (bd->shader_module_vert) {
|
||||||
v.device.destroyShaderModule(bd->shader_module_vert, v.allocator);
|
v.device.destroyShaderModule(bd->shader_module_vert, v.allocator);
|
||||||
|
@ -1041,9 +1250,9 @@ void ImGuiImplVulkanDestroyDeviceObjects() {
|
||||||
v.device.destroyShaderModule(bd->shader_module_frag, v.allocator);
|
v.device.destroyShaderModule(bd->shader_module_frag, v.allocator);
|
||||||
bd->shader_module_frag = VK_NULL_HANDLE;
|
bd->shader_module_frag = VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
if (bd->font_sampler) {
|
if (bd->simple_sampler) {
|
||||||
v.device.destroySampler(bd->font_sampler, v.allocator);
|
v.device.destroySampler(bd->simple_sampler, v.allocator);
|
||||||
bd->font_sampler = VK_NULL_HANDLE;
|
bd->simple_sampler = VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
if (bd->descriptor_set_layout) {
|
if (bd->descriptor_set_layout) {
|
||||||
v.device.destroyDescriptorSetLayout(bd->descriptor_set_layout, v.allocator);
|
v.device.destroyDescriptorSetLayout(bd->descriptor_set_layout, v.allocator);
|
||||||
|
@ -1095,13 +1304,4 @@ void Shutdown() {
|
||||||
IM_DELETE(bd);
|
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
|
} // namespace ImGui::Vulkan
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define VULKAN_HPP_NO_EXCEPTIONS
|
#define VULKAN_HPP_NO_EXCEPTIONS
|
||||||
|
#include "common/types.h"
|
||||||
#include "video_core/renderer_vulkan/vk_common.h"
|
#include "video_core/renderer_vulkan/vk_common.h"
|
||||||
|
|
||||||
struct ImDrawData;
|
struct ImDrawData;
|
||||||
|
@ -29,14 +30,33 @@ struct InitInfo {
|
||||||
void (*check_vk_result_fn)(vk::Result err);
|
void (*check_vk_result_fn)(vk::Result err);
|
||||||
};
|
};
|
||||||
|
|
||||||
vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view,
|
// Prepare all resources needed for uploading textures
|
||||||
vk::ImageLayout image_layout);
|
// 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);
|
void RemoveTexture(vk::DescriptorSet descriptor_set);
|
||||||
|
|
||||||
bool Init(InitInfo info);
|
bool Init(InitInfo info);
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
void NewFrame();
|
|
||||||
void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer,
|
void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer,
|
||||||
vk::Pipeline pipeline = VK_NULL_HANDLE);
|
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;
|
game.path = filePath;
|
||||||
|
|
||||||
PSF psf;
|
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";
|
game.icon_path = game.path + "/sce_sys/icon0.png";
|
||||||
QString iconpath = QString::fromStdString(game.icon_path);
|
QString iconpath = QString::fromStdString(game.icon_path);
|
||||||
game.icon = QImage(iconpath);
|
game.icon = QImage(iconpath);
|
||||||
game.pic_path = game.path + "/sce_sys/pic1.png";
|
game.pic_path = game.path + "/sce_sys/pic1.png";
|
||||||
game.name = psf.GetString("TITLE");
|
game.name = *psf.GetString("TITLE");
|
||||||
game.serial = psf.GetString("TITLE_ID");
|
game.serial = *psf.GetString("TITLE_ID");
|
||||||
game.region = GameListUtils::GetRegion(psf.GetString("CONTENT_ID").at(0)).toStdString();
|
game.region =
|
||||||
u32 fw_int = psf.GetInteger("SYSTEM_VER");
|
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 = QString::number(fw_int, 16);
|
||||||
QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.')
|
QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.')
|
||||||
: fw.left(3).insert(1, '.');
|
: fw.left(3).insert(1, '.');
|
||||||
game.fw = (fw_int == 0) ? "0.00" : fw_.toStdString();
|
game.fw = (fw_int == 0) ? "0.00" : fw_.toStdString();
|
||||||
game.version = psf.GetString("APP_VER");
|
game.version = *psf.GetString("APP_VER");
|
||||||
}
|
}
|
||||||
return game;
|
return game;
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,8 +80,8 @@ public:
|
||||||
|
|
||||||
if (selected == &openSfoViewer) {
|
if (selected == &openSfoViewer) {
|
||||||
PSF psf;
|
PSF psf;
|
||||||
if (psf.open(m_games[itemID].path + "/sce_sys/param.sfo", {})) {
|
if (psf.Open(std::filesystem::path(m_games[itemID].path) / "sce_sys" / "param.sfo")) {
|
||||||
int rows = psf.map_strings.size() + psf.map_integers.size();
|
int rows = psf.GetEntries().size();
|
||||||
QTableWidget* tableWidget = new QTableWidget(rows, 2);
|
QTableWidget* tableWidget = new QTableWidget(rows, 2);
|
||||||
tableWidget->setAttribute(Qt::WA_DeleteOnClose);
|
tableWidget->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
connect(widget->parent(), &QWidget::destroyed, tableWidget,
|
connect(widget->parent(), &QWidget::destroyed, tableWidget,
|
||||||
|
@ -90,23 +90,33 @@ public:
|
||||||
tableWidget->verticalHeader()->setVisible(false); // Hide vertical header
|
tableWidget->verticalHeader()->setVisible(false); // Hide vertical header
|
||||||
int row = 0;
|
int row = 0;
|
||||||
|
|
||||||
for (const auto& pair : psf.map_strings) {
|
for (const auto& entry : psf.GetEntries()) {
|
||||||
QTableWidgetItem* keyItem =
|
QTableWidgetItem* keyItem =
|
||||||
new QTableWidgetItem(QString::fromStdString(pair.first));
|
new QTableWidgetItem(QString::fromStdString(entry.key));
|
||||||
QTableWidgetItem* valueItem =
|
QTableWidgetItem* valueItem;
|
||||||
new QTableWidgetItem(QString::fromStdString(pair.second));
|
switch (entry.param_fmt) {
|
||||||
|
case PSFEntryFmt::Binary: {
|
||||||
|
|
||||||
tableWidget->setItem(row, 0, keyItem);
|
const auto bin = *psf.GetBinary(entry.key);
|
||||||
tableWidget->setItem(row, 1, valueItem);
|
std::string text;
|
||||||
keyItem->setFlags(keyItem->flags() & ~Qt::ItemIsEditable);
|
text.reserve(bin.size() * 2);
|
||||||
valueItem->setFlags(valueItem->flags() & ~Qt::ItemIsEditable);
|
for (const auto& c : bin) {
|
||||||
row++;
|
static constexpr char hex[] = "0123456789ABCDEF";
|
||||||
}
|
text.push_back(hex[c >> 4 & 0xF]);
|
||||||
for (const auto& pair : psf.map_integers) {
|
text.push_back(hex[c & 0xF]);
|
||||||
QTableWidgetItem* keyItem =
|
}
|
||||||
new QTableWidgetItem(QString::fromStdString(pair.first));
|
valueItem = new QTableWidgetItem(QString::fromStdString(text));
|
||||||
QTableWidgetItem* valueItem = new QTableWidgetItem(
|
} break;
|
||||||
QString("0x").append(QString::number(pair.second, 16)));
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
tableWidget->setItem(row, 0, keyItem);
|
tableWidget->setItem(row, 0, keyItem);
|
||||||
tableWidget->setItem(row, 1, valueItem);
|
tableWidget->setItem(row, 1, valueItem);
|
||||||
|
|
|
@ -636,9 +636,9 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
|
||||||
QMessageBox msgBox;
|
QMessageBox msgBox;
|
||||||
msgBox.setWindowTitle(tr("PKG Extraction"));
|
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];
|
std::string entitlement_label = Common::SplitString(content_id, '-')[2];
|
||||||
|
|
||||||
auto addon_extract_path = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir) /
|
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");
|
auto category = psf.GetString("CATEGORY");
|
||||||
|
|
||||||
if (pkgType.contains("PATCH")) {
|
if (pkgType.contains("PATCH")) {
|
||||||
QString pkg_app_version = QString::fromStdString(psf.GetString("APP_VER"));
|
QString pkg_app_version =
|
||||||
psf.open(extract_path.string() + "/sce_sys/param.sfo", {});
|
QString::fromStdString(std::string{*psf.GetString("APP_VER")});
|
||||||
QString game_app_version = QString::fromStdString(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 appD = game_app_version.toDouble();
|
||||||
double pkgD = pkg_app_version.toDouble();
|
double pkgD = pkg_app_version.toDouble();
|
||||||
if (pkgD == appD) {
|
if (pkgD == appD) {
|
||||||
|
|
|
@ -109,12 +109,12 @@ void PKGViewer::ProcessPKGInfo() {
|
||||||
path = std::filesystem::path(m_pkg_list[i].toStdWString());
|
path = std::filesystem::path(m_pkg_list[i].toStdWString());
|
||||||
#endif
|
#endif
|
||||||
package.Open(path);
|
package.Open(path);
|
||||||
psf.open("", package.sfo);
|
psf.Open(package.sfo);
|
||||||
QString title_name = QString::fromStdString(psf.GetString("TITLE"));
|
QString title_name = QString::fromStdString(std::string{*psf.GetString("TITLE")});
|
||||||
QString title_id = QString::fromStdString(psf.GetString("TITLE_ID"));
|
QString title_id = QString::fromStdString(std::string{*psf.GetString("TITLE_ID")});
|
||||||
QString app_type = game_list_util.GetAppType(psf.GetInteger("APP_TYPE"));
|
QString app_type = game_list_util.GetAppType(*psf.GetInteger("APP_TYPE"));
|
||||||
QString app_version = QString::fromStdString(psf.GetString("APP_VER"));
|
QString app_version = QString::fromStdString(std::string{*psf.GetString("APP_VER")});
|
||||||
QString title_category = QString::fromStdString(psf.GetString("CATEGORY"));
|
QString title_category = QString::fromStdString(std::string{*psf.GetString("CATEGORY")});
|
||||||
QString pkg_size = game_list_util.FormatSize(package.GetPkgHeader().pkg_size);
|
QString pkg_size = game_list_util.FormatSize(package.GetPkgHeader().pkg_size);
|
||||||
pkg_content_flag = package.GetPkgHeader().pkg_content_flags;
|
pkg_content_flag = package.GetPkgHeader().pkg_content_flags;
|
||||||
QString flagss = "";
|
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 = QString::number(fw_int, 16);
|
||||||
QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.')
|
QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.')
|
||||||
: fw.left(3).insert(1, '.');
|
: fw.left(3).insert(1, '.');
|
||||||
|
|
|
@ -33,7 +33,6 @@ private:
|
||||||
PKGHeader pkgheader;
|
PKGHeader pkgheader;
|
||||||
PKGEntry entry;
|
PKGEntry entry;
|
||||||
PSFHeader header;
|
PSFHeader header;
|
||||||
PSFEntry psfentry;
|
|
||||||
char pkgTitleID[9];
|
char pkgTitleID[9];
|
||||||
std::vector<u8> pkg;
|
std::vector<u8> pkg;
|
||||||
u64 pkgSize = 0;
|
u64 pkgSize = 0;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/debug.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_instance.h"
|
||||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||||
|
|
||||||
|
@ -190,6 +191,7 @@ void Scheduler::SubmitExecution(SubmitInfo& info) {
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
ImGui::Core::TextureManager::Submit();
|
||||||
instance.GetGraphicsQueue().submit(submit_info, info.fence);
|
instance.GetGraphicsQueue().submit(submit_info, info.fence);
|
||||||
} catch (vk::DeviceLostError& err) {
|
} catch (vk::DeviceLostError& err) {
|
||||||
UNREACHABLE_MSG("Device lost during submit: {}", err.what());
|
UNREACHABLE_MSG("Device lost during submit: {}", err.what());
|
||||||
|
|
Loading…
Reference in a new issue