Merge branch 'main' into fix-issue-1684

This commit is contained in:
pdaloxd 2025-01-30 12:08:35 +01:00 committed by Pablo Santana
commit 3ba53b42a4
36 changed files with 801 additions and 493 deletions

View File

@ -440,6 +440,11 @@ set(NP_LIBS src/core/libraries/np_common/np_common.cpp
src/core/libraries/np_party/np_party.h src/core/libraries/np_party/np_party.h
) )
set(ZLIB_LIB src/core/libraries/zlib/zlib.cpp
src/core/libraries/zlib/zlib_sce.h
src/core/libraries/zlib/zlib_error.h
)
set(MISC_LIBS src/core/libraries/screenshot/screenshot.cpp set(MISC_LIBS src/core/libraries/screenshot/screenshot.cpp
src/core/libraries/screenshot/screenshot.h src/core/libraries/screenshot/screenshot.h
src/core/libraries/move/move.cpp src/core/libraries/move/move.cpp
@ -612,6 +617,7 @@ set(CORE src/core/aerolib/stubs.cpp
${PLAYGO_LIB} ${PLAYGO_LIB}
${RANDOM_LIB} ${RANDOM_LIB}
${USBD_LIB} ${USBD_LIB}
${ZLIB_LIB}
${MISC_LIBS} ${MISC_LIBS}
${IME_LIB} ${IME_LIB}
${FIBER_LIB} ${FIBER_LIB}

View File

@ -76,6 +76,9 @@ For more information on how to test, debug and report issues with the emulator o
# Keyboard mapping # Keyboard mapping
> [!NOTE]
> Some keyboards may also require you to hold the Fn key to use the F\* keys. Mac users should use the Command key instead of Control, and need to use Command+F11 for full screen to avoid conflicting with system key bindings.
| Button | Function | | Button | Function |
|-------------|-------------| |-------------|-------------|
F10 | FPS Counter F10 | FPS Counter
@ -86,32 +89,32 @@ F12 | Trigger RenderDoc Capture
> [!NOTE] > [!NOTE]
> Xbox and DualShock controllers work out of the box. > Xbox and DualShock controllers work out of the box.
| Controller button | Keyboard equivelant | Mac alternative | | Controller button | Keyboard equivalent |
|-------------|-------------|--------------| |-------------|-------------|
LEFT AXIS UP | W | | LEFT AXIS UP | W |
LEFT AXIS DOWN | S | | LEFT AXIS DOWN | S |
LEFT AXIS LEFT | A | | LEFT AXIS LEFT | A |
LEFT AXIS RIGHT | D | | LEFT AXIS RIGHT | D |
RIGHT AXIS UP | I | | RIGHT AXIS UP | I |
RIGHT AXIS DOWN | K | | RIGHT AXIS DOWN | K |
RIGHT AXIS LEFT | J | | RIGHT AXIS LEFT | J |
RIGHT AXIS RIGHT | L | | RIGHT AXIS RIGHT | L |
TRIANGLE | Numpad 8 | C | TRIANGLE | Numpad 8 or C |
CIRCLE | Numpad 6 | B | CIRCLE | Numpad 6 or B |
CROSS | Numpad 2 | N | CROSS | Numpad 2 or N |
SQUARE | Numpad 4 | V | SQUARE | Numpad 4 or V |
PAD UP | UP | | PAD UP | UP |
PAD DOWN | DOWN | | PAD DOWN | DOWN |
PAD LEFT | LEFT | | PAD LEFT | LEFT |
PAD RIGHT | RIGHT | | PAD RIGHT | RIGHT |
OPTIONS | RETURN | | OPTIONS | RETURN |
BACK BUTTON / TOUCH PAD | SPACE | | BACK BUTTON / TOUCH PAD | SPACE |
L1 | Q | | L1 | Q |
R1 | U | | R1 | U |
L2 | E | | L2 | E |
R2 | O | | R2 | O |
L3 | X | | L3 | X |
R3 | M | | R3 | M |
# Main team # Main team

@ -1 +1 @@
Subproject commit 6fa077fb8ed8dac4e4cd66b6b1ebd7b4d955a754 Subproject commit 0c090001cb42997031cfe43914340e2639944972

View File

@ -80,6 +80,7 @@ public:
static constexpr u32 FW_40 = 0x4000000; static constexpr u32 FW_40 = 0x4000000;
static constexpr u32 FW_45 = 0x4500000; static constexpr u32 FW_45 = 0x4500000;
static constexpr u32 FW_50 = 0x5000000; static constexpr u32 FW_50 = 0x5000000;
static constexpr u32 FW_55 = 0x5500000;
static constexpr u32 FW_80 = 0x8000000; static constexpr u32 FW_80 = 0x8000000;
static ElfInfo& Instance() { static ElfInfo& Instance() {

View File

@ -133,6 +133,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Lib, Mouse) \ SUB(Lib, Mouse) \
SUB(Lib, WebBrowserDialog) \ SUB(Lib, WebBrowserDialog) \
SUB(Lib, NpParty) \ SUB(Lib, NpParty) \
SUB(Lib, Zlib) \
CLS(Frontend) \ CLS(Frontend) \
CLS(Render) \ CLS(Render) \
SUB(Render, Vulkan) \ SUB(Render, Vulkan) \

View File

@ -100,6 +100,7 @@ enum class Class : u8 {
Lib_Mouse, ///< The LibSceMouse implementation Lib_Mouse, ///< The LibSceMouse implementation
Lib_WebBrowserDialog, ///< The LibSceWebBrowserDialog implementation Lib_WebBrowserDialog, ///< The LibSceWebBrowserDialog implementation
Lib_NpParty, ///< The LibSceNpParty implementation Lib_NpParty, ///< The LibSceNpParty implementation
Lib_Zlib, ///< The LibSceZlib implementation.
Frontend, ///< Emulator UI Frontend, ///< Emulator UI
Render, ///< Video Core Render, ///< Video Core
Render_Vulkan, ///< Vulkan backend Render_Vulkan, ///< Vulkan backend

View File

@ -176,7 +176,8 @@ void SetUserPath(PathType shad_path, const fs::path& new_path) {
UserPaths.insert_or_assign(shad_path, new_path); UserPaths.insert_or_assign(shad_path, new_path);
} }
std::optional<fs::path> FindGameByID(const fs::path& dir, const std::string& game_id, int max_depth) { std::optional<fs::path> FindGameByID(const fs::path& dir, const std::string& game_id,
int max_depth) {
if (max_depth < 0) { if (max_depth < 0) {
return std::nullopt; return std::nullopt;
} }

View File

@ -54,6 +54,7 @@
#include "core/libraries/videodec/videodec2.h" #include "core/libraries/videodec/videodec2.h"
#include "core/libraries/videoout/video_out.h" #include "core/libraries/videoout/video_out.h"
#include "core/libraries/web_browser_dialog/webbrowserdialog.h" #include "core/libraries/web_browser_dialog/webbrowserdialog.h"
#include "core/libraries/zlib/zlib_sce.h"
#include "fiber/fiber.h" #include "fiber/fiber.h"
#include "jpeg/jpegenc.h" #include "jpeg/jpegenc.h"
@ -111,6 +112,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
Libraries::Mouse::RegisterlibSceMouse(sym); Libraries::Mouse::RegisterlibSceMouse(sym);
Libraries::WebBrowserDialog::RegisterlibSceWebBrowserDialog(sym); Libraries::WebBrowserDialog::RegisterlibSceWebBrowserDialog(sym);
Libraries::NpParty::RegisterlibSceNpParty(sym); Libraries::NpParty::RegisterlibSceNpParty(sym);
Libraries::Zlib::RegisterlibSceZlib(sym);
} }
} // namespace Libraries } // namespace Libraries

View File

@ -121,16 +121,18 @@ static void BackupThreadBody() {
std::scoped_lock lk{g_backup_queue_mutex}; std::scoped_lock lk{g_backup_queue_mutex};
g_backup_queue.front().done = true; g_backup_queue.front().done = true;
} }
std::this_thread::sleep_for(std::chrono::seconds(5)); // Don't backup too often
{ {
std::scoped_lock lk{g_backup_queue_mutex}; std::scoped_lock lk{g_backup_queue_mutex};
g_backup_queue.pop_front(); g_backup_queue.pop_front();
if (req.origin != OrbisSaveDataEventType::__DO_NOT_SAVE) {
g_result_queue.push_back(std::move(req)); g_result_queue.push_back(std::move(req));
if (g_result_queue.size() > 20) { if (g_result_queue.size() > 20) {
g_result_queue.pop_front(); g_result_queue.pop_front();
} }
} }
} }
std::this_thread::sleep_for(std::chrono::seconds(5)); // Don't backup too often
}
g_backup_status = WorkerStatus::NotStarted; g_backup_status = WorkerStatus::NotStarted;
} }
@ -141,6 +143,15 @@ void StartThread() {
LOG_DEBUG(Lib_SaveData, "Starting backup thread"); LOG_DEBUG(Lib_SaveData, "Starting backup thread");
g_backup_status = WorkerStatus::Waiting; g_backup_status = WorkerStatus::Waiting;
g_backup_thread = std::jthread{BackupThreadBody}; g_backup_thread = std::jthread{BackupThreadBody};
static std::once_flag flag;
std::call_once(flag, [] {
std::at_quick_exit([] {
StopThread();
while (GetWorkerStatus() != WorkerStatus::NotStarted) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
});
});
} }
void StopThread() { void StopThread() {
@ -148,12 +159,12 @@ void StopThread() {
return; return;
} }
LOG_DEBUG(Lib_SaveData, "Stopping backup thread"); LOG_DEBUG(Lib_SaveData, "Stopping backup thread");
g_backup_status = WorkerStatus::Stopping;
{ {
std::scoped_lock lk{g_backup_queue_mutex}; std::scoped_lock lk{g_backup_queue_mutex};
g_backup_queue.emplace_back(BackupRequest{}); g_backup_queue.emplace_back(BackupRequest{});
} }
g_backup_thread_semaphore.release(); g_backup_thread_semaphore.release();
g_backup_status = WorkerStatus::Stopping;
} }
bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id, bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id,

View File

@ -25,6 +25,8 @@ enum class OrbisSaveDataEventType : u32 {
UMOUNT_BACKUP = 1, UMOUNT_BACKUP = 1,
BACKUP = 2, BACKUP = 2,
SAVE_DATA_MEMORY_SYNC = 3, SAVE_DATA_MEMORY_SYNC = 3,
__DO_NOT_SAVE = 1000000, // This value is only for the backup thread
}; };
struct BackupRequest { struct BackupRequest {

View File

@ -10,6 +10,7 @@
#include "common/path_util.h" #include "common/path_util.h"
#include "common/singleton.h" #include "common/singleton.h"
#include "core/file_sys/fs.h" #include "core/file_sys/fs.h"
#include "save_backup.h"
#include "save_instance.h" #include "save_instance.h"
constexpr auto OrbisSaveDataBlocksMin2 = 96; // 3MiB constexpr auto OrbisSaveDataBlocksMin2 = 96; // 3MiB
@ -45,13 +46,12 @@ static const std::unordered_map<std::string, std::string> default_title = {
namespace Libraries::SaveData { namespace Libraries::SaveData {
std::filesystem::path SaveInstance::MakeTitleSavePath(OrbisUserServiceUserId user_id, fs::path SaveInstance::MakeTitleSavePath(OrbisUserServiceUserId user_id,
std::string_view game_serial) { std::string_view game_serial) {
return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial; return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial;
} }
std::filesystem::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_id, fs::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_id, std::string_view game_serial,
std::string_view game_serial,
std::string_view dir_name) { std::string_view dir_name) {
return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial / dir_name; return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial / dir_name;
} }
@ -65,7 +65,7 @@ uint64_t SaveInstance::GetMaxBlockFromSFO(const PSF& psf) {
return *(uint64_t*)value.data(); return *(uint64_t*)value.data();
} }
std::filesystem::path SaveInstance::GetParamSFOPath(const std::filesystem::path& dir_path) { fs::path SaveInstance::GetParamSFOPath(const fs::path& dir_path) {
return dir_path / sce_sys / "param.sfo"; return dir_path / sce_sys / "param.sfo";
} }
@ -129,7 +129,6 @@ SaveInstance& SaveInstance::operator=(SaveInstance&& other) noexcept {
save_path = std::move(other.save_path); save_path = std::move(other.save_path);
param_sfo_path = std::move(other.param_sfo_path); param_sfo_path = std::move(other.param_sfo_path);
corrupt_file_path = std::move(other.corrupt_file_path); corrupt_file_path = std::move(other.corrupt_file_path);
corrupt_file = std::move(other.corrupt_file);
param_sfo = std::move(other.param_sfo); param_sfo = std::move(other.param_sfo);
mount_point = std::move(other.mount_point); mount_point = std::move(other.mount_point);
max_blocks = other.max_blocks; max_blocks = other.max_blocks;
@ -142,7 +141,8 @@ SaveInstance& SaveInstance::operator=(SaveInstance&& other) noexcept {
return *this; return *this;
} }
void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_corrupt) { void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_corrupt,
bool dont_restore_backup) {
if (mounted) { if (mounted) {
UNREACHABLE_MSG("Save instance is already mounted"); UNREACHABLE_MSG("Save instance is already mounted");
} }
@ -161,25 +161,27 @@ void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_cor
} }
exists = true; exists = true;
} else { } else {
std::optional<fs::filesystem_error> err;
if (!ignore_corrupt && fs::exists(corrupt_file_path)) { if (!ignore_corrupt && fs::exists(corrupt_file_path)) {
throw std::filesystem::filesystem_error( err = fs::filesystem_error("Corrupted save data", corrupt_file_path,
"Corrupted save data", corrupt_file_path, std::make_error_code(std::errc::illegal_byte_sequence));
} else if (!param_sfo.Open(param_sfo_path)) {
err = fs::filesystem_error("Failed to read param.sfo", param_sfo_path,
std::make_error_code(std::errc::illegal_byte_sequence)); std::make_error_code(std::errc::illegal_byte_sequence));
} }
if (!param_sfo.Open(param_sfo_path)) { if (err.has_value()) {
throw std::filesystem::filesystem_error( if (dont_restore_backup) {
"Failed to read param.sfo", param_sfo_path, throw err.value();
std::make_error_code(std::errc::illegal_byte_sequence)); }
if (Backup::Restore(save_path)) {
return SetupAndMount(read_only, copy_icon, ignore_corrupt, true);
}
} }
} }
if (!ignore_corrupt && !read_only) { if (!ignore_corrupt && !read_only) {
int err = corrupt_file.Open(corrupt_file_path, Common::FS::FileAccessMode::Write); Common::FS::IOFile f(corrupt_file_path, Common::FS::FileAccessMode::Write);
if (err != 0) { f.Close();
throw std::filesystem::filesystem_error(
"Failed to open corrupted file", corrupt_file_path,
std::make_error_code(std::errc::illegal_byte_sequence));
}
} }
max_blocks = static_cast<int>(GetMaxBlockFromSFO(param_sfo)); max_blocks = static_cast<int>(GetMaxBlockFromSFO(param_sfo));
@ -197,12 +199,11 @@ void SaveInstance::Umount() {
mounted = false; mounted = false;
const bool ok = param_sfo.Encode(param_sfo_path); const bool ok = param_sfo.Encode(param_sfo_path);
if (!ok) { if (!ok) {
throw std::filesystem::filesystem_error("Failed to write param.sfo", param_sfo_path, throw fs::filesystem_error("Failed to write param.sfo", param_sfo_path,
std::make_error_code(std::errc::permission_denied)); std::make_error_code(std::errc::permission_denied));
} }
param_sfo = PSF(); param_sfo = PSF();
corrupt_file.Close();
fs::remove(corrupt_file_path); fs::remove(corrupt_file_path);
g_mnt->Unmount(save_path, mount_point); g_mnt->Unmount(save_path, mount_point);
} }
@ -216,7 +217,7 @@ void SaveInstance::CreateFiles() {
const bool ok = param_sfo.Encode(param_sfo_path); const bool ok = param_sfo.Encode(param_sfo_path);
if (!ok) { if (!ok) {
throw std::filesystem::filesystem_error("Failed to write param.sfo", param_sfo_path, throw fs::filesystem_error("Failed to write param.sfo", param_sfo_path,
std::make_error_code(std::errc::permission_denied)); std::make_error_code(std::errc::permission_denied));
} }
} }

View File

@ -42,8 +42,6 @@ class SaveInstance {
std::filesystem::path param_sfo_path; std::filesystem::path param_sfo_path;
std::filesystem::path corrupt_file_path; std::filesystem::path corrupt_file_path;
Common::FS::IOFile corrupt_file;
PSF param_sfo; PSF param_sfo;
std::string mount_point; std::string mount_point;
@ -80,7 +78,8 @@ public:
SaveInstance& operator=(const SaveInstance& other) = delete; SaveInstance& operator=(const SaveInstance& other) = delete;
SaveInstance& operator=(SaveInstance&& other) noexcept; SaveInstance& operator=(SaveInstance&& other) noexcept;
void SetupAndMount(bool read_only = false, bool copy_icon = false, bool ignore_corrupt = false); void SetupAndMount(bool read_only = false, bool copy_icon = false, bool ignore_corrupt = false,
bool dont_restore_backup = false);
void Umount(); void Umount();

View File

@ -6,14 +6,16 @@
#include <condition_variable> #include <condition_variable>
#include <filesystem> #include <filesystem>
#include <mutex> #include <mutex>
#include <thread>
#include <utility> #include <utility>
#include <fmt/format.h> #include <fmt/format.h>
#include <core/libraries/system/msgdialog_ui.h> #include <core/libraries/system/msgdialog_ui.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/elf_info.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/polyfill_thread.h" #include "common/path_util.h"
#include "common/singleton.h" #include "common/singleton.h"
#include "common/thread.h" #include "common/thread.h"
#include "core/file_sys/fs.h" #include "core/file_sys/fs.h"
@ -23,265 +25,202 @@ using Common::FS::IOFile;
namespace fs = std::filesystem; namespace fs = std::filesystem;
constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save
constexpr std::string_view DirnameSaveDataMemory = "sce_sdmemory"; constexpr std::string_view StandardDirnameSaveDataMemory = "sce_sdmemory";
constexpr std::string_view FilenameSaveDataMemory = "memory.dat"; constexpr std::string_view FilenameSaveDataMemory = "memory.dat";
constexpr std::string_view IconName = "icon0.png";
constexpr std::string_view CorruptFileName = "corrupted";
namespace Libraries::SaveData::SaveMemory { namespace Libraries::SaveData::SaveMemory {
static Core::FileSys::MntPoints* g_mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance(); static Core::FileSys::MntPoints* g_mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
static OrbisUserServiceUserId g_user_id{}; struct SlotData {
static std::string g_game_serial{}; OrbisUserServiceUserId user_id;
static std::filesystem::path g_save_path{}; std::string game_serial;
static std::filesystem::path g_param_sfo_path{}; std::filesystem::path folder_path;
static PSF g_param_sfo; PSF sfo;
std::vector<u8> memory_cache;
};
static bool g_save_memory_initialized = false; static std::mutex g_slot_mtx;
static std::mutex g_saving_memory_mutex; static std::unordered_map<u32, SlotData> g_attached_slots;
static std::vector<u8> g_save_memory;
static std::filesystem::path g_icon_path; void PersistMemory(u32 slot_id, bool lock) {
static std::vector<u8> g_icon_memory; std::unique_lock lck{g_slot_mtx, std::defer_lock};
if (lock) {
static std::condition_variable g_trigger_save_memory; lck.lock();
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);
} }
auto& data = g_attached_slots[slot_id];
auto memoryPath = data.folder_path / FilenameSaveDataMemory;
fs::create_directories(memoryPath.parent_path());
[[noreturn]] void SaveThreadLoop() { int n = 0;
Common::SetCurrentThreadName("shadPS4:SaveData:SaveDataMemoryThread"); std::string errMsg;
std::mutex mtx; while (n++ < 10) {
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 { try {
LOG_DEBUG(Lib_SaveData, "Saving save data memory {}", fmt::UTF(g_save_path.u8string())); IOFile f;
int r = f.Open(memoryPath, Common::FS::FileAccessMode::Write);
if (g_memory_dirty) { if (f.IsOpen()) {
g_memory_dirty = false; f.WriteRaw<u8>(data.memory_cache.data(), data.memory_cache.size());
SaveFileSafe(g_save_memory.data(), g_save_memory.size(), f.Close();
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 {}",
fmt::UTF(g_param_sfo_path.u8string()));
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)) {
if (fs::exists(g_icon_path)) {
fs::remove(g_icon_path);
}
fs::copy_file(src_icon, g_icon_path);
}
if (fs::exists(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::Write);
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; return;
} }
const bool ok = g_param_sfo.Encode(g_param_sfo_path); const auto err = std::error_code{r, std::iostream_category()};
throw std::filesystem::filesystem_error{err.message(), err};
} catch (const std::filesystem::filesystem_error& e) {
errMsg = std::string{e.what()};
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
const MsgDialog::MsgDialogState dialog{MsgDialog::MsgDialogState::UserState{
.type = MsgDialog::ButtonType::OK,
.msg = "Failed to persist save memory:\n" + errMsg + "\nat " +
Common::FS::PathToUTF8String(memoryPath),
}};
MsgDialog::ShowMsgDialog(dialog);
}
std::string GetSaveDir(u32 slot_id) {
std::string dir(StandardDirnameSaveDataMemory);
if (slot_id > 0) {
dir += std::to_string(slot_id);
}
return dir;
}
std::filesystem::path GetSavePath(OrbisUserServiceUserId user_id, u32 slot_id,
std::string_view game_serial) {
std::string dir(StandardDirnameSaveDataMemory);
if (slot_id > 0) {
dir += std::to_string(slot_id);
}
return SaveInstance::MakeDirSavePath(user_id, Common::ElfInfo::Instance().GameSerial(), dir);
}
size_t SetupSaveMemory(OrbisUserServiceUserId user_id, u32 slot_id, std::string_view game_serial) {
std::lock_guard lck{g_slot_mtx};
const auto save_dir = GetSavePath(user_id, slot_id, game_serial);
auto& data = g_attached_slots[slot_id];
data = SlotData{
.user_id = user_id,
.game_serial = std::string{game_serial},
.folder_path = save_dir,
.sfo = {},
.memory_cache = {},
};
SaveInstance::SetupDefaultParamSFO(data.sfo, GetSaveDir(slot_id), std::string{game_serial});
auto param_sfo_path = SaveInstance::GetParamSFOPath(save_dir);
if (!fs::exists(param_sfo_path)) {
return 0;
}
if (!data.sfo.Open(param_sfo_path) || fs::exists(save_dir / CorruptFileName)) {
if (!Backup::Restore(save_dir)) { // Could not restore the backup
return 0;
}
}
const auto memory = save_dir / FilenameSaveDataMemory;
if (fs::exists(memory)) {
return fs::file_size(memory);
}
return 0;
}
void SetIcon(u32 slot_id, void* buf, size_t buf_size) {
std::lock_guard lck{g_slot_mtx};
const auto& data = g_attached_slots[slot_id];
const auto icon_path = data.folder_path / sce_sys / "icon0.png";
if (buf == nullptr) {
const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png");
if (fs::exists(icon_path)) {
fs::remove(icon_path);
}
if (fs::exists(src_icon)) {
fs::create_directories(icon_path.parent_path());
fs::copy_file(src_icon, icon_path);
}
} else {
IOFile file(icon_path, Common::FS::FileAccessMode::Write);
file.WriteRaw<u8>(buf, buf_size);
file.Close();
}
}
bool IsSaveMemoryInitialized(u32 slot_id) {
std::lock_guard lck{g_slot_mtx};
return g_attached_slots.contains(slot_id);
}
PSF& GetParamSFO(u32 slot_id) {
std::lock_guard lck{g_slot_mtx};
auto& data = g_attached_slots[slot_id];
return data.sfo;
}
std::vector<u8> GetIcon(u32 slot_id) {
std::lock_guard lck{g_slot_mtx};
auto& data = g_attached_slots[slot_id];
const auto icon_path = data.folder_path / sce_sys / "icon0.png";
IOFile f{icon_path, Common::FS::FileAccessMode::Read};
if (!f.IsOpen()) {
return {};
}
const u64 size = f.GetSize();
std::vector<u8> ret;
ret.resize(size);
f.ReadSpan(std::span{ret});
return ret;
}
void SaveSFO(u32 slot_id) {
std::lock_guard lck{g_slot_mtx};
const auto& data = g_attached_slots[slot_id];
const auto sfo_path = SaveInstance::GetParamSFOPath(data.folder_path);
fs::create_directories(sfo_path.parent_path());
const bool ok = data.sfo.Encode(sfo_path);
if (!ok) { if (!ok) {
LOG_ERROR(Lib_SaveData, "Failed to encode param.sfo"); LOG_ERROR(Lib_SaveData, "Failed to encode param.sfo");
throw std::filesystem::filesystem_error("Failed to write param.sfo", g_param_sfo_path, throw std::filesystem::filesystem_error("Failed to write param.sfo", sfo_path,
std::make_error_code(std::errc::permission_denied)); std::make_error_code(std::errc::permission_denied));
} }
} }
bool IsSaving() {
return g_saving_memory; void ReadMemory(u32 slot_id, void* buf, size_t buf_size, int64_t offset) {
std::lock_guard lk{g_slot_mtx};
auto& data = g_attached_slots[slot_id];
auto& memory = data.memory_cache;
if (memory.empty()) { // Load file
IOFile f{data.folder_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Read};
if (f.IsOpen()) {
memory.resize(f.GetSize());
f.Seek(0);
f.ReadSpan(std::span{memory});
}
}
s64 read_size = buf_size;
if (read_size + offset > memory.size()) {
read_size = memory.size() - offset;
}
std::memcpy(buf, memory.data() + offset, read_size);
} }
bool TriggerSaveWithoutEvent() { void WriteMemory(u32 slot_id, void* buf, size_t buf_size, int64_t offset) {
if (g_saving_memory) { std::lock_guard lk{g_slot_mtx};
return false; auto& data = g_attached_slots[slot_id];
auto& memory = data.memory_cache;
if (offset + buf_size > memory.size()) {
memory.resize(offset + buf_size);
} }
g_trigger_save_memory.notify_one(); std::memcpy(memory.data() + offset, buf, buf_size);
return true; PersistMemory(slot_id, false);
} Backup::NewRequest(data.user_id, data.game_serial, GetSaveDir(slot_id),
Backup::OrbisSaveDataEventType::__DO_NOT_SAVE);
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 + 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 + buf_size > g_save_memory.size()) {
g_save_memory.resize(offset + buf_size);
}
std::memcpy(g_save_memory.data() + offset, buf, buf_size);
g_memory_dirty = true;
} }
} // namespace Libraries::SaveData::SaveMemory } // namespace Libraries::SaveData::SaveMemory

View File

@ -3,7 +3,7 @@
#pragma once #pragma once
#include <span> #include <vector>
#include "save_backup.h" #include "save_backup.h"
class PSF; class PSF;
@ -14,36 +14,30 @@ using OrbisUserServiceUserId = s32;
namespace Libraries::SaveData::SaveMemory { namespace Libraries::SaveData::SaveMemory {
void SetDirectories(OrbisUserServiceUserId user_id, std::string game_serial); void PersistMemory(u32 slot_id, bool lock = true);
[[nodiscard]] const std::filesystem::path& GetSavePath(); [[nodiscard]] std::string GetSaveDir(u32 slot_id);
// returns the size of the existed save memory [[nodiscard]] std::filesystem::path GetSavePath(OrbisUserServiceUserId user_id, u32 slot_id,
size_t CreateSaveMemory(size_t memory_size); std::string_view game_serial);
// Initialize the icon. Set buf to null to read the standard icon. // returns the size of the save memory if exists
void SetIcon(void* buf, size_t buf_size); size_t SetupSaveMemory(OrbisUserServiceUserId user_id, u32 slot_id, std::string_view game_serial);
// Update the icon // Write the icon. Set buf to null to read the standard icon.
void WriteIcon(void* buf, size_t buf_size); void SetIcon(u32 slot_id, void* buf = nullptr, size_t buf_size = 0);
[[nodiscard]] bool IsSaveMemoryInitialized(); [[nodiscard]] bool IsSaveMemoryInitialized(u32 slot_id);
[[nodiscard]] PSF& GetParamSFO(); [[nodiscard]] PSF& GetParamSFO(u32 slot_id);
[[nodiscard]] std::span<u8> GetIcon(); [[nodiscard]] std::vector<u8> GetIcon(u32 slot_id);
// Save now or wait for the background thread to save // Save now or wait for the background thread to save
void SaveSFO(bool sync = false); void SaveSFO(u32 slot_id);
[[nodiscard]] bool IsSaving(); void ReadMemory(u32 slot_id, void* buf, size_t buf_size, int64_t offset);
bool TriggerSaveWithoutEvent(); void WriteMemory(u32 slot_id, void* buf, size_t buf_size, int64_t offset);
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 } // namespace Libraries::SaveData::SaveMemory

View File

@ -177,7 +177,8 @@ struct OrbisSaveDataMemoryGet2 {
OrbisSaveDataMemoryData* data; OrbisSaveDataMemoryData* data;
OrbisSaveDataParam* param; OrbisSaveDataParam* param;
OrbisSaveDataIcon* icon; OrbisSaveDataIcon* icon;
std::array<u8, 32> _reserved; u32 slotId;
std::array<u8, 28> _reserved;
}; };
struct OrbisSaveDataMemorySet2 { struct OrbisSaveDataMemorySet2 {
@ -186,6 +187,8 @@ struct OrbisSaveDataMemorySet2 {
const OrbisSaveDataMemoryData* data; const OrbisSaveDataMemoryData* data;
const OrbisSaveDataParam* param; const OrbisSaveDataParam* param;
const OrbisSaveDataIcon* icon; const OrbisSaveDataIcon* icon;
u32 dataNum;
u32 slotId;
std::array<u8, 32> _reserved; std::array<u8, 32> _reserved;
}; };
@ -198,7 +201,8 @@ struct OrbisSaveDataMemorySetup2 {
const OrbisSaveDataParam* initParam; const OrbisSaveDataParam* initParam;
// +4.5 // +4.5
const OrbisSaveDataIcon* initIcon; const OrbisSaveDataIcon* initIcon;
std::array<u8, 24> _reserved; u32 slotId;
std::array<u8, 20> _reserved;
}; };
struct OrbisSaveDataMemorySetupResult { struct OrbisSaveDataMemorySetupResult {
@ -206,9 +210,16 @@ struct OrbisSaveDataMemorySetupResult {
std::array<u8, 16> _reserved; std::array<u8, 16> _reserved;
}; };
enum OrbisSaveDataMemorySyncOption : u32 {
NONE = 0,
BLOCKING = 1,
};
struct OrbisSaveDataMemorySync { struct OrbisSaveDataMemorySync {
OrbisUserServiceUserId userId; OrbisUserServiceUserId userId;
std::array<u8, 36> _reserved; u32 slotId;
OrbisSaveDataMemorySyncOption option;
std::array<u8, 28> _reserved;
}; };
struct OrbisSaveDataMount2 { struct OrbisSaveDataMount2 {
@ -327,6 +338,7 @@ static void initialize() {
g_initialized = true; g_initialized = true;
g_game_serial = ElfInfo::Instance().GameSerial(); g_game_serial = ElfInfo::Instance().GameSerial();
g_fw_ver = ElfInfo::Instance().FirmwareVer(); g_fw_ver = ElfInfo::Instance().FirmwareVer();
Backup::StartThread();
} }
// game_00other | game*other // game_00other | game*other
@ -558,7 +570,6 @@ Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup) {
} }
} }
Backup::StartThread();
Backup::NewRequest(backup->userId, title, dir_name, OrbisSaveDataEventType::BACKUP); Backup::NewRequest(backup->userId, title, dir_name, OrbisSaveDataEventType::BACKUP);
return Error::OK; return Error::OK;
@ -1136,22 +1147,27 @@ Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getPar
LOG_INFO(Lib_SaveData, "called with invalid parameter"); LOG_INFO(Lib_SaveData, "called with invalid parameter");
return Error::PARAMETER; return Error::PARAMETER;
} }
if (!SaveMemory::IsSaveMemoryInitialized()) {
u32 slot_id = 0;
if (g_fw_ver > ElfInfo::FW_50) {
slot_id = getParam->slotId;
}
if (!SaveMemory::IsSaveMemoryInitialized(slot_id)) {
LOG_INFO(Lib_SaveData, "called without save memory initialized"); LOG_INFO(Lib_SaveData, "called without save memory initialized");
return Error::MEMORY_NOT_READY; return Error::MEMORY_NOT_READY;
} }
LOG_DEBUG(Lib_SaveData, "called"); LOG_DEBUG(Lib_SaveData, "called");
auto data = getParam->data; auto data = getParam->data;
if (data != nullptr) { if (data != nullptr) {
SaveMemory::ReadMemory(data->buf, data->bufSize, data->offset); SaveMemory::ReadMemory(slot_id, data->buf, data->bufSize, data->offset);
} }
auto param = getParam->param; auto param = getParam->param;
if (param != nullptr) { if (param != nullptr) {
param->FromSFO(SaveMemory::GetParamSFO()); param->FromSFO(SaveMemory::GetParamSFO(slot_id));
} }
auto icon = getParam->icon; auto icon = getParam->icon;
if (icon != nullptr) { if (icon != nullptr) {
auto icon_mem = SaveMemory::GetIcon(); auto icon_mem = SaveMemory::GetIcon(slot_id);
size_t total = std::min(icon->bufSize, icon_mem.size()); size_t total = std::min(icon->bufSize, icon_mem.size());
std::memcpy(icon->buf, icon_mem.data(), total); std::memcpy(icon->buf, icon_mem.data(), total);
icon->dataSize = total; icon->dataSize = total;
@ -1494,36 +1510,37 @@ Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2*
LOG_INFO(Lib_SaveData, "called with invalid parameter"); LOG_INFO(Lib_SaveData, "called with invalid parameter");
return Error::PARAMETER; return Error::PARAMETER;
} }
if (!SaveMemory::IsSaveMemoryInitialized()) { u32 slot_id = 0;
u32 data_num = 1;
if (g_fw_ver > ElfInfo::FW_50) {
slot_id = setParam->slotId;
if (setParam->dataNum > 1) {
data_num = setParam->dataNum;
}
}
if (!SaveMemory::IsSaveMemoryInitialized(slot_id)) {
LOG_INFO(Lib_SaveData, "called without save memory initialized"); LOG_INFO(Lib_SaveData, "called without save memory initialized");
return Error::MEMORY_NOT_READY; return Error::MEMORY_NOT_READY;
} }
if (SaveMemory::IsSaving()) {
int count = 0;
while (++count < 100 && SaveMemory::IsSaving()) { // try for more 10 seconds
std::this_thread::sleep_for(chrono::milliseconds(100));
}
if (SaveMemory::IsSaving()) {
LOG_TRACE(Lib_SaveData, "called while saving");
return Error::BUSY_FOR_SAVING;
}
}
LOG_DEBUG(Lib_SaveData, "called"); LOG_DEBUG(Lib_SaveData, "called");
auto data = setParam->data; auto data = setParam->data;
if (data != nullptr) { if (data != nullptr) {
SaveMemory::WriteMemory(data->buf, data->bufSize, data->offset); for (int i = 0; i < data_num; i++) {
SaveMemory::WriteMemory(slot_id, data[i].buf, data[i].bufSize, data[i].offset);
}
} }
auto param = setParam->param; auto param = setParam->param;
if (param != nullptr) { if (param != nullptr) {
param->ToSFO(SaveMemory::GetParamSFO()); param->ToSFO(SaveMemory::GetParamSFO(slot_id));
SaveMemory::SaveSFO(); SaveMemory::SaveSFO(slot_id);
} }
auto icon = setParam->icon;
if (icon != nullptr) { auto icon = setParam->icon;
SaveMemory::WriteIcon(icon->buf, icon->bufSize); if (icon != nullptr) {
SaveMemory::SetIcon(slot_id, icon->buf, icon->bufSize);
} }
SaveMemory::TriggerSaveWithoutEvent();
return Error::OK; return Error::OK;
} }
@ -1563,9 +1580,12 @@ Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetu
} }
LOG_DEBUG(Lib_SaveData, "called"); LOG_DEBUG(Lib_SaveData, "called");
SaveMemory::SetDirectories(setupParam->userId, g_game_serial); u32 slot_id = 0;
if (g_fw_ver > ElfInfo::FW_50) {
slot_id = setupParam->slotId;
}
const auto& save_path = SaveMemory::GetSavePath(); const auto& save_path = SaveMemory::GetSavePath(setupParam->userId, slot_id, g_game_serial);
for (const auto& instance : g_mount_slots) { for (const auto& instance : g_mount_slots) {
if (instance.has_value() && instance->GetSavePath() == save_path) { if (instance.has_value() && instance->GetSavePath() == save_path) {
return Error::BUSY; return Error::BUSY;
@ -1573,21 +1593,21 @@ Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetu
} }
try { try {
size_t existed_size = SaveMemory::CreateSaveMemory(setupParam->memorySize); size_t existed_size =
SaveMemory::SetupSaveMemory(setupParam->userId, slot_id, g_game_serial);
if (existed_size == 0) { // Just created if (existed_size == 0) { // Just created
if (g_fw_ver >= ElfInfo::FW_45 && setupParam->initParam != nullptr) { if (g_fw_ver >= ElfInfo::FW_45 && setupParam->initParam != nullptr) {
auto& sfo = SaveMemory::GetParamSFO(); auto& sfo = SaveMemory::GetParamSFO(slot_id);
setupParam->initParam->ToSFO(sfo); setupParam->initParam->ToSFO(sfo);
} }
SaveMemory::SaveSFO(); SaveMemory::SaveSFO(slot_id);
auto init_icon = setupParam->initIcon; auto init_icon = setupParam->initIcon;
if (g_fw_ver >= ElfInfo::FW_45 && init_icon != nullptr) { if (g_fw_ver >= ElfInfo::FW_45 && init_icon != nullptr) {
SaveMemory::SetIcon(init_icon->buf, init_icon->bufSize); SaveMemory::SetIcon(slot_id, init_icon->buf, init_icon->bufSize);
} else { } else {
SaveMemory::SetIcon(nullptr, 0); SaveMemory::SetIcon(slot_id);
} }
SaveMemory::TriggerSaveWithoutEvent();
} }
if (g_fw_ver >= ElfInfo::FW_45 && result != nullptr) { if (g_fw_ver >= ElfInfo::FW_45 && result != nullptr) {
result->existedMemorySize = existed_size; result->existedMemorySize = existed_size;
@ -1631,15 +1651,23 @@ Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncPa
LOG_INFO(Lib_SaveData, "called with invalid parameter"); LOG_INFO(Lib_SaveData, "called with invalid parameter");
return Error::PARAMETER; return Error::PARAMETER;
} }
if (!SaveMemory::IsSaveMemoryInitialized()) {
u32 slot_id = 0;
if (g_fw_ver > ElfInfo::FW_50) {
slot_id = syncParam->slotId;
}
if (!SaveMemory::IsSaveMemoryInitialized(slot_id)) {
LOG_INFO(Lib_SaveData, "called without save memory initialized"); LOG_INFO(Lib_SaveData, "called without save memory initialized");
return Error::MEMORY_NOT_READY; return Error::MEMORY_NOT_READY;
} }
LOG_DEBUG(Lib_SaveData, "called"); LOG_DEBUG(Lib_SaveData, "called");
bool ok = SaveMemory::TriggerSave();
if (!ok) { SaveMemory::PersistMemory(slot_id);
return Error::BUSY_FOR_SAVING; const auto& save_path = SaveMemory::GetSaveDir(slot_id);
} Backup::NewRequest(syncParam->userId, g_game_serial, save_path,
OrbisSaveDataEventType::SAVE_DATA_MEMORY_SYNC);
return Error::OK; return Error::OK;
} }

View File

@ -9,7 +9,7 @@
namespace Libraries::Videodec { namespace Libraries::Videodec {
static constexpr u64 kMinimumMemorySize = 32_MB; ///> Fake minimum memory size for querying static constexpr u64 kMinimumMemorySize = 16_MB; ///> Fake minimum memory size for querying
int PS4_SYSV_ABI sceVideodecCreateDecoder(const OrbisVideodecConfigInfo* pCfgInfoIn, int PS4_SYSV_ABI sceVideodecCreateDecoder(const OrbisVideodecConfigInfo* pCfgInfoIn,
const OrbisVideodecResourceInfo* pRsrcInfoIn, const OrbisVideodecResourceInfo* pRsrcInfoIn,

View File

@ -10,7 +10,7 @@
namespace Libraries::Vdec2 { namespace Libraries::Vdec2 {
static constexpr u64 kMinimumMemorySize = 32_MB; ///> Fake minimum memory size for querying static constexpr u64 kMinimumMemorySize = 16_MB; ///> Fake minimum memory size for querying
s32 PS4_SYSV_ABI s32 PS4_SYSV_ABI
sceVideodec2QueryComputeMemoryInfo(OrbisVideodec2ComputeMemoryInfo* computeMemInfo) { sceVideodec2QueryComputeMemoryInfo(OrbisVideodec2ComputeMemoryInfo* computeMemInfo) {

View File

@ -0,0 +1,183 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <condition_variable>
#include <mutex>
#include <stop_token>
#include <unordered_map>
#include <queue>
#include <zlib.h>
#include "common/logging/log.h"
#include "common/thread.h"
#include "core/libraries/kernel/threads.h"
#include "core/libraries/libs.h"
#include "core/libraries/zlib/zlib_error.h"
#include "core/libraries/zlib/zlib_sce.h"
namespace Libraries::Zlib {
struct InflateTask {
u64 request_id;
const void* src;
u32 src_length;
void* dst;
u32 dst_length;
};
struct InflateResult {
u32 length;
s32 status;
};
static Kernel::Thread task_thread;
static std::mutex mutex;
static std::queue<InflateTask> task_queue;
static std::condition_variable_any task_queue_cv;
static std::queue<u64> done_queue;
static std::condition_variable_any done_queue_cv;
static std::unordered_map<u64, InflateResult> results;
static u64 next_request_id;
void ZlibTaskThread(const std::stop_token& stop) {
Common::SetCurrentThreadName("shadPS4:ZlibTaskThread");
while (!stop.stop_requested()) {
InflateTask task;
{
// Lock and pop from the task queue, unless stop has been requested.
std::unique_lock lock(mutex);
if (!task_queue_cv.wait(lock, stop, [&] { return !task_queue.empty(); })) {
break;
}
task = task_queue.back();
task_queue.pop();
}
uLongf decompressed_length = task.dst_length;
const auto ret = uncompress(static_cast<Bytef*>(task.dst), &decompressed_length,
static_cast<const Bytef*>(task.src), task.src_length);
{
// Lock, insert the new result, and push the finished request ID to the done queue.
std::unique_lock lock(mutex);
results[task.request_id] = InflateResult{
.length = static_cast<u32>(decompressed_length),
.status = ret == Z_BUF_ERROR ? ORBIS_ZLIB_ERROR_NOSPACE
: ret == Z_OK ? ORBIS_OK
: ORBIS_ZLIB_ERROR_FATAL,
};
done_queue.push(task.request_id);
}
done_queue_cv.notify_one();
}
}
s32 PS4_SYSV_ABI sceZlibInitialize(const void* buffer, u32 length) {
LOG_INFO(Lib_Zlib, "called");
if (task_thread.Joinable()) {
return ORBIS_ZLIB_ERROR_ALREADY_INITIALIZED;
}
// Initialize with empty task data
task_queue = std::queue<InflateTask>();
done_queue = std::queue<u64>();
results.clear();
next_request_id = 1;
task_thread.Run([](const std::stop_token& stop) { ZlibTaskThread(stop); });
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceZlibInflate(const void* src, u32 src_len, void* dst, u32 dst_len,
u64* request_id) {
LOG_DEBUG(Lib_Zlib, "(STUBBED) called");
if (!task_thread.Joinable()) {
return ORBIS_ZLIB_ERROR_NOT_INITIALIZED;
}
if (!src || !src_len || !dst || !dst_len || !request_id || dst_len > 64_KB ||
dst_len % 2_KB != 0) {
return ORBIS_ZLIB_ERROR_INVALID;
}
{
std::unique_lock lock(mutex);
*request_id = next_request_id++;
task_queue.emplace(InflateTask{
.request_id = *request_id,
.src = src,
.src_length = src_len,
.dst = dst,
.dst_length = dst_len,
});
task_queue_cv.notify_one();
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceZlibWaitForDone(u64* request_id, const u32* timeout) {
LOG_DEBUG(Lib_Zlib, "(STUBBED) called");
if (!task_thread.Joinable()) {
return ORBIS_ZLIB_ERROR_NOT_INITIALIZED;
}
if (!request_id) {
return ORBIS_ZLIB_ERROR_INVALID;
}
{
// Pop from the done queue, unless the timeout is reached.
std::unique_lock lock(mutex);
const auto pred = [] { return !done_queue.empty(); };
if (timeout) {
if (!done_queue_cv.wait_for(lock, std::chrono::milliseconds(*timeout), pred)) {
return ORBIS_ZLIB_ERROR_TIMEDOUT;
}
} else {
done_queue_cv.wait(lock, pred);
}
*request_id = done_queue.back();
done_queue.pop();
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceZlibGetResult(const u64 request_id, u32* dst_length, s32* status) {
LOG_DEBUG(Lib_Zlib, "(STUBBED) called");
if (!task_thread.Joinable()) {
return ORBIS_ZLIB_ERROR_NOT_INITIALIZED;
}
if (!dst_length || !status) {
return ORBIS_ZLIB_ERROR_INVALID;
}
{
std::unique_lock lock(mutex);
if (!results.contains(request_id)) {
return ORBIS_ZLIB_ERROR_NOT_FOUND;
}
const auto result = results[request_id];
*dst_length = result.length;
*status = result.status;
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceZlibFinalize() {
LOG_INFO(Lib_Zlib, "called");
if (!task_thread.Joinable()) {
return ORBIS_ZLIB_ERROR_NOT_INITIALIZED;
}
task_thread.Stop();
return ORBIS_OK;
}
void RegisterlibSceZlib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("m1YErdIXCp4", "libSceZlib", 1, "libSceZlib", 1, 1, sceZlibInitialize);
LIB_FUNCTION("6na+Sa-B83w", "libSceZlib", 1, "libSceZlib", 1, 1, sceZlibFinalize);
LIB_FUNCTION("TLar1HULv1Q", "libSceZlib", 1, "libSceZlib", 1, 1, sceZlibInflate);
LIB_FUNCTION("uB8VlDD4e0s", "libSceZlib", 1, "libSceZlib", 1, 1, sceZlibWaitForDone);
LIB_FUNCTION("2eDcGHC0YaM", "libSceZlib", 1, "libSceZlib", 1, 1, sceZlibGetResult);
};
} // namespace Libraries::Zlib

View File

@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/libraries/error_codes.h"
// Zlib library
constexpr int ORBIS_ZLIB_ERROR_NOT_FOUND = 0x81120002;
constexpr int ORBIS_ZLIB_ERROR_BUSY = 0x8112000B;
constexpr int ORBIS_ZLIB_ERROR_FAULT = 0x8112000E;
constexpr int ORBIS_ZLIB_ERROR_INVALID = 0x81120016;
constexpr int ORBIS_ZLIB_ERROR_NOSPACE = 0x8112001C;
constexpr int ORBIS_ZLIB_ERROR_NOT_SUPPORTED = 0x81120025;
constexpr int ORBIS_ZLIB_ERROR_TIMEDOUT = 0x81120027;
constexpr int ORBIS_ZLIB_ERROR_NOT_INITIALIZED = 0x81120032;
constexpr int ORBIS_ZLIB_ERROR_ALREADY_INITIALIZED = 0x81120033;
constexpr int ORBIS_ZLIB_ERROR_FATAL = 0x811200FF;

View File

@ -0,0 +1,21 @@
// 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::Zlib {
s32 PS4_SYSV_ABI sceZlibInitialize(const void* buffer, u32 length);
s32 PS4_SYSV_ABI sceZlibInflate(const void* src, u32 src_len, void* dst, u32 dst_len,
u64* request_id);
s32 PS4_SYSV_ABI sceZlibWaitForDone(u64* request_id, const u32* timeout);
s32 PS4_SYSV_ABI sceZlibGetResult(u64 request_id, u32* dst_length, s32* status);
s32 PS4_SYSV_ABI sceZlibFinalize();
void RegisterlibSceZlib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Zlib

View File

@ -33,6 +33,7 @@
#include "core/libraries/ngs2/ngs2.h" #include "core/libraries/ngs2/ngs2.h"
#include "core/libraries/np_trophy/np_trophy.h" #include "core/libraries/np_trophy/np_trophy.h"
#include "core/libraries/rtc/rtc.h" #include "core/libraries/rtc/rtc.h"
#include "core/libraries/save_data/save_backup.h"
#include "core/linker.h" #include "core/linker.h"
#include "core/memory.h" #include "core/memory.h"
#include "emulator.h" #include "emulator.h"
@ -271,7 +272,7 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector<std::str
UpdatePlayTime(id); UpdatePlayTime(id);
#endif #endif
std::exit(0); std::quick_exit(0);
} }
void Emulator::LoadSystemModules(const std::string& game_serial) { void Emulator::LoadSystemModules(const std::string& game_serial) {

View File

@ -52,10 +52,12 @@ public:
// "Open Folder..." submenu // "Open Folder..." submenu
QMenu* openFolderMenu = new QMenu(tr("Open Folder..."), widget); QMenu* openFolderMenu = new QMenu(tr("Open Folder..."), widget);
QAction* openGameFolder = new QAction(tr("Open Game Folder"), widget); QAction* openGameFolder = new QAction(tr("Open Game Folder"), widget);
QAction* openUpdateFolder = new QAction(tr("Open Update Folder"), widget);
QAction* openSaveDataFolder = new QAction(tr("Open Save Data Folder"), widget); QAction* openSaveDataFolder = new QAction(tr("Open Save Data Folder"), widget);
QAction* openLogFolder = new QAction(tr("Open Log Folder"), widget); QAction* openLogFolder = new QAction(tr("Open Log Folder"), widget);
openFolderMenu->addAction(openGameFolder); openFolderMenu->addAction(openGameFolder);
openFolderMenu->addAction(openUpdateFolder);
openFolderMenu->addAction(openSaveDataFolder); openFolderMenu->addAction(openSaveDataFolder);
openFolderMenu->addAction(openLogFolder); openFolderMenu->addAction(openLogFolder);
@ -87,10 +89,12 @@ public:
QMenu* deleteMenu = new QMenu(tr("Delete..."), widget); QMenu* deleteMenu = new QMenu(tr("Delete..."), widget);
QAction* deleteGame = new QAction(tr("Delete Game"), widget); QAction* deleteGame = new QAction(tr("Delete Game"), widget);
QAction* deleteUpdate = new QAction(tr("Delete Update"), widget); QAction* deleteUpdate = new QAction(tr("Delete Update"), widget);
QAction* deleteSaveData = new QAction(tr("Delete Save Data"), widget);
QAction* deleteDLC = new QAction(tr("Delete DLC"), widget); QAction* deleteDLC = new QAction(tr("Delete DLC"), widget);
deleteMenu->addAction(deleteGame); deleteMenu->addAction(deleteGame);
deleteMenu->addAction(deleteUpdate); deleteMenu->addAction(deleteUpdate);
deleteMenu->addAction(deleteSaveData);
deleteMenu->addAction(deleteDLC); deleteMenu->addAction(deleteDLC);
menu.addMenu(deleteMenu); menu.addMenu(deleteMenu);
@ -122,6 +126,18 @@ public:
QDesktopServices::openUrl(QUrl::fromLocalFile(folderPath)); QDesktopServices::openUrl(QUrl::fromLocalFile(folderPath));
} }
if (selected == openUpdateFolder) {
QString open_update_path;
Common::FS::PathToQString(open_update_path, m_games[itemID].path);
open_update_path += "-UPDATE";
if (!std::filesystem::exists(Common::FS::PathFromQString(open_update_path))) {
QMessageBox::critical(nullptr, tr("Error"),
QString(tr("This game has no update folder to open!")));
} else {
QDesktopServices::openUrl(QUrl::fromLocalFile(open_update_path));
}
}
if (selected == openSaveDataFolder) { if (selected == openSaveDataFolder) {
QString userPath; QString userPath;
Common::FS::PathToQString(userPath, Common::FS::PathToQString(userPath,
@ -143,7 +159,7 @@ public:
PSF psf; PSF psf;
std::filesystem::path game_folder_path = m_games[itemID].path; std::filesystem::path game_folder_path = m_games[itemID].path;
std::filesystem::path game_update_path = game_folder_path; std::filesystem::path game_update_path = game_folder_path;
game_update_path += "UPDATE"; game_update_path += "-UPDATE";
if (std::filesystem::exists(game_update_path)) { if (std::filesystem::exists(game_update_path)) {
game_folder_path = game_update_path; game_folder_path = game_update_path;
} }
@ -238,6 +254,11 @@ public:
QString trophyPath, gameTrpPath; QString trophyPath, gameTrpPath;
Common::FS::PathToQString(trophyPath, m_games[itemID].serial); Common::FS::PathToQString(trophyPath, m_games[itemID].serial);
Common::FS::PathToQString(gameTrpPath, m_games[itemID].path); Common::FS::PathToQString(gameTrpPath, m_games[itemID].path);
auto game_update_path = Common::FS::PathFromQString(gameTrpPath);
game_update_path += "-UPDATE";
if (std::filesystem::exists(game_update_path)) {
Common::FS::PathToQString(gameTrpPath, game_update_path);
}
TrophyViewer* trophyViewer = new TrophyViewer(trophyPath, gameTrpPath); TrophyViewer* trophyViewer = new TrophyViewer(trophyPath, gameTrpPath);
trophyViewer->show(); trophyViewer->show();
connect(widget->parent(), &QWidget::destroyed, trophyViewer, connect(widget->parent(), &QWidget::destroyed, trophyViewer,
@ -335,14 +356,18 @@ public:
clipboard->setText(combinedText); clipboard->setText(combinedText);
} }
if (selected == deleteGame || selected == deleteUpdate || selected == deleteDLC) { if (selected == deleteGame || selected == deleteUpdate || selected == deleteDLC ||
selected == deleteSaveData) {
bool error = false; bool error = false;
QString folder_path, game_update_path, dlc_path; QString folder_path, game_update_path, dlc_path, save_data_path;
Common::FS::PathToQString(folder_path, m_games[itemID].path); Common::FS::PathToQString(folder_path, m_games[itemID].path);
game_update_path = folder_path + "-UPDATE"; game_update_path = folder_path + "-UPDATE";
Common::FS::PathToQString( Common::FS::PathToQString(
dlc_path, Config::getAddonInstallDir() / dlc_path, Config::getAddonInstallDir() /
Common::FS::PathFromQString(folder_path).parent_path().filename()); Common::FS::PathFromQString(folder_path).parent_path().filename());
Common::FS::PathToQString(save_data_path,
Common::FS::GetUserPath(Common::FS::PathType::UserDir) /
"savedata/1" / m_games[itemID].serial);
QString message_type = tr("Game"); QString message_type = tr("Game");
if (selected == deleteUpdate) { if (selected == deleteUpdate) {
@ -363,6 +388,15 @@ public:
folder_path = dlc_path; folder_path = dlc_path;
message_type = tr("DLC"); message_type = tr("DLC");
} }
} else if (selected == deleteSaveData) {
if (!std::filesystem::exists(Common::FS::PathFromQString(save_data_path))) {
QMessageBox::critical(nullptr, tr("Error"),
QString(tr("This game has no save data to delete!")));
error = true;
} else {
folder_path = save_data_path;
message_type = tr("Save Data");
}
} }
if (!error) { if (!error) {
QString gameName = QString::fromStdString(m_games[itemID].name); QString gameName = QString::fromStdString(m_games[itemID].name);
@ -374,7 +408,10 @@ public:
QMessageBox::Yes | QMessageBox::No); QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) { if (reply == QMessageBox::Yes) {
dir.removeRecursively(); dir.removeRecursively();
if (selected == deleteGame) {
widget->removeRow(itemID); widget->removeRow(itemID);
m_games.removeAt(itemID);
}
} }
} }
} }

View File

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <QCheckBox>
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QDir> #include <QDir>
#include <QFileDialog> #include <QFileDialog>
@ -15,10 +16,11 @@
#include "install_dir_select.h" #include "install_dir_select.h"
InstallDirSelect::InstallDirSelect() : selected_dir() { InstallDirSelect::InstallDirSelect() : selected_dir() {
selected_dir = Config::getGameInstallDirs().empty() ? "" : Config::getGameInstallDirs().front(); auto install_dirs = Config::getGameInstallDirs();
selected_dir = install_dirs.empty() ? "" : install_dirs.front();
if (!Config::getGameInstallDirs().empty() && Config::getGameInstallDirs().size() == 1) { if (!install_dirs.empty() && install_dirs.size() == 1) {
reject(); accept();
} }
auto layout = new QVBoxLayout(this); auto layout = new QVBoxLayout(this);
@ -53,6 +55,14 @@ QWidget* InstallDirSelect::SetupInstallDirList() {
vlayout->addWidget(m_path_list); vlayout->addWidget(m_path_list);
auto checkbox = new QCheckBox(tr("Install All Queued to Selected Folder"));
connect(checkbox, &QCheckBox::toggled, this, &InstallDirSelect::setUseForAllQueued);
vlayout->addWidget(checkbox);
auto checkbox2 = new QCheckBox(tr("Delete PKG File on Install"));
connect(checkbox2, &QCheckBox::toggled, this, &InstallDirSelect::setDeleteFileOnInstall);
vlayout->addWidget(checkbox2);
group->setLayout(vlayout); group->setLayout(vlayout);
return group; return group;
} }
@ -66,6 +76,14 @@ void InstallDirSelect::setSelectedDirectory(QListWidgetItem* item) {
} }
} }
void InstallDirSelect::setUseForAllQueued(bool enabled) {
use_for_all_queued = enabled;
}
void InstallDirSelect::setDeleteFileOnInstall(bool enabled) {
delete_file_on_install = enabled;
}
QWidget* InstallDirSelect::SetupDialogActions() { QWidget* InstallDirSelect::SetupDialogActions() {
auto actions = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); auto actions = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);

View File

@ -22,9 +22,21 @@ public:
return selected_dir; return selected_dir;
} }
bool useForAllQueued() {
return use_for_all_queued;
}
bool deleteFileOnInstall() {
return delete_file_on_install;
}
private: private:
QWidget* SetupInstallDirList(); QWidget* SetupInstallDirList();
QWidget* SetupDialogActions(); QWidget* SetupDialogActions();
void setSelectedDirectory(QListWidgetItem* item); void setSelectedDirectory(QListWidgetItem* item);
void setDeleteFileOnInstall(bool enabled);
void setUseForAllQueued(bool enabled);
std::filesystem::path selected_dir; std::filesystem::path selected_dir;
bool delete_file_on_install = false;
bool use_for_all_queued = false;
}; };

View File

@ -725,16 +725,27 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
return; return;
} }
auto category = psf.GetString("CATEGORY"); auto category = psf.GetString("CATEGORY");
if (!use_for_all_queued || pkgNum == 1) {
InstallDirSelect ids; InstallDirSelect ids;
ids.exec(); const auto selected = ids.exec();
auto game_install_dir = ids.getSelectedDirectory(); if (selected == QDialog::Rejected) {
return;
}
last_install_dir = ids.getSelectedDirectory();
delete_file_on_install = ids.deleteFileOnInstall();
use_for_all_queued = ids.useForAllQueued();
}
std::filesystem::path game_install_dir = last_install_dir;
QString pkgType = QString::fromStdString(pkg.GetPkgFlags()); QString pkgType = QString::fromStdString(pkg.GetPkgFlags());
bool use_game_update = pkgType.contains("PATCH") && Config::getSeparateUpdateEnabled(); bool use_game_update = pkgType.contains("PATCH") && Config::getSeparateUpdateEnabled();
// Default paths // Default paths
auto game_folder_path = game_install_dir / pkg.GetTitleID(); auto game_folder_path = game_install_dir / pkg.GetTitleID();
auto game_update_path = use_game_update auto game_update_path = use_game_update ? game_folder_path.parent_path() /
? game_folder_path.parent_path() / (std::string{pkg.GetTitleID()} + "-UPDATE") (std::string{pkg.GetTitleID()} + "-UPDATE")
: game_folder_path; : game_folder_path;
const int max_depth = 5; const int max_depth = 5;
@ -744,8 +755,8 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
std::string{pkg.GetTitleID()}, max_depth); std::string{pkg.GetTitleID()}, max_depth);
if (found_game.has_value()) { if (found_game.has_value()) {
game_folder_path = found_game.value().parent_path(); game_folder_path = found_game.value().parent_path();
game_update_path = use_game_update game_update_path = use_game_update ? game_folder_path.parent_path() /
? game_folder_path.parent_path() / (std::string{pkg.GetTitleID()} + "-UPDATE") (std::string{pkg.GetTitleID()} + "-UPDATE")
: game_folder_path; : game_folder_path;
} }
} else { } else {
@ -759,8 +770,8 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
else { else {
game_folder_path = game_install_dir / pkg.GetTitleID(); game_folder_path = game_install_dir / pkg.GetTitleID();
} }
game_update_path = use_game_update game_update_path = use_game_update ? game_folder_path.parent_path() /
? game_folder_path.parent_path() / (std::string{pkg.GetTitleID()} + "-UPDATE") (std::string{pkg.GetTitleID()} + "-UPDATE")
: game_folder_path; : game_folder_path;
} }
@ -908,10 +919,18 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
connect(&futureWatcher, &QFutureWatcher<void>::finished, this, [=, this]() { connect(&futureWatcher, &QFutureWatcher<void>::finished, this, [=, this]() {
if (pkgNum == nPkg) { if (pkgNum == nPkg) {
QString path; QString path;
// We want to show the parent path instead of the full path // We want to show the parent path instead of the full path
Common::FS::PathToQString(path, game_folder_path.parent_path()); Common::FS::PathToQString(path, game_folder_path.parent_path());
QIcon windowIcon(
Common::FS::PathToUTF8String(game_folder_path / "sce_sys/icon0.png")
.c_str());
QMessageBox extractMsgBox(this); QMessageBox extractMsgBox(this);
extractMsgBox.setWindowTitle(tr("Extraction Finished")); extractMsgBox.setWindowTitle(tr("Extraction Finished"));
if (!windowIcon.isNull()) {
extractMsgBox.setWindowIcon(windowIcon);
}
extractMsgBox.setText( extractMsgBox.setText(
QString(tr("Game successfully installed at %1")).arg(path)); QString(tr("Game successfully installed at %1")).arg(path));
extractMsgBox.addButton(QMessageBox::Ok); extractMsgBox.addButton(QMessageBox::Ok);
@ -925,6 +944,9 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
}); });
extractMsgBox.exec(); extractMsgBox.exec();
} }
if (delete_file_on_install) {
std::filesystem::remove(file);
}
}); });
connect(&dialog, &QProgressDialog::canceled, [&]() { futureWatcher.cancel(); }); connect(&dialog, &QProgressDialog::canceled, [&]() { futureWatcher.cancel(); });
connect(&futureWatcher, &QFutureWatcher<void>::progressValueChanged, &dialog, connect(&futureWatcher, &QFutureWatcher<void>::progressValueChanged, &dialog,

View File

@ -123,4 +123,8 @@ protected:
} }
void resizeEvent(QResizeEvent* event) override; void resizeEvent(QResizeEvent* event) override;
std::filesystem::path last_install_dir = "";
bool delete_file_on_install = false;
bool use_for_all_queued = false;
}; };

View File

@ -146,19 +146,19 @@
</message> </message>
<message> <message>
<source>Compatibility...</source> <source>Compatibility...</source>
<translation>Compatibility...</translation> <translation>Compatibilité...</translation>
</message> </message>
<message> <message>
<source>Update database</source> <source>Update database</source>
<translation>Update database</translation> <translation>Mettre à jour la base de données</translation>
</message> </message>
<message> <message>
<source>View report</source> <source>View report</source>
<translation>View report</translation> <translation>Voir rapport</translation>
</message> </message>
<message> <message>
<source>Submit a report</source> <source>Submit a report</source>
<translation>Submit a report</translation> <translation>Soumettre un rapport</translation>
</message> </message>
<message> <message>
<source>Shortcut creation</source> <source>Shortcut creation</source>
@ -186,7 +186,7 @@
</message> </message>
<message> <message>
<source>requiresEnableSeparateUpdateFolder_MSG</source> <source>requiresEnableSeparateUpdateFolder_MSG</source>
<translation>This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it.</translation> <translation>Cette fonctionnalité nécessite l'option 'Dossier séparé pour les mises à jour' pour fonctionner. Si vous voulez utiliser cette fonctionnalité, veuillez l'activer.</translation>
</message> </message>
<message> <message>
<source>This game has no update to delete!</source> <source>This game has no update to delete!</source>
@ -373,7 +373,7 @@
</message> </message>
<message> <message>
<source>toolBar</source> <source>toolBar</source>
<translation>Bare d'outils</translation> <translation>Barre d'outils</translation>
</message> </message>
<message> <message>
<source>Game List</source> <source>Game List</source>
@ -489,7 +489,7 @@
</message> </message>
<message> <message>
<source>Game successfully installed at %1</source> <source>Game successfully installed at %1</source>
<translation>Jeu installé avec succès à %1</translation> <translation>Jeu installé avec succès dans %1</translation>
</message> </message>
<message> <message>
<source>File doesn't appear to be a valid PKG file</source> <source>File doesn't appear to be a valid PKG file</source>
@ -546,7 +546,7 @@
</message> </message>
<message> <message>
<source>Enable Separate Update Folder</source> <source>Enable Separate Update Folder</source>
<translation>Dossier séparé pour les mises à jours</translation> <translation>Dossier séparé pour les mises à jour</translation>
</message> </message>
<message> <message>
<source>Default tab when opening settings</source> <source>Default tab when opening settings</source>
@ -554,7 +554,7 @@
</message> </message>
<message> <message>
<source>Show Game Size In List</source> <source>Show Game Size In List</source>
<translation>Afficher la taille du jeu dans la liste</translation> <translation>Afficher la taille des jeux dans la liste</translation>
</message> </message>
<message> <message>
<source>Show Splash</source> <source>Show Splash</source>
@ -574,11 +574,11 @@
</message> </message>
<message> <message>
<source>Trophy Key</source> <source>Trophy Key</source>
<translation>Trophy Key</translation> <translation>Clé de trophée</translation>
</message> </message>
<message> <message>
<source>Trophy</source> <source>Trophy</source>
<translation>Trophy</translation> <translation>Trophée</translation>
</message> </message>
<message> <message>
<source>Logger</source> <source>Logger</source>
@ -586,11 +586,11 @@
</message> </message>
<message> <message>
<source>Log Type</source> <source>Log Type</source>
<translation>Type</translation> <translation>Type de journal</translation>
</message> </message>
<message> <message>
<source>Log Filter</source> <source>Log Filter</source>
<translation>Filtre</translation> <translation>Filtre du journal</translation>
</message> </message>
<message> <message>
<source>Open Log Location</source> <source>Open Log Location</source>
@ -722,7 +722,7 @@
</message> </message>
<message> <message>
<source>Disable Trophy Pop-ups</source> <source>Disable Trophy Pop-ups</source>
<translation>Disable Trophy Pop-ups</translation> <translation>Désactiver les notifications de trophées</translation>
</message> </message>
<message> <message>
<source>Play title music</source> <source>Play title music</source>
@ -730,19 +730,19 @@
</message> </message>
<message> <message>
<source>Update Compatibility Database On Startup</source> <source>Update Compatibility Database On Startup</source>
<translation>Update Compatibility Database On Startup</translation> <translation>Mettre à jour la base de données de compatibilité au lancement</translation>
</message> </message>
<message> <message>
<source>Game Compatibility</source> <source>Game Compatibility</source>
<translation>Game Compatibility</translation> <translation>Compatibilité du jeu</translation>
</message> </message>
<message> <message>
<source>Display Compatibility Data</source> <source>Display Compatibility Data</source>
<translation>Display Compatibility Data</translation> <translation>Afficher les données de compatibilité</translation>
</message> </message>
<message> <message>
<source>Update Compatibility Database</source> <source>Update Compatibility Database</source>
<translation>Update Compatibility Database</translation> <translation>Mettre à jour la base de données de compatibilité</translation>
</message> </message>
<message> <message>
<source>Volume</source> <source>Volume</source>
@ -750,7 +750,7 @@
</message> </message>
<message> <message>
<source>Audio Backend</source> <source>Audio Backend</source>
<translation>Audio Backend</translation> <translation>Back-end audio</translation>
</message> </message>
<message> <message>
<source>Save</source> <source>Save</source>
@ -786,7 +786,7 @@
</message> </message>
<message> <message>
<source>separateUpdatesCheckBox</source> <source>separateUpdatesCheckBox</source>
<translation>Dossier séparé pour les mises à jours:\nInstalle les mises à jours des jeux dans un dossier séparé pour une gestion plus facile.</translation> <translation>Dossier séparé pour les mises à jour:\nInstalle les mises à jours des jeux dans un dossier séparé pour une gestion plus facile.</translation>
</message> </message>
<message> <message>
<source>showSplashCheckBox</source> <source>showSplashCheckBox</source>
@ -806,7 +806,7 @@
</message> </message>
<message> <message>
<source>TrophyKey</source> <source>TrophyKey</source>
<translation>Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters.</translation> <translation>Clé de trophées:\nClé utilisée pour décrypter les trophées. Doit être obtenu à partir de votre console jailbreakée.\nDoit contenir des caractères hexadécimaux uniquement.</translation>
</message> </message>
<message> <message>
<source>logTypeGroupBox</source> <source>logTypeGroupBox</source>
@ -826,7 +826,7 @@
</message> </message>
<message> <message>
<source>disableTrophycheckBox</source> <source>disableTrophycheckBox</source>
<translation>Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window).</translation> <translation>Désactiver les notifications de trophées:\nDésactive les notifications de trophées en jeu. La progression des trophées peut toujours être suivie à l'aide de la Visionneuse de trophées (clique droit sur le jeu sur la fenêtre principale).</translation>
</message> </message>
<message> <message>
<source>hideCursorGroupBox</source> <source>hideCursorGroupBox</source>
@ -842,15 +842,15 @@
</message> </message>
<message> <message>
<source>enableCompatibilityCheckBox</source> <source>enableCompatibilityCheckBox</source>
<translation>Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information.</translation> <translation>Afficher les données de compatibilité:\nAffiche les informations de compatibilité des jeux dans une colonne dédiée. Activez "Mettre à jour la compatibilité au démarrage" pour avoir des informations à jour.</translation>
</message> </message>
<message> <message>
<source>checkCompatibilityOnStartupCheckBox</source> <source>checkCompatibilityOnStartupCheckBox</source>
<translation>Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts.</translation> <translation>Mettre à jour la compatibilité au démarrage:\nMettre à jour automatiquement la base de données de compatibilité au démarrage de shadPS4.</translation>
</message> </message>
<message> <message>
<source>updateCompatibilityButton</source> <source>updateCompatibilityButton</source>
<translation>Update Compatibility Database:\nImmediately update the compatibility database.</translation> <translation>Mettre à jour la compatibilité au démarrage:\nMet à jour immédiatement la base de données de compatibilité.</translation>
</message> </message>
<message> <message>
<source>Never</source> <source>Never</source>
@ -945,7 +945,7 @@
</message> </message>
<message> <message>
<source>Serial: </source> <source>Serial: </source>
<translation>Série: </translation> <translation>Numéro de série: </translation>
</message> </message>
<message> <message>
<source>Version: </source> <source>Version: </source>
@ -1093,7 +1093,7 @@
</message> </message>
<message> <message>
<source>DownloadComplete_MSG</source> <source>DownloadComplete_MSG</source>
<translation>Patchs téléchargés avec succès ! Tous les patches disponibles pour tous les jeux ont é téléchargés, il n'est pas nécessaire de les télécharger individuellement pour chaque jeu comme c'est le cas pour les Cheats. Si le correctif n'apparaît pas, il se peut qu'il n'existe pas pour le numéro de série et la version spécifiques du jeu.</translation> <translation>Patchs téléchargés avec succès ! Tous les patches disponibles pour tous les jeux ont é téléchargés, il n'est pas nécessaire de les télécharger individuellement pour chaque jeu comme c'est le cas pour les Cheats. Si le correctif n'apparaît pas, il se peut qu'il n'existe pas pour la série et la version spécifiques du jeu.</translation>
</message> </message>
<message> <message>
<source>Failed to parse JSON data from HTML.</source> <source>Failed to parse JSON data from HTML.</source>
@ -1113,7 +1113,7 @@
</message> </message>
<message> <message>
<source>You may need to update your game.</source> <source>You may need to update your game.</source>
<translation>Vous devrez peut-être mettre à jour votre jeu.</translation> <translation>Vous devriez peut-être mettre à jour votre jeu.</translation>
</message> </message>
<message> <message>
<source>Incompatibility Notice</source> <source>Incompatibility Notice</source>
@ -1137,7 +1137,7 @@
</message> </message>
<message> <message>
<source>Directory does not exist:</source> <source>Directory does not exist:</source>
<translation>Répertoire n'existe pas:</translation> <translation>Le répertoire n'existe pas:</translation>
</message> </message>
<message> <message>
<source>Failed to open files.json for reading.</source> <source>Failed to open files.json for reading.</source>
@ -1149,7 +1149,7 @@
</message> </message>
<message> <message>
<source>Can't apply cheats before the game is started</source> <source>Can't apply cheats before the game is started</source>
<translation>Impossible d'appliquer les Cheats avant que le jeu ne commence.</translation> <translation>Impossible d'appliquer les cheats avant que le jeu ne soit lancé</translation>
</message> </message>
</context> </context>
<context> <context>
@ -1164,11 +1164,11 @@
</message> </message>
<message> <message>
<source>Serial</source> <source>Serial</source>
<translation>Série</translation> <translation>Numéro de série</translation>
</message> </message>
<message> <message>
<source>Compatibility</source> <source>Compatibility</source>
<translation>Compatibility</translation> <translation>Compatibilité</translation>
</message> </message>
<message> <message>
<source>Region</source> <source>Region</source>
@ -1212,27 +1212,27 @@
</message> </message>
<message> <message>
<source>Compatibility is untested</source> <source>Compatibility is untested</source>
<translation>Compatibility is untested</translation> <translation>La compatibilité n'a pas é testé</translation>
</message> </message>
<message> <message>
<source>Game does not initialize properly / crashes the emulator</source> <source>Game does not initialize properly / crashes the emulator</source>
<translation>Game does not initialize properly / crashes the emulator</translation> <translation>Le jeu ne se lance pas correctement / crash l'émulateur</translation>
</message> </message>
<message> <message>
<source>Game boots, but only displays a blank screen</source> <source>Game boots, but only displays a blank screen</source>
<translation>Game boots, but only displays a blank screen</translation> <translation>Le jeu démarre, mais n'affiche qu'un écran noir</translation>
</message> </message>
<message> <message>
<source>Game displays an image but does not go past the menu</source> <source>Game displays an image but does not go past the menu</source>
<translation>Game displays an image but does not go past the menu</translation> <translation>Le jeu affiche une image mais ne dépasse pas le menu</translation>
</message> </message>
<message> <message>
<source>Game has game-breaking glitches or unplayable performance</source> <source>Game has game-breaking glitches or unplayable performance</source>
<translation>Game has game-breaking glitches or unplayable performance</translation> <translation>Le jeu a des problèmes majeurs ou des performances qui le rendent injouable</translation>
</message> </message>
<message> <message>
<source>Game can be completed with playable performance and no major glitches</source> <source>Game can be completed with playable performance and no major glitches</source>
<translation>Game can be completed with playable performance and no major glitches</translation> <translation>Le jeu peut être terminé avec des performances acceptables et sans problèmes majeurs</translation>
</message> </message>
</context> </context>
<context> <context>

View File

@ -98,7 +98,7 @@
</message> </message>
<message> <message>
<source>Open Folder...</source> <source>Open Folder...</source>
<translation>Åpne mappen...</translation> <translation>Åpne mappe...</translation>
</message> </message>
<message> <message>
<source>Open Game Folder</source> <source>Open Game Folder</source>
@ -126,7 +126,7 @@
</message> </message>
<message> <message>
<source>Copy All</source> <source>Copy All</source>
<translation>Kopier alle</translation> <translation>Kopier alt</translation>
</message> </message>
<message> <message>
<source>Delete...</source> <source>Delete...</source>
@ -146,19 +146,19 @@
</message> </message>
<message> <message>
<source>Compatibility...</source> <source>Compatibility...</source>
<translation>Compatibility...</translation> <translation>Kompatibilitet...</translation>
</message> </message>
<message> <message>
<source>Update database</source> <source>Update database</source>
<translation>Update database</translation> <translation>Oppdater database</translation>
</message> </message>
<message> <message>
<source>View report</source> <source>View report</source>
<translation>View report</translation> <translation>Vis rapport</translation>
</message> </message>
<message> <message>
<source>Submit a report</source> <source>Submit a report</source>
<translation>Submit a report</translation> <translation>Send inn en rapport</translation>
</message> </message>
<message> <message>
<source>Shortcut creation</source> <source>Shortcut creation</source>
@ -574,11 +574,11 @@
</message> </message>
<message> <message>
<source>Trophy Key</source> <source>Trophy Key</source>
<translation>Trophy Key</translation> <translation>Trofénøkkel</translation>
</message> </message>
<message> <message>
<source>Trophy</source> <source>Trophy</source>
<translation>Trophy</translation> <translation>Tro</translation>
</message> </message>
<message> <message>
<source>Logger</source> <source>Logger</source>
@ -658,7 +658,7 @@
</message> </message>
<message> <message>
<source>Enable Shaders Dumping</source> <source>Enable Shaders Dumping</source>
<translation>Aktiver dumping av skyggelegger</translation> <translation>Aktiver skyggeleggerdumping</translation>
</message> </message>
<message> <message>
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
@ -666,7 +666,7 @@
</message> </message>
<message> <message>
<source>Paths</source> <source>Paths</source>
<translation>Stier</translation> <translation>Mapper</translation>
</message> </message>
<message> <message>
<source>Game Folders</source> <source>Game Folders</source>
@ -686,7 +686,7 @@
</message> </message>
<message> <message>
<source>Enable Debug Dumping</source> <source>Enable Debug Dumping</source>
<translation>Aktiver dumping av feilretting</translation> <translation>Aktiver feilrettingsdumping</translation>
</message> </message>
<message> <message>
<source>Enable Vulkan Validation Layers</source> <source>Enable Vulkan Validation Layers</source>
@ -718,7 +718,7 @@
</message> </message>
<message> <message>
<source>GUI Settings</source> <source>GUI Settings</source>
<translation>GUI-innstillinger</translation> <translation>Grensesnitt-innstillinger</translation>
</message> </message>
<message> <message>
<source>Disable Trophy Pop-ups</source> <source>Disable Trophy Pop-ups</source>
@ -730,7 +730,7 @@
</message> </message>
<message> <message>
<source>Update Compatibility Database On Startup</source> <source>Update Compatibility Database On Startup</source>
<translation>Oppdater kompatibilitets-database ved oppstart</translation> <translation>Oppdater database ved oppstart</translation>
</message> </message>
<message> <message>
<source>Game Compatibility</source> <source>Game Compatibility</source>
@ -750,7 +750,7 @@
</message> </message>
<message> <message>
<source>Audio Backend</source> <source>Audio Backend</source>
<translation>Audio Backend</translation> <translation>Lydsystem</translation>
</message> </message>
<message> <message>
<source>Save</source> <source>Save</source>
@ -806,7 +806,7 @@
</message> </message>
<message> <message>
<source>TrophyKey</source> <source>TrophyKey</source>
<translation>Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters.</translation> <translation>Trofénøkkel:\nNøkkel brukes til å dekryptere trofeer. hentes fra din konsoll (jailbroken).\nMå bare inneholde sekskantede tegn.</translation>
</message> </message>
<message> <message>
<source>logTypeGroupBox</source> <source>logTypeGroupBox</source>
@ -846,7 +846,7 @@
</message> </message>
<message> <message>
<source>checkCompatibilityOnStartupCheckBox</source> <source>checkCompatibilityOnStartupCheckBox</source>
<translation>Oppdater kompatibilitets-data ved oppstart:\nOppdaterer kompatibilitets-databasen automatisk når shadPS4 starter.</translation> <translation>Oppdater database ved oppstart:\nOppdaterer kompatibilitets-databasen automatisk når shadPS4 starter.</translation>
</message> </message>
<message> <message>
<source>updateCompatibilityButton</source> <source>updateCompatibilityButton</source>
@ -894,7 +894,7 @@
</message> </message>
<message> <message>
<source>dumpShadersCheckBox</source> <source>dumpShadersCheckBox</source>
<translation>Aktiver dumping av skyggelegger:\nFor teknisk feilsøking lagrer skyggeleggerne fra spillet i en mappe mens de gjengis.</translation> <translation>Aktiver skyggeleggerdumping:\nFor teknisk feilsøking lagrer skyggeleggerne fra spillet i en mappe mens de gjengis.</translation>
</message> </message>
<message> <message>
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
@ -914,7 +914,7 @@
</message> </message>
<message> <message>
<source>debugDump</source> <source>debugDump</source>
<translation>Aktiver dumping av feilsøking:\nLagrer import- og eksport-symbolene og filoverskriftsinformasjonen til det nåværende kjørende PS4-programmet i en katalog.</translation> <translation>Aktiver feilrettingsdumping:\nLagrer import- og eksport-symbolene og filoverskriftsinformasjonen til det nåværende kjørende PS4-programmet i en katalog.</translation>
</message> </message>
<message> <message>
<source>vkValidationCheckBox</source> <source>vkValidationCheckBox</source>
@ -1188,7 +1188,7 @@
</message> </message>
<message> <message>
<source>Path</source> <source>Path</source>
<translation>Sti</translation> <translation>Adresse</translation>
</message> </message>
<message> <message>
<source>Play Time</source> <source>Play Time</source>
@ -1232,7 +1232,7 @@
</message> </message>
<message> <message>
<source>Game can be completed with playable performance and no major glitches</source> <source>Game can be completed with playable performance and no major glitches</source>
<translation>Spillet kan fullføres med spillbar ytelse og ingen store feil</translation> <translation>Spillet kan fullføres med spillbar ytelse og uten store feil</translation>
</message> </message>
</context> </context>
<context> <context>

View File

@ -126,11 +126,11 @@
</message> </message>
<message> <message>
<source>Copy All</source> <source>Copy All</source>
<translation>Копировать все</translation> <translation>Копировать всё</translation>
</message> </message>
<message> <message>
<source>Delete...</source> <source>Delete...</source>
<translation>Удаление...</translation> <translation>Удалить...</translation>
</message> </message>
<message> <message>
<source>Delete Game</source> <source>Delete Game</source>
@ -158,7 +158,7 @@
</message> </message>
<message> <message>
<source>Submit a report</source> <source>Submit a report</source>
<translation>Отправить отчет</translation> <translation>Отправить отчёт</translation>
</message> </message>
<message> <message>
<source>Shortcut creation</source> <source>Shortcut creation</source>
@ -297,7 +297,7 @@
</message> </message>
<message> <message>
<source>Elf Viewer</source> <source>Elf Viewer</source>
<translation>Elf</translation> <translation>Исполняемый файл</translation>
</message> </message>
<message> <message>
<source>Game Install Directory</source> <source>Game Install Directory</source>
@ -542,7 +542,7 @@
</message> </message>
<message> <message>
<source>Fullscreen Mode</source> <source>Fullscreen Mode</source>
<translation>Режим Полного Экран</translation> <translation>Тип полноэкранного режима</translation>
</message> </message>
<message> <message>
<source>Enable Separate Update Folder</source> <source>Enable Separate Update Folder</source>
@ -574,11 +574,11 @@
</message> </message>
<message> <message>
<source>Trophy Key</source> <source>Trophy Key</source>
<translation>Trophy Key</translation> <translation>Ключ трофеев</translation>
</message> </message>
<message> <message>
<source>Trophy</source> <source>Trophy</source>
<translation>Trophy</translation> <translation>Трофеи</translation>
</message> </message>
<message> <message>
<source>Logger</source> <source>Logger</source>
@ -750,7 +750,7 @@
</message> </message>
<message> <message>
<source>Audio Backend</source> <source>Audio Backend</source>
<translation>Звуковая Подсистема</translation> <translation>Звуковая подсистема</translation>
</message> </message>
<message> <message>
<source>Save</source> <source>Save</source>
@ -826,7 +826,7 @@
</message> </message>
<message> <message>
<source>disableTrophycheckBox</source> <source>disableTrophycheckBox</source>
<translation>Отключить уведомления о трофеях:\nОтключает внутриигровые уведомления о трофеях. Прогресс трофеев по прежнему можно отслеживать в меню Просмотр трофеев (правая кнопка мыши по игре в главном окне).</translation> <translation>Отключить уведомления о трофеях:\nОтключает внутриигровые уведомления о трофеях. Прогресс трофеев по прежнему можно отслеживать в меню просмотра трофеев (правая кнопка мыши по игре в главном окне).</translation>
</message> </message>
<message> <message>
<source>hideCursorGroupBox</source> <source>hideCursorGroupBox</source>
@ -1192,11 +1192,11 @@
</message> </message>
<message> <message>
<source>Play Time</source> <source>Play Time</source>
<translation>Времени в игре</translation> <translation>Время в игре</translation>
</message> </message>
<message> <message>
<source>Never Played</source> <source>Never Played</source>
<translation>Вы не играли</translation> <translation>Нет</translation>
</message> </message>
<message> <message>
<source>h</source> <source>h</source>

View File

@ -546,7 +546,7 @@
</message> </message>
<message> <message>
<source>Enable Separate Update Folder</source> <source>Enable Separate Update Folder</source>
<translation>Enable Separate Update Folder</translation> <translation>Ayrı Güncelleme Klasörünü Etkinleştir</translation>
</message> </message>
<message> <message>
<source>Default tab when opening settings</source> <source>Default tab when opening settings</source>
@ -554,7 +554,7 @@
</message> </message>
<message> <message>
<source>Show Game Size In List</source> <source>Show Game Size In List</source>
<translation>Göster oyun boyutunu listede</translation> <translation>Oyun Boyutunu Listede Göster</translation>
</message> </message>
<message> <message>
<source>Show Splash</source> <source>Show Splash</source>
@ -574,11 +574,11 @@
</message> </message>
<message> <message>
<source>Trophy Key</source> <source>Trophy Key</source>
<translation>Trophy Key</translation> <translation>Kupa Anahtarı</translation>
</message> </message>
<message> <message>
<source>Trophy</source> <source>Trophy</source>
<translation>Trophy</translation> <translation>Kupa</translation>
</message> </message>
<message> <message>
<source>Logger</source> <source>Logger</source>
@ -722,7 +722,7 @@
</message> </message>
<message> <message>
<source>Disable Trophy Pop-ups</source> <source>Disable Trophy Pop-ups</source>
<translation>Disable Trophy Pop-ups</translation> <translation>Kupa ılır Pencerelerini Devre Dışı Bırak</translation>
</message> </message>
<message> <message>
<source>Play title music</source> <source>Play title music</source>
@ -730,23 +730,23 @@
</message> </message>
<message> <message>
<source>Update Compatibility Database On Startup</source> <source>Update Compatibility Database On Startup</source>
<translation>Update Compatibility Database On Startup</translation> <translation>Başlangıçta Uyumluluk Veritabanını Güncelle</translation>
</message> </message>
<message> <message>
<source>Game Compatibility</source> <source>Game Compatibility</source>
<translation>Game Compatibility</translation> <translation>Oyun Uyumluluğu</translation>
</message> </message>
<message> <message>
<source>Display Compatibility Data</source> <source>Display Compatibility Data</source>
<translation>Display Compatibility Data</translation> <translation>Uyumluluk Verilerini Göster</translation>
</message> </message>
<message> <message>
<source>Update Compatibility Database</source> <source>Update Compatibility Database</source>
<translation>Update Compatibility Database</translation> <translation>Uyumluluk Veritabanını Güncelle</translation>
</message> </message>
<message> <message>
<source>Volume</source> <source>Volume</source>
<translation>Ses seviyesi</translation> <translation>Ses Seviyesi</translation>
</message> </message>
<message> <message>
<source>Audio Backend</source> <source>Audio Backend</source>
@ -1105,11 +1105,11 @@
</message> </message>
<message> <message>
<source>The game is in version: %1</source> <source>The game is in version: %1</source>
<translation>Oyun sürümde: %1</translation> <translation>Oyun sürümü: %1</translation>
</message> </message>
<message> <message>
<source>The downloaded patch only works on version: %1</source> <source>The downloaded patch only works on version: %1</source>
<translation>İndirilen yamanın sadece sürümde çalışıyor: %1</translation> <translation>İndirilen yama sadece şu sürümde çalışıyor: %1</translation>
</message> </message>
<message> <message>
<source>You may need to update your game.</source> <source>You may need to update your game.</source>
@ -1168,7 +1168,7 @@
</message> </message>
<message> <message>
<source>Compatibility</source> <source>Compatibility</source>
<translation>Compatibility</translation> <translation>Uyumluluk</translation>
</message> </message>
<message> <message>
<source>Region</source> <source>Region</source>
@ -1196,35 +1196,35 @@
</message> </message>
<message> <message>
<source>Never Played</source> <source>Never Played</source>
<translation>Never Played</translation> <translation>Hiç Oynanmadı</translation>
</message> </message>
<message> <message>
<source>h</source> <source>h</source>
<translation>h</translation> <translation>sa</translation>
</message> </message>
<message> <message>
<source>m</source> <source>m</source>
<translation>m</translation> <translation>dk</translation>
</message> </message>
<message> <message>
<source>s</source> <source>s</source>
<translation>s</translation> <translation>sn</translation>
</message> </message>
<message> <message>
<source>Compatibility is untested</source> <source>Compatibility is untested</source>
<translation>Compatibility is untested</translation> <translation>Uyumluluk test edilmemiş</translation>
</message> </message>
<message> <message>
<source>Game does not initialize properly / crashes the emulator</source> <source>Game does not initialize properly / crashes the emulator</source>
<translation>Game does not initialize properly / crashes the emulator</translation> <translation>Oyun düzgün bir şekilde başlatılamıyor / emülatörü çökertiyor</translation>
</message> </message>
<message> <message>
<source>Game boots, but only displays a blank screen</source> <source>Game boots, but only displays a blank screen</source>
<translation>Game boots, but only displays a blank screen</translation> <translation>Oyun başlatılabiliyor ancak yalnızca boş bir ekran gösteriyor</translation>
</message> </message>
<message> <message>
<source>Game displays an image but does not go past the menu</source> <source>Game displays an image but does not go past the menu</source>
<translation>Game displays an image but does not go past the menu</translation> <translation>Oyun bir resim gösteriyor ancak menüleri geçemiyor</translation>
</message> </message>
<message> <message>
<source>Game has game-breaking glitches or unplayable performance</source> <source>Game has game-breaking glitches or unplayable performance</source>
@ -1232,7 +1232,7 @@
</message> </message>
<message> <message>
<source>Game can be completed with playable performance and no major glitches</source> <source>Game can be completed with playable performance and no major glitches</source>
<translation>Game can be completed with playable performance and no major glitches</translation> <translation>Oyun, oynanabilir performansla tamamlanabilir ve büyük aksaklık yok</translation>
</message> </message>
</context> </context>
<context> <context>
@ -1267,7 +1267,7 @@
</message> </message>
<message> <message>
<source>Your version is already up to date!</source> <source>Your version is already up to date!</source>
<translation>Versiyonunuz zaten güncel!</translation> <translation>Sürümünüz zaten güncel!</translation>
</message> </message>
<message> <message>
<source>Update Available</source> <source>Update Available</source>
@ -1279,11 +1279,11 @@
</message> </message>
<message> <message>
<source>Current Version</source> <source>Current Version</source>
<translation>Mevcut Versiyon</translation> <translation>Mevcut Sürüm</translation>
</message> </message>
<message> <message>
<source>Latest Version</source> <source>Latest Version</source>
<translation>Son Versiyon</translation> <translation>Son Sürüm</translation>
</message> </message>
<message> <message>
<source>Do you want to update?</source> <source>Do you want to update?</source>
@ -1335,7 +1335,7 @@
</message> </message>
<message> <message>
<source>Failed to create the update script file</source> <source>Failed to create the update script file</source>
<translation>Güncelleme betiği dosyası oluşturulamadı</translation> <translation>Güncelleme komut dosyası oluşturulamadı</translation>
</message> </message>
</context> </context>
<context> <context>

View File

@ -382,20 +382,6 @@ void WindowSDL::OnResize() {
} }
void WindowSDL::OnKeyPress(const SDL_Event* event) { void WindowSDL::OnKeyPress(const SDL_Event* event) {
#ifdef __APPLE__
// Use keys that are more friendly for keyboards without a keypad.
// Once there are key binding options this won't be necessary.
constexpr SDL_Keycode CrossKey = SDLK_N;
constexpr SDL_Keycode CircleKey = SDLK_B;
constexpr SDL_Keycode SquareKey = SDLK_V;
constexpr SDL_Keycode TriangleKey = SDLK_C;
#else
constexpr SDL_Keycode CrossKey = SDLK_KP_2;
constexpr SDL_Keycode CircleKey = SDLK_KP_6;
constexpr SDL_Keycode SquareKey = SDLK_KP_4;
constexpr SDL_Keycode TriangleKey = SDLK_KP_8;
#endif
auto button = OrbisPadButtonDataOffset::None; auto button = OrbisPadButtonDataOffset::None;
Input::Axis axis = Input::Axis::AxisMax; Input::Axis axis = Input::Axis::AxisMax;
int axisvalue = 0; int axisvalue = 0;
@ -414,16 +400,21 @@ void WindowSDL::OnKeyPress(const SDL_Event* event) {
case SDLK_RIGHT: case SDLK_RIGHT:
button = OrbisPadButtonDataOffset::Right; button = OrbisPadButtonDataOffset::Right;
break; break;
case TriangleKey: // Provide alternatives for face buttons for users without a numpad.
case SDLK_KP_8:
case SDLK_C:
button = OrbisPadButtonDataOffset::Triangle; button = OrbisPadButtonDataOffset::Triangle;
break; break;
case CircleKey: case SDLK_KP_6:
case SDLK_B:
button = OrbisPadButtonDataOffset::Circle; button = OrbisPadButtonDataOffset::Circle;
break; break;
case CrossKey: case SDLK_KP_2:
case SDLK_N:
button = OrbisPadButtonDataOffset::Cross; button = OrbisPadButtonDataOffset::Cross;
break; break;
case SquareKey: case SDLK_KP_4:
case SDLK_V:
button = OrbisPadButtonDataOffset::Square; button = OrbisPadButtonDataOffset::Square;
break; break;
case SDLK_RETURN: case SDLK_RETURN:

View File

@ -583,6 +583,18 @@ void PatchTextureBufferArgs(IR::Block& block, IR::Inst& inst, Info& info) {
} }
} }
IR::Value FixCubeCoords(IR::IREmitter& ir, const AmdGpu::Image& image, const IR::Value& x,
const IR::Value& y, const IR::Value& face) {
if (!image.IsCube()) {
return ir.CompositeConstruct(x, y, face);
}
// AMD cube math results in coordinates in the range [1.0, 2.0]. We need
// to convert this to the range [0.0, 1.0] to get correct results.
const auto fixed_x = ir.FPSub(IR::F32{x}, ir.Imm32(1.f));
const auto fixed_y = ir.FPSub(IR::F32{y}, ir.Imm32(1.f));
return ir.CompositeConstruct(fixed_x, fixed_y, face);
}
void PatchImageSampleArgs(IR::Block& block, IR::Inst& inst, Info& info, void PatchImageSampleArgs(IR::Block& block, IR::Inst& inst, Info& info,
const ImageResource& image_res, const AmdGpu::Image& image) { const ImageResource& image_res, const AmdGpu::Image& image) {
const auto handle = inst.Arg(0); const auto handle = inst.Arg(0);
@ -643,8 +655,8 @@ void PatchImageSampleArgs(IR::Block& block, IR::Inst& inst, Info& info,
case AmdGpu::ImageType::Color1DArray: case AmdGpu::ImageType::Color1DArray:
return read(0); return read(0);
case AmdGpu::ImageType::Color2D: case AmdGpu::ImageType::Color2D:
case AmdGpu::ImageType::Color2DArray:
case AmdGpu::ImageType::Color2DMsaa: case AmdGpu::ImageType::Color2DMsaa:
case AmdGpu::ImageType::Color2DArray:
return ir.CompositeConstruct(read(0), read(8)); return ir.CompositeConstruct(read(0), read(8));
case AmdGpu::ImageType::Color3D: case AmdGpu::ImageType::Color3D:
return ir.CompositeConstruct(read(0), read(8), read(16)); return ir.CompositeConstruct(read(0), read(8), read(16));
@ -665,8 +677,8 @@ void PatchImageSampleArgs(IR::Block& block, IR::Inst& inst, Info& info,
addr_reg = addr_reg + 2; addr_reg = addr_reg + 2;
return {get_addr_reg(addr_reg - 2), get_addr_reg(addr_reg - 1)}; return {get_addr_reg(addr_reg - 2), get_addr_reg(addr_reg - 1)};
case AmdGpu::ImageType::Color2D: case AmdGpu::ImageType::Color2D:
case AmdGpu::ImageType::Color2DArray:
case AmdGpu::ImageType::Color2DMsaa: case AmdGpu::ImageType::Color2DMsaa:
case AmdGpu::ImageType::Color2DArray:
// (du/dx, dv/dx), (du/dy, dv/dy) // (du/dx, dv/dx), (du/dy, dv/dy)
addr_reg = addr_reg + 4; addr_reg = addr_reg + 4;
return {ir.CompositeConstruct(get_addr_reg(addr_reg - 4), get_addr_reg(addr_reg - 3)), return {ir.CompositeConstruct(get_addr_reg(addr_reg - 4), get_addr_reg(addr_reg - 3)),
@ -711,11 +723,12 @@ void PatchImageSampleArgs(IR::Block& block, IR::Inst& inst, Info& info,
case AmdGpu::ImageType::Color2D: // x, y case AmdGpu::ImageType::Color2D: // x, y
addr_reg = addr_reg + 2; addr_reg = addr_reg + 2;
return ir.CompositeConstruct(get_coord(addr_reg - 2, 0), get_coord(addr_reg - 1, 1)); return ir.CompositeConstruct(get_coord(addr_reg - 2, 0), get_coord(addr_reg - 1, 1));
case AmdGpu::ImageType::Color2DArray: // x, y, slice
[[fallthrough]];
case AmdGpu::ImageType::Color2DMsaa: // x, y, frag case AmdGpu::ImageType::Color2DMsaa: // x, y, frag
[[fallthrough]];
case AmdGpu::ImageType::Color2DArray: // x, y, slice
addr_reg = addr_reg + 3; addr_reg = addr_reg + 3;
return ir.CompositeConstruct(get_coord(addr_reg - 3, 0), get_coord(addr_reg - 2, 1), // Note we can use FixCubeCoords with fallthrough cases since it checks for image type.
return FixCubeCoords(ir, image, get_coord(addr_reg - 3, 0), get_coord(addr_reg - 2, 1),
get_addr_reg(addr_reg - 1)); get_addr_reg(addr_reg - 1));
case AmdGpu::ImageType::Color3D: // x, y, z case AmdGpu::ImageType::Color3D: // x, y, z
addr_reg = addr_reg + 3; addr_reg = addr_reg + 3;

View File

@ -47,6 +47,7 @@ struct ImageSpecialization {
AmdGpu::ImageType type = AmdGpu::ImageType::Color2D; AmdGpu::ImageType type = AmdGpu::ImageType::Color2D;
bool is_integer = false; bool is_integer = false;
bool is_storage = false; bool is_storage = false;
bool is_cube = false;
AmdGpu::CompMapping dst_select{}; AmdGpu::CompMapping dst_select{};
AmdGpu::NumberConversion num_conversion{}; AmdGpu::NumberConversion num_conversion{};
@ -127,6 +128,7 @@ struct StageSpecialization {
spec.type = sharp.GetViewType(desc.is_array); spec.type = sharp.GetViewType(desc.is_array);
spec.is_integer = AmdGpu::IsInteger(sharp.GetNumberFmt()); spec.is_integer = AmdGpu::IsInteger(sharp.GetNumberFmt());
spec.is_storage = desc.is_written; spec.is_storage = desc.is_written;
spec.is_cube = sharp.IsCube();
if (spec.is_storage) { if (spec.is_storage) {
spec.dst_select = sharp.DstSelect(); spec.dst_select = sharp.DstSelect();
} }

View File

@ -1155,10 +1155,7 @@ void Rasterizer::UpdateViewportScissorState(const GraphicsPipeline& pipeline) {
const auto& vp_ctl = regs.viewport_control; const auto& vp_ctl = regs.viewport_control;
const float reduce_z = const float reduce_z =
instance.IsDepthClipControlSupported() && regs.clipper_control.clip_space == AmdGpu::Liverpool::ClipSpace::MinusWToW ? 1.0f : 0.0f;
regs.clipper_control.clip_space == AmdGpu::Liverpool::ClipSpace::MinusWToW
? 1.0f
: 0.0f;
if (regs.polygon_control.enable_window_offset) { if (regs.polygon_control.enable_window_offset) {
LOG_ERROR(Render_Vulkan, LOG_ERROR(Render_Vulkan,