From c2dc7c3fd42d86868a5b3d3c2ddf283babebc9b9 Mon Sep 17 00:00:00 2001 From: psucien Date: Mon, 10 Jun 2024 23:20:32 +0200 Subject: [PATCH 01/17] renderer_vulkan: another fix for vertex buffer offsets --- src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp | 6 ++++-- src/video_core/renderer_vulkan/vk_stream_buffer.cpp | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 3f2195d7e..8f438020a 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -429,8 +429,10 @@ void GraphicsPipeline::BindVertexBuffers(StreamBuffer& staging) const { for (u32 i = 0; i < num_buffers; ++i) { const auto& buffer = guest_buffers[i]; const auto& host_buffer = std::ranges::find_if( - ranges_merged.cbegin(), ranges_merged.cend(), - [&](const BufferRange& range) { return (buffer.base_address >= range.base_address); }); + ranges_merged.cbegin(), ranges_merged.cend(), [&](const BufferRange& range) { + return (buffer.base_address >= range.base_address && + buffer.base_address < range.end_address); + }); assert(host_buffer != ranges_merged.cend()); host_buffers[i] = staging.Handle(); diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp index 86a03a034..116f7896d 100644 --- a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp +++ b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp @@ -232,8 +232,7 @@ void StreamBuffer::WaitPendingOperations(u64 requested_upper_bound) { } u64 StreamBuffer::Copy(VAddr src, size_t size, size_t alignment /*= 0*/) { - static const u64 MinUniformAlignment = instance.UniformMinAlignment(); - const auto [data, offset, _] = Map(size, MinUniformAlignment); + const auto [data, offset, _] = Map(size, alignment); std::memcpy(data, reinterpret_cast(src), size); Commit(size); return offset; From 7fcb758da2370cdd511d78c2577d4a43bd8068e0 Mon Sep 17 00:00:00 2001 From: psucien Date: Mon, 10 Jun 2024 23:48:06 +0200 Subject: [PATCH 02/17] timer_management: `sceKernelUsleep` on <1ms delays --- src/core/libraries/kernel/time_management.cpp | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/core/libraries/kernel/time_management.cpp b/src/core/libraries/kernel/time_management.cpp index 8c31c550c..3f5f92eef 100644 --- a/src/core/libraries/kernel/time_management.cpp +++ b/src/core/libraries/kernel/time_management.cpp @@ -9,9 +9,16 @@ #include "core/libraries/libs.h" #ifdef _WIN64 +#include #include + +// http://stackoverflow.com/a/31411628/4725495 +static u32(__stdcall* NtDelayExecution)(BOOL Alertable, PLARGE_INTEGER DelayInterval) = + (u32(__stdcall*)(BOOL, PLARGE_INTEGER))GetProcAddress(GetModuleHandle("ntdll.dll"), + "NtDelayExecution"); #else #include +#include #endif namespace Libraries::Kernel { @@ -40,8 +47,18 @@ u64 PS4_SYSV_ABI sceKernelReadTsc() { } int PS4_SYSV_ABI sceKernelUsleep(u32 microseconds) { - ASSERT(microseconds >= 1000); - std::this_thread::sleep_for(std::chrono::microseconds(microseconds)); + if (microseconds < 1000u) { +#if _WIN64 + LARGE_INTEGER interval{ + .QuadPart = -1 * (microseconds * 10u), + }; + NtDelayExecution(FALSE, &interval); + } else { + std::this_thread::sleep_for(std::chrono::microseconds(microseconds)); + } +#else + usleep(microseconds); +#endif return 0; } From cb2cf7d93cdcbf9646a6806f0b2661344bae8771 Mon Sep 17 00:00:00 2001 From: psucien Date: Mon, 10 Jun 2024 23:49:23 +0200 Subject: [PATCH 03/17] recompiler: trivial missing ops (VALU OR and SALU LE, GE) added --- src/shader_recompiler/frontend/translate/translate.cpp | 9 +++++++++ src/shader_recompiler/frontend/translate/translate.h | 1 + src/shader_recompiler/frontend/translate/vector_alu.cpp | 7 +++++++ 3 files changed, 17 insertions(+) diff --git a/src/shader_recompiler/frontend/translate/translate.cpp b/src/shader_recompiler/frontend/translate/translate.cpp index c0ddf4ae9..a32cde39b 100644 --- a/src/shader_recompiler/frontend/translate/translate.cpp +++ b/src/shader_recompiler/frontend/translate/translate.cpp @@ -228,6 +228,9 @@ void Translate(IR::Block* block, std::span inst_list, Info& info) case Opcode::V_AND_B32: translator.V_AND_B32(inst); break; + case Opcode::V_OR_B32: + translator.V_OR_B32(inst); + break; case Opcode::V_LSHLREV_B32: translator.V_LSHLREV_B32(inst); break; @@ -318,6 +321,9 @@ void Translate(IR::Block* block, std::span inst_list, Info& info) case Opcode::V_CMP_EQ_I32: translator.V_CMP_U32(ConditionOp::EQ, true, false, inst); break; + case Opcode::V_CMP_LE_I32: + translator.V_CMP_U32(ConditionOp::LE, true, false, inst); + break; case Opcode::V_CMP_NE_U32: translator.V_CMP_U32(ConditionOp::LG, false, false, inst); break; @@ -378,6 +384,9 @@ void Translate(IR::Block* block, std::span inst_list, Info& info) case Opcode::S_CMP_GT_I32: translator.S_CMP(ConditionOp::GT, true, inst); break; + case Opcode::S_CMP_GE_I32: + translator.S_CMP(ConditionOp::GE, true, inst); + break; case Opcode::S_CMP_EQ_I32: translator.S_CMP(ConditionOp::EQ, true, inst); break; diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index a8964fc9e..64d6d7f08 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -62,6 +62,7 @@ public: void V_CVT_PKRTZ_F16_F32(const GcnInst& inst); void V_MUL_F32(const GcnInst& inst); void V_CNDMASK_B32(const GcnInst& inst); + void V_OR_B32(const GcnInst& inst); void V_AND_B32(const GcnInst& inst); void V_LSHLREV_B32(const GcnInst& inst); void V_ADD_I32(const GcnInst& inst); diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index dbd9471f1..2281a038c 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -50,6 +50,13 @@ void Translator::V_CNDMASK_B32(const GcnInst& inst) { ir.SetVectorReg(dst_reg, IR::U32F32{result}); } +void Translator::V_OR_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; + const IR::VectorReg dst_reg{inst.dst[0].code}; + ir.SetVectorReg(dst_reg, ir.BitwiseOr(src0, src1)); +} + void Translator::V_AND_B32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; From 0f27e0edf22e2a111ade9b6d6b43f851b9a18451 Mon Sep 17 00:00:00 2001 From: raziel1000 Date: Mon, 10 Jun 2024 20:42:21 -0600 Subject: [PATCH 04/17] - Added trophy decryption when extracting a fpkg. trp icons and xmls are dumped to game_data/ (can be restored if deleted by accident by opening the trophy viewer) - Added a trophy viewer (right click on game ==> trophy viewer) - Enabled Run button. - Switched gui settings to toml. - Added recent files (6 max) - Applied @raphaelthegreat suggestions and corrections (Thanks a lot). - Fixed several bugs and crashes. - Full screen should disabled by default. - Added region in list mode. - Added a simple temp elf list widget. - Added messages when extracting pkg (ex: installing a patch before the game...etc) --- .reuse/dep5 | 8 +- CMakeLists.txt | 28 +- src/common/config.cpp | 151 +++++++++ src/common/config.h | 33 ++ src/common/io_file.h | 5 + src/core/crypto/crypto.cpp | 133 +++++--- src/core/crypto/crypto.h | 13 +- src/core/crypto/keys.h | 1 + src/core/file_format/pkg.cpp | 72 ++++- src/core/file_format/pkg.h | 46 ++- src/core/file_format/psf.cpp | 24 +- src/core/file_format/psf.h | 2 +- src/core/file_format/trp.cpp | 91 ++++++ src/core/file_format/trp.h | 46 +++ src/core/loader.cpp | 2 +- src/core/loader.h | 2 +- src/emulator.cpp | 2 +- src/images/flag_china.png | Bin 0 -> 572 bytes src/images/flag_eu.png | Bin 0 -> 942 bytes src/images/flag_jp.png | Bin 0 -> 371 bytes src/images/flag_unk.png | Bin 0 -> 257 bytes src/images/flag_us.png | Bin 0 -> 321 bytes src/images/flag_world.png | Bin 0 -> 2995 bytes src/qt_gui/elf_viewer.cpp | 96 ++++++ src/qt_gui/elf_viewer.h | 61 ++++ src/qt_gui/game_grid_frame.cpp | 40 +-- src/qt_gui/game_grid_frame.h | 9 +- src/qt_gui/game_info.cpp | 48 ++- src/qt_gui/game_info.h | 42 +-- src/qt_gui/game_install_dialog.cpp | 11 +- src/qt_gui/game_install_dialog.h | 6 +- src/qt_gui/game_list_frame.cpp | 119 ++++--- src/qt_gui/game_list_frame.h | 29 +- src/qt_gui/game_list_utils.h | 65 +++- src/qt_gui/gui_context_menus.h | 41 ++- src/qt_gui/gui_save.h | 29 -- src/qt_gui/gui_settings.cpp | 24 -- src/qt_gui/gui_settings.h | 88 ------ src/qt_gui/main.cpp | 26 +- src/qt_gui/main_window.cpp | 490 +++++++++++++++++++---------- src/qt_gui/main_window.h | 52 +-- src/qt_gui/main_window_ui.h | 24 +- src/qt_gui/pkg_viewer.cpp | 157 ++++----- src/qt_gui/pkg_viewer.h | 64 +--- src/qt_gui/settings.cpp | 77 ----- src/qt_gui/settings.h | 50 --- src/qt_gui/trophy_viewer.cpp | 145 +++++++++ src/qt_gui/trophy_viewer.h | 49 +++ src/shadps4.qrc | 6 + 49 files changed, 1616 insertions(+), 891 deletions(-) create mode 100644 src/core/file_format/trp.cpp create mode 100644 src/core/file_format/trp.h create mode 100644 src/images/flag_china.png create mode 100644 src/images/flag_eu.png create mode 100644 src/images/flag_jp.png create mode 100644 src/images/flag_unk.png create mode 100644 src/images/flag_us.png create mode 100644 src/images/flag_world.png create mode 100644 src/qt_gui/elf_viewer.cpp create mode 100644 src/qt_gui/elf_viewer.h delete mode 100644 src/qt_gui/gui_save.h delete mode 100644 src/qt_gui/gui_settings.cpp delete mode 100644 src/qt_gui/gui_settings.h delete mode 100644 src/qt_gui/settings.cpp delete mode 100644 src/qt_gui/settings.h create mode 100644 src/qt_gui/trophy_viewer.cpp create mode 100644 src/qt_gui/trophy_viewer.h diff --git a/.reuse/dep5 b/.reuse/dep5 index 69e066f58..283c680b4 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -6,10 +6,10 @@ Files: CMakeSettings.json scripts/ps4_names.txt documents/changelog.txt documents/readme.txt + documents/Screenshots/screenshot.png .github/shadps4.desktop .github/shadps4.png .gitmodules - documents/Screenshots/screenshot.png src/images/shadps4.ico src/images/controller_icon.png src/images/exit_icon.png @@ -25,6 +25,12 @@ Files: CMakeSettings.json src/images/settings_icon.png src/images/stop_icon.png src/images/themes_icon.png + src/images/flag_jp.png + src/images/flag_eu.png + src/images/flag_us.png + src/images/flag_china.png + src/images/flag_world.png + src/images/flag_unk.png src/shadps4.rc src/shadps4.qrc externals/stb_image.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e9b3a332..7bb549d2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -266,6 +266,8 @@ set(CORE src/core/aerolib/stubs.cpp src/core/file_format/pkg_type.h src/core/file_format/psf.cpp src/core/file_format/psf.h + src/core/file_format/trp.cpp + src/core/file_format/trp.h src/core/file_format/splash.h src/core/file_format/splash.cpp src/core/file_sys/fs.cpp @@ -433,6 +435,12 @@ set(INPUT src/input/controller.cpp src/input/controller.h ) +set(EMULATOR src/emulator.cpp + src/emulator.h + src/sdl_window.h + src/sdl_window.cpp +) + # the above is shared in sdl and qt version (TODO share them all) if(ENABLE_QT_GUI) @@ -442,9 +450,6 @@ set(QT_GUI src/qt_gui/main_window_ui.h src/qt_gui/main_window.cpp src/qt_gui/main_window.h - src/qt_gui/gui_settings.cpp - src/qt_gui/gui_settings.h - src/qt_gui/gui_save.h src/qt_gui/gui_context_menus.h src/qt_gui/game_list_utils.h src/qt_gui/game_info.cpp @@ -457,11 +462,14 @@ set(QT_GUI src/qt_gui/game_install_dialog.h src/qt_gui/pkg_viewer.cpp src/qt_gui/pkg_viewer.h - src/qt_gui/settings.cpp - src/qt_gui/settings.h + src/qt_gui/trophy_viewer.cpp + src/qt_gui/trophy_viewer.h + src/qt_gui/elf_viewer.cpp + src/qt_gui/elf_viewer.h src/qt_gui/main_window_themes.cpp src/qt_gui/main_window_themes.h src/qt_gui/main.cpp + ${EMULATOR} ${RESOURCE_FILES} ) endif() @@ -475,8 +483,7 @@ if (ENABLE_QT_GUI) ${CORE} ${SHADER_RECOMPILER} ${VIDEO_CORE} - src/sdl_window.h - src/sdl_window.cpp + ${EMULATOR} ) else() add_executable(shadps4 @@ -486,11 +493,8 @@ else() ${CORE} ${SHADER_RECOMPILER} ${VIDEO_CORE} + ${EMULATOR} src/main.cpp - src/emulator.cpp - src/emulator.h - src/sdl_window.h - src/sdl_window.cpp ) endif() @@ -548,7 +552,7 @@ target_include_directories(shadps4 PRIVATE ${HOST_SHADERS_INCLUDE}) if (ENABLE_QT_GUI) set_target_properties(shadps4 PROPERTIES - WIN32_EXECUTABLE ON +# WIN32_EXECUTABLE ON MACOSX_BUNDLE ON) endif() diff --git a/src/common/config.cpp b/src/common/config.cpp index 63dd4c136..a577b143a 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -24,6 +24,23 @@ static bool shouldDumpShaders = false; static bool shouldDumpPM4 = false; static bool vkValidation = false; static bool vkValidationSync = false; +// Gui +std::string settings_install_dir = ""; +u32 main_window_geometry_x = 400; +u32 main_window_geometry_y = 400; +u32 main_window_geometry_w = 1280; +u32 main_window_geometry_h = 720; +u32 mw_themes = 0; +u32 m_icon_size = 36; +u32 m_icon_size_grid = 69; +u32 m_slider_pos = 0; +u32 m_slider_pos_grid = 0; +u32 m_table_mode = 0; +u32 m_window_size_W = 1280; +u32 m_window_size_H = 720; +std::vector<std::string> m_pkg_viewer; +std::vector<std::string> m_elf_viewer; +std::vector<std::string> m_recent_files; bool isLleLibc() { return isLibc; @@ -85,6 +102,101 @@ bool vkValidationSyncEnabled() { return vkValidationSync; } +void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) { + main_window_geometry_x = x; + main_window_geometry_y = y; + main_window_geometry_w = w; + main_window_geometry_h = h; +} +void setGameInstallDir(const std::string& dir) { + settings_install_dir = dir; +} +void setMainWindowTheme(u32 theme) { + mw_themes = theme; +} +void setIconSize(u32 size) { + m_icon_size = size; +} +void setIconSizeGrid(u32 size) { + m_icon_size_grid = size; +} +void setSliderPositon(u32 pos) { + m_slider_pos = pos; +} +void setSliderPositonGrid(u32 pos) { + m_slider_pos_grid = pos; +} +void setTableMode(u32 mode) { + m_table_mode = mode; +} +void setMainWindowWidth(u32 width) { + m_window_size_W = width; +} +void setMainWindowHeight(u32 height) { + m_window_size_H = height; +} +void setPkgViewer(std::vector<std::string> pkgList) { + m_pkg_viewer.resize(pkgList.size()); + m_pkg_viewer = pkgList; +} +void setElfViewer(std::vector<std::string> elfList) { + m_elf_viewer.resize(elfList.size()); + m_elf_viewer = elfList; +} +void setRecentFiles(std::vector<std::string> recentFiles) { + m_recent_files.resize(recentFiles.size()); + m_recent_files = recentFiles; +} + +u32 getMainWindowGeometryX() { + return main_window_geometry_x; +} +u32 getMainWindowGeometryY() { + return main_window_geometry_y; +} +u32 getMainWindowGeometryW() { + return main_window_geometry_w; +} +u32 getMainWindowGeometryH() { + return main_window_geometry_h; +} +std::string getGameInstallDir() { + return settings_install_dir; +} +u32 getMainWindowTheme() { + return mw_themes; +} +u32 getIconSize() { + return m_icon_size; +} +u32 getIconSizeGrid() { + return m_icon_size_grid; +} +u32 getSliderPositon() { + return m_slider_pos; +} +u32 getSliderPositonGrid() { + return m_slider_pos_grid; +} +u32 getTableMode() { + return m_table_mode; +} +u32 getMainWindowWidth() { + return m_window_size_W; +} +u32 getMainWindowHeight() { + return m_window_size_H; +} +std::vector<std::string> getPkgViewer() { + return m_pkg_viewer; +} +std::vector<std::string> getElfViewer() { + return m_elf_viewer; +} +std::vector<std::string> getRecentFiles() { + return m_recent_files; +} + void load(const std::filesystem::path& path) { // If the configuration file does not exist, create it and return std::error_code error; @@ -152,6 +264,29 @@ void load(const std::filesystem::path& path) { isLibc = toml::find_or<toml::boolean>(lle, "libc", true); } } + if (data.contains("GUI")) { + auto guiResult = toml::expect<toml::value>(data.at("GUI")); + if (guiResult.is_ok()) { + auto gui = guiResult.unwrap(); + + m_icon_size = toml::find_or<toml::integer>(gui, "iconSize", 0); + m_icon_size_grid = toml::find_or<toml::integer>(gui, "iconSizeGrid", 0); + m_slider_pos = toml::find_or<toml::integer>(gui, "sliderPos", 0); + m_slider_pos_grid = toml::find_or<toml::integer>(gui, "sliderPosGrid", 0); + mw_themes = toml::find_or<toml::integer>(gui, "theme", 0); + m_window_size_W = toml::find_or<toml::integer>(gui, "mw_width", 0); + m_window_size_H = toml::find_or<toml::integer>(gui, "mw_height", 0); + settings_install_dir = toml::find_or<toml::string>(gui, "installDir", ""); + main_window_geometry_x = toml::find_or<toml::integer>(gui, "geometry_x", 0); + main_window_geometry_y = toml::find_or<toml::integer>(gui, "geometry_y", 0); + main_window_geometry_w = toml::find_or<toml::integer>(gui, "geometry_w", 0); + main_window_geometry_h = toml::find_or<toml::integer>(gui, "geometry_h", 0); + m_pkg_viewer = toml::find_or<std::vector<std::string>>(gui, "pkgDirs", {}); + m_elf_viewer = toml::find_or<std::vector<std::string>>(gui, "elfDirs", {}); + m_recent_files = toml::find_or<std::vector<std::string>>(gui, "recentFiles", {}); + m_table_mode = toml::find_or<toml::integer>(gui, "gameTableMode", 0); + } + } } void save(const std::filesystem::path& path) { toml::basic_value<toml::preserve_comments> data; @@ -187,6 +322,22 @@ void save(const std::filesystem::path& path) { data["Vulkan"]["validation_sync"] = vkValidationSync; data["Debug"]["DebugDump"] = isDebugDump; data["LLE"]["libc"] = isLibc; + data["GUI"]["theme"] = mw_themes; + data["GUI"]["iconSize"] = m_icon_size; + data["GUI"]["sliderPos"] = m_slider_pos; + data["GUI"]["iconSizeGrid"] = m_icon_size_grid; + data["GUI"]["sliderPosGrid"] = m_slider_pos_grid; + data["GUI"]["gameTableMode"] = m_table_mode; + data["GUI"]["mw_width"] = m_window_size_W; + data["GUI"]["mw_height"] = m_window_size_H; + data["GUI"]["installDir"] = settings_install_dir; + data["GUI"]["geometry_x"] = main_window_geometry_x; + data["GUI"]["geometry_y"] = main_window_geometry_y; + data["GUI"]["geometry_w"] = main_window_geometry_w; + data["GUI"]["geometry_h"] = main_window_geometry_h; + data["GUI"]["pkgDirs"] = m_pkg_viewer; + data["GUI"]["elfDirs"] = m_elf_viewer; + data["GUI"]["recentFiles"] = m_recent_files; std::ofstream file(path, std::ios::out); file << data; diff --git a/src/common/config.h b/src/common/config.h index 7af028dcb..c41c8c294 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -5,6 +5,7 @@ #include <filesystem> #include "types.h" +#include <vector> namespace Config { void load(const std::filesystem::path& path); @@ -29,4 +30,36 @@ bool dumpPM4(); bool vkValidationEnabled(); bool vkValidationSyncEnabled(); +// Gui +void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h); +void setGameInstallDir(const std::string& dir); +void setMainWindowTheme(u32 theme); +void setIconSize(u32 size); +void setIconSizeGrid(u32 size); +void setSliderPositon(u32 pos); +void setSliderPositonGrid(u32 pos); +void setTableMode(u32 mode); +void setMainWindowWidth(u32 width); +void setMainWindowHeight(u32 height); +void setPkgViewer(std::vector<std::string> pkgList); +void setElfViewer(std::vector<std::string> elfList); +void setRecentFiles(std::vector<std::string> recentFiles); + +u32 getMainWindowGeometryX(); +u32 getMainWindowGeometryY(); +u32 getMainWindowGeometryW(); +u32 getMainWindowGeometryH(); +std::string getGameInstallDir(); +u32 getMainWindowTheme(); +u32 getIconSize(); +u32 getIconSizeGrid(); +u32 getSliderPositon(); +u32 getSliderPositonGrid(); +u32 getTableMode(); +u32 getMainWindowWidth(); +u32 getMainWindowHeight(); +std::vector<std::string> getPkgViewer(); +std::vector<std::string> getElfViewer(); +std::vector<std::string> getRecentFiles(); + }; // namespace Config diff --git a/src/common/io_file.h b/src/common/io_file.h index 59cfcf7b5..6beeb7943 100644 --- a/src/common/io_file.h +++ b/src/common/io_file.h @@ -201,6 +201,11 @@ public: return WriteSpan(string); } + static void WriteBytes(const std::filesystem::path path, std::span<u8> vec) { + IOFile out(path, FileAccessMode::Write); + out.Write(vec); + } + private: std::filesystem::path file_path; FileAccessMode file_access_mode{}; diff --git a/src/core/crypto/crypto.cpp b/src/core/crypto/crypto.cpp index d45b56519..630faa344 100644 --- a/src/core/crypto/crypto.cpp +++ b/src/core/crypto/crypto.cpp @@ -4,67 +4,69 @@ #include <array> #include "crypto.h" -RSA::PrivateKey Crypto::key_pkg_derived_key3_keyset_init() { - InvertibleRSAFunction params; - params.SetPrime1(Integer(pkg_derived_key3_keyset.Prime1, 0x80)); - params.SetPrime2(Integer(pkg_derived_key3_keyset.Prime2, 0x80)); +CryptoPP::RSA::PrivateKey Crypto::key_pkg_derived_key3_keyset_init() { + CryptoPP::InvertibleRSAFunction params; + params.SetPrime1(CryptoPP::Integer(pkg_derived_key3_keyset.Prime1, 0x80)); + params.SetPrime2(CryptoPP::Integer(pkg_derived_key3_keyset.Prime2, 0x80)); - params.SetPublicExponent(Integer(pkg_derived_key3_keyset.PublicExponent, 4)); - params.SetPrivateExponent(Integer(pkg_derived_key3_keyset.PrivateExponent, 0x100)); + params.SetPublicExponent(CryptoPP::Integer(pkg_derived_key3_keyset.PublicExponent, 4)); + params.SetPrivateExponent(CryptoPP::Integer(pkg_derived_key3_keyset.PrivateExponent, 0x100)); - params.SetModPrime1PrivateExponent(Integer(pkg_derived_key3_keyset.Exponent1, 0x80)); - params.SetModPrime2PrivateExponent(Integer(pkg_derived_key3_keyset.Exponent2, 0x80)); + params.SetModPrime1PrivateExponent(CryptoPP::Integer(pkg_derived_key3_keyset.Exponent1, 0x80)); + params.SetModPrime2PrivateExponent(CryptoPP::Integer(pkg_derived_key3_keyset.Exponent2, 0x80)); - params.SetModulus(Integer(pkg_derived_key3_keyset.Modulus, 0x100)); + params.SetModulus(CryptoPP::Integer(pkg_derived_key3_keyset.Modulus, 0x100)); params.SetMultiplicativeInverseOfPrime2ModPrime1( - Integer(pkg_derived_key3_keyset.Coefficient, 0x80)); + CryptoPP::Integer(pkg_derived_key3_keyset.Coefficient, 0x80)); - RSA::PrivateKey privateKey(params); + CryptoPP::RSA::PrivateKey privateKey(params); return privateKey; } -RSA::PrivateKey Crypto::FakeKeyset_keyset_init() { - InvertibleRSAFunction params; - params.SetPrime1(Integer(FakeKeyset_keyset.Prime1, 0x80)); - params.SetPrime2(Integer(FakeKeyset_keyset.Prime2, 0x80)); +CryptoPP::RSA::PrivateKey Crypto::FakeKeyset_keyset_init() { + CryptoPP::InvertibleRSAFunction params; + params.SetPrime1(CryptoPP::Integer(FakeKeyset_keyset.Prime1, 0x80)); + params.SetPrime2(CryptoPP::Integer(FakeKeyset_keyset.Prime2, 0x80)); - params.SetPublicExponent(Integer(FakeKeyset_keyset.PublicExponent, 4)); - params.SetPrivateExponent(Integer(FakeKeyset_keyset.PrivateExponent, 0x100)); + params.SetPublicExponent(CryptoPP::Integer(FakeKeyset_keyset.PublicExponent, 4)); + params.SetPrivateExponent(CryptoPP::Integer(FakeKeyset_keyset.PrivateExponent, 0x100)); - params.SetModPrime1PrivateExponent(Integer(FakeKeyset_keyset.Exponent1, 0x80)); - params.SetModPrime2PrivateExponent(Integer(FakeKeyset_keyset.Exponent2, 0x80)); + params.SetModPrime1PrivateExponent(CryptoPP::Integer(FakeKeyset_keyset.Exponent1, 0x80)); + params.SetModPrime2PrivateExponent(CryptoPP::Integer(FakeKeyset_keyset.Exponent2, 0x80)); - params.SetModulus(Integer(FakeKeyset_keyset.Modulus, 0x100)); - params.SetMultiplicativeInverseOfPrime2ModPrime1(Integer(FakeKeyset_keyset.Coefficient, 0x80)); + params.SetModulus(CryptoPP::Integer(FakeKeyset_keyset.Modulus, 0x100)); + params.SetMultiplicativeInverseOfPrime2ModPrime1( + CryptoPP::Integer(FakeKeyset_keyset.Coefficient, 0x80)); - RSA::PrivateKey privateKey(params); + CryptoPP::RSA::PrivateKey privateKey(params); return privateKey; } -RSA::PrivateKey Crypto::DebugRifKeyset_init() { - AutoSeededRandomPool rng; - InvertibleRSAFunction params; - params.SetPrime1(Integer(DebugRifKeyset_keyset.Prime1, sizeof(DebugRifKeyset_keyset.Prime1))); - params.SetPrime2(Integer(DebugRifKeyset_keyset.Prime2, sizeof(DebugRifKeyset_keyset.Prime2))); +CryptoPP::RSA::PrivateKey Crypto::DebugRifKeyset_init() { + CryptoPP::InvertibleRSAFunction params; + params.SetPrime1( + CryptoPP::Integer(DebugRifKeyset_keyset.Prime1, sizeof(DebugRifKeyset_keyset.Prime1))); + params.SetPrime2( + CryptoPP::Integer(DebugRifKeyset_keyset.Prime2, sizeof(DebugRifKeyset_keyset.Prime2))); - params.SetPublicExponent(Integer(DebugRifKeyset_keyset.PrivateExponent, - sizeof(DebugRifKeyset_keyset.PrivateExponent))); - params.SetPrivateExponent(Integer(DebugRifKeyset_keyset.PrivateExponent, - sizeof(DebugRifKeyset_keyset.PrivateExponent))); + params.SetPublicExponent(CryptoPP::Integer(DebugRifKeyset_keyset.PrivateExponent, + sizeof(DebugRifKeyset_keyset.PrivateExponent))); + params.SetPrivateExponent(CryptoPP::Integer(DebugRifKeyset_keyset.PrivateExponent, + sizeof(DebugRifKeyset_keyset.PrivateExponent))); - params.SetModPrime1PrivateExponent( - Integer(DebugRifKeyset_keyset.Exponent1, sizeof(DebugRifKeyset_keyset.Exponent1))); - params.SetModPrime2PrivateExponent( - Integer(DebugRifKeyset_keyset.Exponent2, sizeof(DebugRifKeyset_keyset.Exponent2))); + params.SetModPrime1PrivateExponent(CryptoPP::Integer(DebugRifKeyset_keyset.Exponent1, + sizeof(DebugRifKeyset_keyset.Exponent1))); + params.SetModPrime2PrivateExponent(CryptoPP::Integer(DebugRifKeyset_keyset.Exponent2, + sizeof(DebugRifKeyset_keyset.Exponent2))); params.SetModulus( - Integer(DebugRifKeyset_keyset.Modulus, sizeof(DebugRifKeyset_keyset.Modulus))); - params.SetMultiplicativeInverseOfPrime2ModPrime1( - Integer(DebugRifKeyset_keyset.Coefficient, sizeof(DebugRifKeyset_keyset.Coefficient))); + CryptoPP::Integer(DebugRifKeyset_keyset.Modulus, sizeof(DebugRifKeyset_keyset.Modulus))); + params.SetMultiplicativeInverseOfPrime2ModPrime1(CryptoPP::Integer( + DebugRifKeyset_keyset.Coefficient, sizeof(DebugRifKeyset_keyset.Coefficient))); - RSA::PrivateKey privateKey(params); + CryptoPP::RSA::PrivateKey privateKey(params); return privateKey; } @@ -73,21 +75,21 @@ void Crypto::RSA2048Decrypt(std::span<CryptoPP::byte, 32> dec_key, std::span<const CryptoPP::byte, 256> ciphertext, bool is_dk3) { // RSAES_PKCS1v15_ // Create an RSA decryptor - RSA::PrivateKey privateKey; + CryptoPP::RSA::PrivateKey privateKey; if (is_dk3) { privateKey = key_pkg_derived_key3_keyset_init(); } else { privateKey = FakeKeyset_keyset_init(); } - RSAES_PKCS1v15_Decryptor rsaDecryptor(privateKey); + CryptoPP::RSAES_PKCS1v15_Decryptor rsaDecryptor(privateKey); // Allocate memory for the decrypted data std::array<CryptoPP::byte, 256> decrypted; // Perform the decryption - AutoSeededRandomPool rng; - DecodingResult result = + CryptoPP::AutoSeededRandomPool rng; + CryptoPP::DecodingResult result = rsaDecryptor.Decrypt(rng, ciphertext.data(), decrypted.size(), decrypted.data()); std::copy(decrypted.begin(), decrypted.begin() + dec_key.size(), dec_key.begin()); } @@ -120,6 +122,47 @@ void Crypto::aesCbcCfb128Decrypt(std::span<const CryptoPP::byte, 32> ivkey, } } +void Crypto::aesCbcCfb128DecryptEntry(std::span<const CryptoPP::byte, 32> ivkey, + std::span<CryptoPP::byte> ciphertext, + std::span<CryptoPP::byte> decrypted) { + std::array<CryptoPP::byte, CryptoPP::AES::DEFAULT_KEYLENGTH> key; + std::array<CryptoPP::byte, CryptoPP::AES::DEFAULT_KEYLENGTH> iv; + + std::copy(ivkey.begin() + 16, ivkey.begin() + 16 + key.size(), key.begin()); + std::copy(ivkey.begin(), ivkey.begin() + iv.size(), iv.begin()); + + CryptoPP::AES::Decryption aesDecryption(key.data(), CryptoPP::AES::DEFAULT_KEYLENGTH); + CryptoPP::CBC_Mode_ExternalCipher::Decryption cbcDecryption(aesDecryption, iv.data()); + + for (size_t i = 0; i < decrypted.size(); i += CryptoPP::AES::BLOCKSIZE) { + cbcDecryption.ProcessData(decrypted.data() + i, ciphertext.data() + i, + CryptoPP::AES::BLOCKSIZE); + } +} + +void Crypto::decryptEFSM(std::span<CryptoPP::byte, 16> NPcommID, + std::span<CryptoPP::byte, 16> efsmIv, std::span<CryptoPP::byte> ciphertext, + std::span<CryptoPP::byte> decrypted) { + + std::vector<CryptoPP::byte> TrophyKey = {0x21, 0xF4, 0x1A, 0x6B, 0xAD, 0x8A, 0x1D, 0x3E, + 0xCA, 0x7A, 0xD5, 0x86, 0xC1, 0x01, 0xB7, 0xA9}; + std::vector<CryptoPP::byte> TrophyIV = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + // step 1: Encrypt NPcommID + CryptoPP::CBC_Mode<CryptoPP::AES>::Encryption encrypt; + encrypt.SetKeyWithIV(TrophyKey.data(), TrophyKey.size(), TrophyIV.data()); + + std::vector<CryptoPP::byte> trpKey(16); + + encrypt.ProcessData(trpKey.data(), NPcommID.data(), 16); + // step 2: decrypt efsm. + CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption decrypt; + decrypt.SetKeyWithIV(trpKey.data(), trpKey.size(), efsmIv.data()); + + for (size_t i = 0; i < decrypted.size(); i += CryptoPP::AES::BLOCKSIZE) { + decrypt.ProcessData(decrypted.data() + i, ciphertext.data() + i, CryptoPP::AES::BLOCKSIZE); + } +} + void Crypto::PfsGenCryptoKey(std::span<const CryptoPP::byte, 32> ekpfs, std::span<const CryptoPP::byte, 16> seed, std::span<CryptoPP::byte, 16> dataKey, @@ -151,8 +194,8 @@ void Crypto::decryptPFS(std::span<const CryptoPP::byte, 16> dataKey, // Start at 0x10000 to keep the header when decrypting the whole pfs_image. for (int i = 0; i < src_image.size(); i += 0x1000) { const u64 current_sector = sector + (i / 0x1000); - CryptoPP::ECB_Mode<AES>::Encryption encrypt(tweakKey.data(), tweakKey.size()); - CryptoPP::ECB_Mode<AES>::Decryption decrypt(dataKey.data(), dataKey.size()); + CryptoPP::ECB_Mode<CryptoPP::AES>::Encryption encrypt(tweakKey.data(), tweakKey.size()); + CryptoPP::ECB_Mode<CryptoPP::AES>::Decryption decrypt(dataKey.data(), dataKey.size()); std::array<CryptoPP::byte, 16> tweak{}; std::array<CryptoPP::byte, 16> encryptedTweak; diff --git a/src/core/crypto/crypto.h b/src/core/crypto/crypto.h index 11edef843..2f9260548 100644 --- a/src/core/crypto/crypto.h +++ b/src/core/crypto/crypto.h @@ -15,17 +15,15 @@ #include "common/types.h" #include "keys.h" -using namespace CryptoPP; - class Crypto { public: PkgDerivedKey3Keyset pkg_derived_key3_keyset; FakeKeyset FakeKeyset_keyset; DebugRifKeyset DebugRifKeyset_keyset; - RSA::PrivateKey key_pkg_derived_key3_keyset_init(); - RSA::PrivateKey FakeKeyset_keyset_init(); - RSA::PrivateKey DebugRifKeyset_init(); + CryptoPP::RSA::PrivateKey key_pkg_derived_key3_keyset_init(); + CryptoPP::RSA::PrivateKey FakeKeyset_keyset_init(); + CryptoPP::RSA::PrivateKey DebugRifKeyset_init(); void RSA2048Decrypt(std::span<CryptoPP::byte, 32> dk3, std::span<const CryptoPP::byte, 256> ciphertext, @@ -35,6 +33,11 @@ public: void aesCbcCfb128Decrypt(std::span<const CryptoPP::byte, 32> ivkey, std::span<const CryptoPP::byte, 256> ciphertext, std::span<CryptoPP::byte, 256> decrypted); + void aesCbcCfb128DecryptEntry(std::span<const CryptoPP::byte, 32> ivkey, + std::span<CryptoPP::byte> ciphertext, + std::span<CryptoPP::byte> decrypted); + void decryptEFSM(std::span<CryptoPP::byte, 16>, std::span<CryptoPP::byte, 16> efsmIv, + std::span<CryptoPP::byte> ciphertext, std::span<CryptoPP::byte> decrypted); void PfsGenCryptoKey(std::span<const CryptoPP::byte, 32> ekpfs, std::span<const CryptoPP::byte, 16> seed, std::span<CryptoPP::byte, 16> dataKey, diff --git a/src/core/crypto/keys.h b/src/core/crypto/keys.h index 5b8a88622..1c7ddfbac 100644 --- a/src/core/crypto/keys.h +++ b/src/core/crypto/keys.h @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include <rsa.h> class FakeKeyset { public: diff --git a/src/core/file_format/pkg.cpp b/src/core/file_format/pkg.cpp index 2251402d0..5150e1286 100644 --- a/src/core/file_format/pkg.cpp +++ b/src/core/file_format/pkg.cpp @@ -45,26 +45,54 @@ PKG::PKG() = default; PKG::~PKG() = default; -bool PKG::Open(const std::string& filepath) { +bool PKG::Open(const std::filesystem::path& filepath) { Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read); if (!file.IsOpen()) { return false; } pkgSize = file.GetSize(); - PKGHeader pkgheader; file.Read(pkgheader); + if (pkgheader.magic != 0x7F434E54) + return false; + + for (const auto& flag : flagNames) { + if (isFlagSet(pkgheader.pkg_content_flags, flag.first)) { + if (!pkgFlags.empty()) + pkgFlags += (", "); + pkgFlags += (flag.second); + } + } // Find title id it is part of pkg_content_id starting at offset 0x40 file.Seek(0x47); // skip first 7 characters of content_id file.Read(pkgTitleID); + file.Seek(0); + pkg.resize(pkgheader.pkg_promote_size); + file.Read(pkg); + + u32 offset = pkgheader.pkg_table_entry_offset; + u32 n_files = pkgheader.pkg_table_entry_count; + for (int i = 0; i < n_files; i++) { + PKGEntry entry; + std::memcpy(&entry, &pkg[offset + i * 0x20], sizeof(entry)); + + // Try to figure out the name + const auto name = GetEntryNameByType(entry.id); + if (name == "param.sfo") { + sfo.clear(); + file.Seek(entry.offset); + sfo.resize(entry.size); + file.ReadRaw<u8>(sfo.data(), entry.size); + } + } file.Close(); return true; } -bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extract, +bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::path& extract, std::string& failreason) { extract_path = extract; pkgpath = filepath; @@ -75,6 +103,9 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr pkgSize = file.GetSize(); file.ReadRaw<u8>(&pkgheader, sizeof(PKGHeader)); + if (pkgheader.magic != 0x7F434E54) + return false; + if (pkgheader.pkg_size > pkgSize) { failreason = "PKG file size is different"; return false; @@ -90,6 +121,7 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr u32 offset = pkgheader.pkg_table_entry_offset; u32 n_files = pkgheader.pkg_table_entry_count; + std::array<u8, 64> concatenated_ivkey_dk3; std::array<u8, 32> seed_digest; std::array<std::array<u8, 32>, 7> digest1; std::array<std::array<u8, 256>, 7> key1; @@ -101,6 +133,9 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr // Try to figure out the name const auto name = GetEntryNameByType(entry.id); + const auto filepath = extract_path / "sce_sys" / name; + std::filesystem::create_directories(filepath.parent_path()); + if (name.empty()) { // Just print with id Common::FS::IOFile out(extract_path / "sce_sys" / std::to_string(entry.id), @@ -110,9 +145,6 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr continue; } - const auto filepath = extract_path / "sce_sys" / name; - std::filesystem::create_directories(filepath.parent_path()); - if (entry.id == 0x1) { // DIGESTS, seek; // file.Seek(entry.offset, fsSeekSet); } else if (entry.id == 0x10) { // ENTRY_KEYS, seek; @@ -133,7 +165,6 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr file.Read(imgkeydata); // The Concatenated iv + dk3 imagekey for HASH256 - std::array<CryptoPP::byte, 64> concatenated_ivkey_dk3; std::memcpy(concatenated_ivkey_dk3.data(), &entry, sizeof(entry)); std::memcpy(concatenated_ivkey_dk3.data() + sizeof(entry), dk3_.data(), sizeof(dk3_)); @@ -150,10 +181,33 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr Common::FS::IOFile out(extract_path / "sce_sys" / name, Common::FS::FileAccessMode::Write); out.WriteRaw<u8>(pkg.data() + entry.offset, entry.size); out.Close(); + + // Decrypt Np stuff and overwrite. + if (entry.id == 0x400 || entry.id == 0x401 || entry.id == 0x402 || + entry.id == 0x403) { // somehow 0x401 is not decrypting + decNp.resize(entry.size); + std::span<u8> cipherNp(pkg.data() + entry.offset, entry.size); + std::array<u8, 64> concatenated_ivkey_dk3_; + std::memcpy(concatenated_ivkey_dk3_.data(), &entry, sizeof(entry)); + std::memcpy(concatenated_ivkey_dk3_.data() + sizeof(entry), dk3_.data(), sizeof(dk3_)); + PKG::crypto.ivKeyHASH256(concatenated_ivkey_dk3_, ivKey); + PKG::crypto.aesCbcCfb128DecryptEntry(ivKey, cipherNp, decNp); + + Common::FS::IOFile out(extract_path / "sce_sys" / name, + Common::FS::FileAccessMode::Write); + out.Write(decNp); + out.Close(); + } + } + + // Extract trophy files + if (!trp.Extract(extract_path)) { + // Do nothing some pkg come with no trp file. + // return false; } // Read the seed - std::array<CryptoPP::byte, 16> seed; + std::array<u8, 16> seed; file.Seek(pkgheader.pfs_image_offset + 0x370); file.Read(seed); @@ -165,7 +219,7 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr std::vector<u8> pfs_encrypted(length); file.Seek(pkgheader.pfs_image_offset); file.Read(pfs_encrypted); - + file.Close(); // Decrypt the pfs_image. std::vector<u8> pfs_decrypted(length); PKG::crypto.decryptPFS(dataKey, tweakKey, pfs_encrypted, pfs_decrypted, 0); diff --git a/src/core/file_format/pkg.h b/src/core/file_format/pkg.h index bf2ce891e..f77a78046 100644 --- a/src/core/file_format/pkg.h +++ b/src/core/file_format/pkg.h @@ -12,6 +12,7 @@ #include "common/endian.h" #include "core/crypto/crypto.h" #include "pfs.h" +#include "trp.h" struct PKGHeader { u32_be magic; // Magic @@ -103,11 +104,13 @@ public: PKG(); ~PKG(); - bool Open(const std::string& filepath); + bool Open(const std::filesystem::path& filepath); void ExtractFiles(const int& index); - bool Extract(const std::string& filepath, const std::filesystem::path& extract, + bool Extract(const std::filesystem::path& filepath, const std::filesystem::path& extract, std::string& failreason); + std::vector<u8> sfo; + u32 GetNumberOfFiles() { return fsTable.size(); } @@ -116,16 +119,42 @@ public: return pkgSize; } + std::string GetPkgFlags() { + return pkgFlags; + } + std::string_view GetTitleID() { return std::string_view(pkgTitleID, 9); } + PKGHeader GetPkgHeader() { + return pkgheader; + } + + static bool isFlagSet(u32_be variable, PKGContentFlag flag) { + return (variable) & static_cast<u32>(flag); + } + + static constexpr std::array<std::pair<PKGContentFlag, std::string_view>, 10> flagNames = { + {{PKGContentFlag::FIRST_PATCH, "FIRST_PATCH"}, + {PKGContentFlag::PATCHGO, "PATCHGO"}, + {PKGContentFlag::REMASTER, "REMASTER"}, + {PKGContentFlag::PS_CLOUD, "PS_CLOUD"}, + {PKGContentFlag::GD_AC, "GD_AC"}, + {PKGContentFlag::NON_GAME, "NON_GAME"}, + {PKGContentFlag::UNKNOWN_0x8000000, "UNKNOWN_0x8000000"}, + {PKGContentFlag::SUBSEQUENT_PATCH, "SUBSEQUENT_PATCH"}, + {PKGContentFlag::DELTA_PATCH, "DELTA_PATCH"}, + {PKGContentFlag::CUMULATIVE_PATCH, "CUMULATIVE_PATCH"}}}; + private: Crypto crypto; + TRP trp; std::vector<u8> pkg; u64 pkgSize = 0; char pkgTitleID[9]; PKGHeader pkgheader; + std::string pkgFlags; std::unordered_map<int, std::filesystem::path> extractPaths; std::vector<pfs_fs_table> fsTable; @@ -133,12 +162,13 @@ private: std::vector<u64> sectorMap; u64 pfsc_offset; - std::array<CryptoPP::byte, 32> dk3_; - std::array<CryptoPP::byte, 32> ivKey; - std::array<CryptoPP::byte, 256> imgKey; - std::array<CryptoPP::byte, 32> ekpfsKey; - std::array<CryptoPP::byte, 16> dataKey; - std::array<CryptoPP::byte, 16> tweakKey; + std::array<u8, 32> dk3_; + std::array<u8, 32> ivKey; + std::array<u8, 256> imgKey; + std::array<u8, 32> ekpfsKey; + std::array<u8, 16> dataKey; + std::array<u8, 16> tweakKey; + std::vector<u8> decNp; std::filesystem::path pkgpath; std::filesystem::path current_dir; diff --git a/src/core/file_format/psf.cpp b/src/core/file_format/psf.cpp index fb808697e..4a7f62159 100644 --- a/src/core/file_format/psf.cpp +++ b/src/core/file_format/psf.cpp @@ -12,16 +12,22 @@ PSF::PSF() = default; PSF::~PSF() = default; -bool PSF::open(const std::string& filepath) { - Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read); - if (!file.IsOpen()) { - return false; - } +bool PSF::open(const std::string& filepath, std::vector<u8> psfBuffer) { + if (!psfBuffer.empty()) { + psf.resize(psfBuffer.size()); + psf = psfBuffer; + } else { + Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read); + if (!file.IsOpen()) { + return false; + } - const u64 psfSize = file.GetSize(); - psf.resize(psfSize); - file.Seek(0); - file.Read(psf); + const u64 psfSize = file.GetSize(); + psf.resize(psfSize); + file.Seek(0); + file.Read(psf); + file.Close(); + } // Parse file contents PSFHeader header; diff --git a/src/core/file_format/psf.h b/src/core/file_format/psf.h index 319786300..17e515de2 100644 --- a/src/core/file_format/psf.h +++ b/src/core/file_format/psf.h @@ -35,7 +35,7 @@ public: PSF(); ~PSF(); - bool open(const std::string& filepath); + bool open(const std::string& filepath, std::vector<u8> psfBuffer); std::string GetString(const std::string& key); u32 GetInteger(const std::string& key); diff --git a/src/core/file_format/trp.cpp b/src/core/file_format/trp.cpp new file mode 100644 index 000000000..cb55af2eb --- /dev/null +++ b/src/core/file_format/trp.cpp @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "trp.h" + +TRP::TRP() = default; +TRP::~TRP() = default; + +void TRP::GetNPcommID(std::filesystem::path trophyPath, int index) { + std::filesystem::path trpPath = trophyPath / "sce_sys/npbind.dat"; + Common::FS::IOFile npbindFile(trpPath, Common::FS::FileAccessMode::Read); + if (!npbindFile.IsOpen()) { + return; + } + npbindFile.Seek(0x84 + (index * 0x180)); + npbindFile.ReadRaw<u8>(np_comm_id.data(), 12); + std::fill(np_comm_id.begin() + 12, np_comm_id.end(), 0); // fill with 0, we need 16 bytes. +} + +static void removePadding(std::vector<u8>& vec) { + for (auto it = vec.rbegin(); it != vec.rend(); ++it) { + if (*it == '>') { + size_t pos = std::distance(vec.begin(), it.base()); + vec.resize(pos); + break; + } + } +} + +bool TRP::Extract(std::filesystem::path trophyPath) { + std::string title = trophyPath.filename().string(); + std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/"; + if (!std::filesystem::exists(gameSysDir)) { + return false; + } + for (int index = 0; const auto& it : std::filesystem::directory_iterator(gameSysDir)) { + if (it.is_regular_file()) { + GetNPcommID(trophyPath, index); + + Common::FS::IOFile file(it.path(), Common::FS::FileAccessMode::Read); + if (!file.IsOpen()) { + return false; + } + + TrpHeader header; + file.Read(header); + if (header.magic != 0xDCA24D00) + return false; + + s64 seekPos = sizeof(TrpHeader); + std::filesystem::path trpFilesPath(std::filesystem::current_path() / "game_data" / + title / "TrophyFiles" / it.path().stem()); + std::filesystem::create_directories(trpFilesPath / "Icons"); + std::filesystem::create_directory(trpFilesPath / "Xml"); + + for (int i = 0; i < header.entry_num; i++) { + file.Seek(seekPos); + seekPos += (s64)header.entry_size; + TrpEntry entry; + file.Read(entry); + std::string_view name(entry.entry_name); + if (entry.flag == 0 && name.find("TROP") != std::string::npos) { // PNG + file.Seek(entry.entry_pos); + std::vector<u8> icon(entry.entry_len); + file.Read(icon); + Common::FS::IOFile::WriteBytes(trpFilesPath / "Icons" / name, icon); + } + if (entry.flag == 3 && np_comm_id[0] == 'N' && + np_comm_id[1] == 'P') { // ESFM, encrypted. + file.Seek(entry.entry_pos); + file.Read(esfmIv); // get iv key. + // Skip the first 16 bytes which are the iv key on every entry as we want a + // clean xml file. + std::vector<u8> ESFM(entry.entry_len - iv_len); + std::vector<u8> XML(entry.entry_len - iv_len); + file.Seek(entry.entry_pos + iv_len); + file.Read(ESFM); + crypto.decryptEFSM(np_comm_id, esfmIv, ESFM, XML); // decrypt + removePadding(XML); + std::string xml_name = entry.entry_name; + size_t pos = xml_name.find("ESFM"); + if (pos != std::string::npos) + xml_name.replace(pos, xml_name.length(), "XML"); + Common::FS::IOFile::WriteBytes(trpFilesPath / "Xml" / xml_name, XML); + } + } + } + index++; + } + return true; +} \ No newline at end of file diff --git a/src/core/file_format/trp.h b/src/core/file_format/trp.h new file mode 100644 index 000000000..6d1f13bd9 --- /dev/null +++ b/src/core/file_format/trp.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <vector> +#include "common/endian.h" +#include "common/io_file.h" +#include "common/types.h" +#include "core/crypto/crypto.h" + +struct TrpHeader { + u32_be magic; // (0xDCA24D00) + u32_be version; + u64_be file_size; // size of full trp file + u32_be entry_num; // num entries + u32_be entry_size; // size of entry + u32_be dev_flag; // 1: dev + unsigned char digest[20]; // sha1 hash + u32_be key_index; // 3031300? + unsigned char padding[44]; +}; + +struct TrpEntry { + char entry_name[32]; + u64_be entry_pos; + u64_be entry_len; + u32_be flag; // 3 = CONFIG/ESFM , 0 = PNG + unsigned char padding[12]; +}; + +class TRP { +public: + TRP(); + ~TRP(); + bool Extract(std::filesystem::path trophyPath); + void GetNPcommID(std::filesystem::path trophyPath, int index); + +private: + Crypto crypto; + std::vector<u8> NPcommID = std::vector<u8>(12); + std::array<u8, 16> np_comm_id{}; + std::array<u8, 16> esfmIv{}; + std::filesystem::path trpFilesPath; + static constexpr int iv_len = 16; +}; \ No newline at end of file diff --git a/src/core/loader.cpp b/src/core/loader.cpp index b12821c1b..f80bfbb81 100644 --- a/src/core/loader.cpp +++ b/src/core/loader.cpp @@ -7,7 +7,7 @@ namespace Loader { -FileTypes DetectFileType(const std::string& filepath) { +FileTypes DetectFileType(const std::filesystem::path& filepath) { // No file loaded if (filepath.empty()) { return FileTypes::Unknown; diff --git a/src/core/loader.h b/src/core/loader.h index 2f4d06513..608970dca 100644 --- a/src/core/loader.h +++ b/src/core/loader.h @@ -14,5 +14,5 @@ enum class FileTypes { Pkg, }; -FileTypes DetectFileType(const std::string& filepath); +FileTypes DetectFileType(const std::filesystem::path& filepath); } // namespace Loader diff --git a/src/emulator.cpp b/src/emulator.cpp index dd8de3a7d..793d996a1 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -65,7 +65,7 @@ void Emulator::Run(const std::filesystem::path& file) { for (const auto& entry : std::filesystem::directory_iterator(sce_sys_folder)) { if (entry.path().filename() == "param.sfo") { auto* param_sfo = Common::Singleton<PSF>::Instance(); - param_sfo->open(sce_sys_folder.string() + "/param.sfo"); + param_sfo->open(sce_sys_folder.string() + "/param.sfo", {}); std::string id(param_sfo->GetString("CONTENT_ID"), 7, 9); std::string title(param_sfo->GetString("TITLE")); LOG_INFO(Loader, "Game id: {} Title: {}", id, title); diff --git a/src/images/flag_china.png b/src/images/flag_china.png new file mode 100644 index 0000000000000000000000000000000000000000..13bf221e982aad18c472337c2056b15b1d588ff7 GIT binary patch literal 572 zcmV-C0>k}@P)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800009a7bBm000XU z000XU0RWnu7ytkP?ny*JR9J=WR?AMpKol*qg93eMA#Pn5*Czgri921HD2adJAGmVq z#zdnF6IZ%&=}t7Lpb{SPER-VTdT*J5f{-AzVgi$#w9|n(XYQPPX9Qv1X1-l{y_**c zp087n0DlSMByM3E<aQAP?=)n&gSsR^4WWlZP{pT#;A5t3oIvlU?Z2af^ZpQEGU9wN z2&Ekba%vDN*sn7Y^rU_P#|ieai3FIo$uoffBvwRdK87K^CBX;wi}<}}6IVZ5vOpqg zP6!}!3U>}CjQjnNJyf8Tj6m&HgNJ~xBcf~<aPF{x@-nXSc+n#6NAoEHm8b$`wJYKi zQAFr$8-C-3fdVM}?*S1~Q3+a!E_v}ph1WIFBIKqd(jpU)AHxd+JwTJDFx0VCe~*CV zZU~-nTPWN-BPi^J3A`{c3utv@!0dr+5gT_JG~*Fy+=t;!6QN{VWsiU{yf9Qi7AG=q zG$<XZ5Yv3{vMz#gjr~2vGNHk8-<2((6_hs++X|#H5*ZGx1^T`Um2kT-RopNkFEUlR zO`x;9Cqi<)m4Z@V8ai1Acoj5xd1aFtOaKox5$4>e1H*pU?{*$`$4l`@0&vB7w&$)L zM*VQ=@EKcxQ^)Vv0^Gt}!2c02%}eR!f~PUHYdMiy*YbIr`SuMHxe!@MCw2<}0000< KMNUMnLSTaOuI^a? literal 0 HcmV?d00001 diff --git a/src/images/flag_eu.png b/src/images/flag_eu.png new file mode 100644 index 0000000000000000000000000000000000000000..0922e11ec850c85b89e72834c980147a6ff1a624 GIT binary patch literal 942 zcmV;f15x~mP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800009a7bBm000XU z000XU0RWnu7ytkRU`a$lR9J=WR$WMwQ5aq~MG#$d(S-(G)=dRP2L5alDvMl}>D>0s z%sJ=O%s*yMH!B6FOdW~;tfoT#1<s#M&5!~UGP;RwgXqFAx`-~KZtCfrcbl%%xw%mn zdf?^zzVn^)zR!8y_c`Z?L_2NUx2V%rSyW8ZGBf;RTjsGv;RUyPZR;RJz7I*}Y|tiF zKS6?O8tHis_MC!Pn1O`#h2p~(H+TT*Sx*pe@VJag^tYx#$Ztcu!o3QDERq12L+)HG zOylhJF$#V~+lRUSSYT0v&vOT0x2Yf%Cg>j@6AU9k1K`dQlv6mDIf538j#kdC#89&W zAXYxk`drlC)treqg^R!h0udYlMx|8kM(y1)EDoQ*$Jv9hS+q#e^rFUWfWD*w4RwW3 z>bsC%+JKSHOZc&{8$HcR6qGl5m<SJmC*QK{K74<%hgMu;E@L@&K_a-Q0J%w2E{GLl zcr|>S#>_ax5G%(c2SB27VqxGE%XJ1Ubw!jC|DxmX@k%!yGX`vy9E|mxCy;?^VN(D; zsa?}Ka4~BTQq?HsR+clc*0C3aOfx`rkbI*b={h@s+6Y8o0De-l+Y~In_4FQ4^L|Xk z$|vCLNrGJ0wJOn20VHb%@w6|2a{Bf8J_baIMD=9A%I2(H)%IilQ4G!d@@zlatl3n^ zf6v>T0hVW7aigEqCN`B82f!I9uQ221%UyUsb%>Y~)4UKF;FAo-JIE@u!DKQ(mEZAa z@okg>MtQZV5{kTb)YldvHMegIDOhnhO)H{RD4nP<n%Va{AiRKiaAS298$aRMjM!L- zus&-+%TOw~k#(~b9ac47j~ru++-LPMv_^&*dOHl2)mUPiRqm}u$n)&s$<VhG^1PQ; z+`})HQ(g~)?U#`#IEe|Npqp07SxVJD#E0o<8uQk9gi-H^z(&Lii>E;`iFdBUcr$tw zw~a<h2`@^%@o0*gSI&pg?j$S>o@CDp8_F+q03^vFr0E_I5N)vyDLK8gGxF7bo8L=& zg+w*R{<RPgT}exX4kK?JtRBfT<e0Cnl%0lLYFwgm>@@T##AgvZj@JXkK~$TJQ0d!H zSycsfVQ0v<qOAk)lPCwmE!->Ff4h`MpJ`h1UCTd(akJfWaJrW7v~Ayh1L^{(CJR-~ QE&u=k07*qoM6N<$g6R0L?*IS* literal 0 HcmV?d00001 diff --git a/src/images/flag_jp.png b/src/images/flag_jp.png new file mode 100644 index 0000000000000000000000000000000000000000..6433eecfd63b88337263602ffa63e0ce1a58a207 GIT binary patch literal 371 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?3oVGw3ym^DX&fq_xg z)5S3)qBS`|f>l}0#BbK)+a`tYxE22Sr~hF}F+a=@$yd(8-1GVC@%&v$Gxjd#TX2Fg z-macGd6(6L&%68o%a?EYzdtSQ$Nwe)eu?HA3WgJ8d}Tj;Jh%7%b)PT);{~1^>vAko zFf}PWFx@}?U;K8X!<m*m%~u+(1l?skpXQ^U0pbDK+iUiAlrebdUX?9yb)K2Q4peZT z(dyL^YlaomUh(aamGaoa?2xtcU4yvyG=aAb&%4Eg?=!~l|NE!@^6lmYPZdlRwq7Y` z5U8*E_vyc{fB2ng%unrXs~x9AKDo~7aDQ*at}p-BD(wFsz9r<K#1Xbm!N<j?WF1s( zUNszi-0*IuH`^-5U;ERqUHZ?XelG6kecR@LeL$aYs$Q^Lb#cN2Muz=dxArUuklhUo OMFvk-KbLh*2~7Zeqn=X$ literal 0 HcmV?d00001 diff --git a/src/images/flag_unk.png b/src/images/flag_unk.png new file mode 100644 index 0000000000000000000000000000000000000000..5c38102826d24753b8914c762e268bb787c02651 GIT binary patch literal 257 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GG!XV7ZFl!D-#b!?z z$B>FSZ>Jb?F&T=u-2W&Nb|E_=BI<_5w73J#9rF%JS1?>W8kDPZw?=T8K+Lq>lj7X$ zY)tht*ypQ!2yfZtz!|Z~yL9i%2@CH$NtRu3X#=|mr<o<!1$MIoycVx*PH;wCU6VLh zxZr9IQ%~u>CoCfRa$4nGtS!?T))sm5ABdS5>#WDQAw2ENNmJf??-jN%%=j~#;rF#J zu>*G>Z|0T?lRRbZt6f#q)qG~N%=@B-ndv)c%XR!b7c_6rJ1L;^89ZJ6T-G@yGywo^ CbYG1C literal 0 HcmV?d00001 diff --git a/src/images/flag_us.png b/src/images/flag_us.png new file mode 100644 index 0000000000000000000000000000000000000000..f4bf3a300790b6e0fd461b0f9c82a0d7b9d2d233 GIT binary patch literal 321 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?3oVGw3ym^DWNDEQga z#W5tJH90|ob#cN1$88(D*35DInJ;(w-T#D~i`)lQKKztX`~N;AQ9*S7i-fcfsSJ_l za<`FT_*CBFV>K-Ng8WdxE0-x&1%uzZ{zVzl*W@zzmUnUN;*5zqk9Zz-jI~hx=Fo zdjpSkTMDCQ&C~!d83rD^3Ct%xTJo%RmH$`o9@?hD)6K@iK0!)IVU6EJX1B`+GNdGY znKJ?%_?Bt#HfRY=P<A-T@rm<<LBO0zhpwNs@ZHDkaPWd<vf)Wb56w3BgALXUVj@e} z6qicKu`<7FVC-*VEN55|>5^95CG|s-c|{^e${rbx(+7Jm1sZU%Nii^l>@sQdIsc#w P=zRuHS3j3^P6<r_JM3{= literal 0 HcmV?d00001 diff --git a/src/images/flag_world.png b/src/images/flag_world.png new file mode 100644 index 0000000000000000000000000000000000000000..0dcccf8002704124b067736b21fb7dd5a7d0ab64 GIT binary patch literal 2995 zcmV;k3rzHhP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00009a7bBm000XU z000XU0RWnu7ytkZWl2OqRCt{2SqrdLRT;iFw9M?G0%xtmMXbntU=IY#G%Jd1e3Ya& zAs?mDjY>s0XZ`zt;#D(6d`yw1R7R4MiCK;|kd-2#gnH%f|KAr(vLUk<;FKv&@;uG= zueJ8s_uPY!I@3(Un%Q$6d+oLU=l6gA+HGyS$L_Iv{QnP=@Ue!nPruZh{oUhlw2mhk zlJ8o_k60(`Ey-rR@ZHJ7#>scelupFlm)x(Ko?=PPZ|h#N$IBMlcK7U!%Q(5qI$phR z((~q4^9IjcZ7uPQ#>oqoq`oT`=0eeqTHnZ)n|Cf8WF0TDPI|EK&J-RnOf@|V7Pn4r zF;1RtwNb~k<*&DnFS8^YRWQffjN^waF_kI%fKB=i=%{34hhHFLh9%j%%TA_PCy60m zj=4dZ&4y(5$n(~`u6h08?y<5V`LiLuxs>!CXsdGGt_h#;v6lF6*70cT<i1iR9op7b zZ|^63t#71tB2jQlya1atuLZ#^@fWR=lZ#d?RnoUw$3KjUK=Gk7q}2cEdiw!2+1EPx zHjH7NEVYy#w{r!2$lR`ich_HI5;?<8?-?BLm#cm6u_Wul#*gE7yf>nI?OrzFPeJHX z!e6zHFR_W#jLXi{b*eI<7>ecEV@Da1--WfFDpk@^{XHy~`}y^D>v*OmzGsJmmusu{ zN75kPkwmbbA^DYY(%H}QL3m5DI11A-t#3a|*%y$+x=z6ZC6|39{>FSme5!SP6P_<Q zb|a?tzQs8CMO32biE@&jgb-G6sPr9bi61Rh(l-q0?%St@<@pMH0x_U~S|pJZnk*8+ zMwXf+Jxk|TvyYcub{Z0IfKih4y$Hp|8ok2`C<7yGEhXu=l1tAqi7Znkh#xHGgssUD z#>o@b$@SLBZ{ZC?GJRmeIDSYMCw|eA%-iq0HE$};2?<+|h*Ykn2LXK`WSnzoci(Hv zjz3u@IU7J=NoK(H_+EB$jwLy_lw=<s=2pJL5<lk!mJN1>9IT#bgp(RG4p`m8vplP^ zkYxC;F`>$zHzdCa3S0;+VVV9=sc5rxSXJH?JasZ$jAw&_AGD4q7xM)ngsb5Jgqoah z9seA8VH-ejb-`mmvrR*5^0tQYqzhg_n0gsyLoqtA`Aq9%Z6QwYHBP4F_la)|E}tJP zGRl&S$M+%C?CqB1hM~moZw{em1@iTh^cX|js~*a8iZ2LzEeLzf%R`aUiI(_&)TOqr zo;?-498b*`G)^u?^+a;u6NwY!5bzg@)K<WmpU+>(pRJ~!0J=o$_%O;&3tpXN9AAX* z_z(9~ASdT&9yp#1OL~Zu%|jDD+*DSL2=KZwPgX5#>aF<*H7JNRf(V)vUos>=vQF;s zE|lX7-kB6))rXjqG1y4&P45}32@J0!y$2d66@*|!qM`>p+VC{Y33HG4VZ@UF5XSMh z%2oMT2;&E_XNlx91tX8KPPQ15?oiD?1aAOEjz)|}Dgdh$5k-^7|F4-}(A<9ybCr4| zQbP^Un_WG7VpCJg#v(~kosHvfqQ-<=7!L$9#6Jc^7S}eERYPvvl<ze?8xGIM`h$Hn z4=l-?A=N%(oZJ?Z2_7kU;1PYOmW}l>>O%!Z4rmj@#t`2Mm;;r-I`Ehw`L!k9uDpi$ zQRDdEmSmwC%gO0LGUQtHXjGt7N#7UpU_~L3uU8|H%u_U@-Xqo6miQV#WQlqYQ=yXd zBygaD2R2|aU^uFCbZgo1JCV46;2ceg7jmhVjYWv%e5r}hS>pQvnV>H!WK|{rzK0Qa zgn8>4a`4TDcrNaT;&(Ul3j)qEB>%(>Tu5NhfJW@`CgcUGG%N+{cu%Cq!E+uKrK^R2 zS4HmtHe0e%%Br9u5fWilZ^$Kk05njO3LZ!x2ln@LhPNw(dx(;{ek$LPdc^vM0_k?R z8lKWbb<&A2(U^C9CwP8CaDK`<`2nC;4FLr4aM`TDMlEn8XNM}=1?~f-N|kJ)?paU( z9#<q(>dseS4GleQM2P=|TrebSAW#vriy;IUMI{aY&jMLPLi*%V{9fTVkvXW?P!a@B zg>2~oo(p3k**%iU2|%`1Aq21ZJeBoGVngznca1hJUL$C%g{dkN3a)$DLt5{Vx=12t zXdw$!7=+AFb_gtp5X2`0gVnJXV9z-DNAS+3*aw6rs1sg@cps~Jp2W(1ayhyh`|@@$ z1$|B#VfWg-jFXjsxS;9E0{;5&a55iFPQc+jB#h%!H>k-GV4kRuNOpJ(&uRlh{G1{A zOQ`UG>!0L;QGi{Kc#~BD$F3Ph!GC<ckh>LqK=px^8h<2nuxYs}M**LW<7e{N3prw) z_O}CDM8|)}V!9v%uBN9nlvlp9-V*;&(T?~@5HcvE^K02y?1{N(USi+vJ39q}oXSHc z^crY}LXyviSA25#>U(jBZFPBkut6nwn^tQd<NcICc){|hz;Hh-qlL<2zBcmK$s&zi zaDOLD!CGyhP1HQ;?%7-0N6&ShFEY;H!3!g#A`J81Lz9DKGZM;@To*$qa+g?*6<yF= z_<~e`O#VIUK{4TC0UYQt*%Er;HA>DvIfy%==d{%<kU8Jds?f_lJ@=4p9DlE^i7v16 z)l#lPc@Kh5FLYv?VHxeRsP`}=80tqsusiaK3Y=~<?k%sf<BuBRpQ11VnnT+-TBCvZ zMGY07yqAa1MaWUyGsN7lUjcNXsw!IeEWajr=4l{FP-b@IA*h6*8ZN^Ipal_PwI={O zb$o3&rFbAVFHP494L8RrD7@wXIxo+=zOH-DSUJQx`9tu`oe`M;-Th8V6kRH-Mg&V| z2$i4!;O}er4tkv@7YWd$5<0nQ2!TfB@XQm;5-vx%SEZWhgdzC`&_pSU%baoi%aFLs zV(5lK`PblqrN;61kr!yD4Dsjk4Yk}8--A{U*c$o1A^sB@`#28`H~tG7-3P0AI?ktp zh&8vNdL9&7PEYjm0`{UOjP>?VI~KQU>Vt>1(E{$rlL|z}fWxj3i!Y$4!GbxQ1s7^~ z03|BvsDctt#HKx*;&p<pqBvngg*}&@7(!(lk{v>VS_c8gXfnrR74RUf1KEjzf^0;= z)4AFJ$=U$jlllpt=K{%fVX<ex)s^o!*`mb_Cv_x?;Vtz<2&a&Aw-$R?V7xD6*_q0B zYUx3G&KY_K%=M7W+hf?|Xyf=I07+<7vHoa7{If#n&2A}tu7s1*^+-=SJ6{{goewSj z?{>I8<iwRZJ@BZrLgTiY9j!T4$;Ng}>pN1hJxP!84G;KI6r499VRN_+4T~Zjcu`2& z@VOSoXxrHUMMjpR_@Hd-gT!;aa(W<DFE%`CO;0xlaQ+=$j-Ln4mjYDkItd@HEn}!b z+9&wLo&)NxhYgigBX+7lz~if-K=WahvP37(f<D9|lq=rdw-53tC<#`&PA%*`v@Td) z30%K3oiMa}bG!}pp{?Jjx9bTV@x9G^?r>=9h9(4p5<d`*7;gi=H%a>CP)%=84)iJv z)97`t)T2j|8&FJ9903%#&=dc6t9C9N<P(aY*%65Y#qd!2GDqjaLCS8O^u!|8yoV*e zwB=dot`1MI(s9c3(Up;mk7t~~Wl(4oHT2OMCMY;4Qod`O3iu36bU}D?17YvNGs6m< zUw(9gL*j%#tML5lc(~q<M#G<LrsINOmmy~RkvfDGUWib32+n*UJkTT@*-U{q^=QSv zg35W4K}|v4;FJO!YK9!F1|vE56<NM{XqSYS4QLhpyRU)(atk&F9|C%`exrl4Aigv- zJ$QMtsJKqxU{3<`;k6yT(t6bfHb4l)-#`yEM-kI^`U0=5-i}T%9@pq8+y83#g8`Yh pt3u#B6}@%a?y-C99&N95{0GLNj>Z}JBiH}{002ovPDHLkV1j*Kp{oD@ literal 0 HcmV?d00001 diff --git a/src/qt_gui/elf_viewer.cpp b/src/qt_gui/elf_viewer.cpp new file mode 100644 index 000000000..1674e1ab8 --- /dev/null +++ b/src/qt_gui/elf_viewer.cpp @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <QHeaderView> +#include "elf_viewer.h" +ElfViewer::ElfViewer(QWidget* parent) : QTableWidget(parent) { + dir_list_std = Config::getElfViewer(); + for (const auto& str : dir_list_std) { + dir_list.append(QString::fromStdString(str)); + } + + CheckElfFolders(); + + this->setShowGrid(false); + this->setEditTriggers(QAbstractItemView::NoEditTriggers); + this->setSelectionBehavior(QAbstractItemView::SelectRows); + this->setSelectionMode(QAbstractItemView::SingleSelection); + this->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + this->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + this->verticalScrollBar()->installEventFilter(this); + this->verticalScrollBar()->setSingleStep(20); + this->horizontalScrollBar()->setSingleStep(20); + this->verticalHeader()->setVisible(false); + this->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); + this->horizontalHeader()->setHighlightSections(false); + this->horizontalHeader()->setSortIndicatorShown(true); + this->horizontalHeader()->setStretchLastSection(true); + this->setContextMenuPolicy(Qt::CustomContextMenu); + this->setColumnCount(2); + this->setColumnWidth(0, 250); + this->setColumnWidth(1, 400); + this->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + this->setStyleSheet("QTableWidget { background-color: #D3D3D3; }"); + OpenElfFiles(); + QStringList headers; + headers << "Name" + << "Path"; + this->setHorizontalHeaderLabels(headers); + this->horizontalHeader()->setSortIndicatorShown(true); + this->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); +} + +void ElfViewer::OpenElfFolder() { + QString folderPath = + QFileDialog::getExistingDirectory(this, tr("Open Folder"), QDir::homePath()); + if (!dir_list.contains(folderPath)) { + dir_list.append(folderPath); + QDir directory(folderPath); + QFileInfoList fileInfoList = directory.entryInfoList(QDir::Files); + for (const QFileInfo& fileInfo : fileInfoList) { + QString file_ext = fileInfo.suffix(); + if (fileInfo.isFile() && (file_ext == "bin" || file_ext == "elf")) { + m_elf_list.append(fileInfo.absoluteFilePath()); + } + } + std::ranges::sort(m_elf_list); + OpenElfFiles(); + dir_list_std.clear(); + for (auto dir : dir_list) { + dir_list_std.push_back(dir.toStdString()); + } + Config::setElfViewer(dir_list_std); + } else { + // qDebug() << "Folder selection canceled."; + } +} + +void ElfViewer::CheckElfFolders() { + m_elf_list.clear(); + for (const QString& dir : dir_list) { + QDir directory(dir); + QFileInfoList fileInfoList = directory.entryInfoList(QDir::Files); + for (const QFileInfo& fileInfo : fileInfoList) { + QString file_ext = fileInfo.suffix(); + if (fileInfo.isFile() && (file_ext == "bin" || file_ext == "elf")) { + m_elf_list.append(fileInfo.absoluteFilePath()); + } + } + } + std::sort(m_elf_list.begin(), m_elf_list.end()); +} + +void ElfViewer::OpenElfFiles() { + this->clearContents(); + this->setRowCount(m_elf_list.size()); + for (int i = 0; auto elf : m_elf_list) { + QTableWidgetItem* item = new QTableWidgetItem(); + QFileInfo fileInfo(m_elf_list[i]); + QString fileName = fileInfo.baseName(); + SetTableItem(this, i, 0, fileName); + item = new QTableWidgetItem(); + SetTableItem(this, i, 1, m_elf_list[i]); + i++; + } + this->resizeColumnsToContents(); +} \ No newline at end of file diff --git a/src/qt_gui/elf_viewer.h b/src/qt_gui/elf_viewer.h new file mode 100644 index 000000000..15aeb55fd --- /dev/null +++ b/src/qt_gui/elf_viewer.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <ranges> +#include <QApplication> +#include <QFileDialog> +#include <QMainWindow> +#include <QMenuBar> +#include <QScrollBar> +#include <QTableWidget> +#include <QTextEdit> +#include <QTreeView> +#include <QVBoxLayout> +#include <QWidget> +#include "game_list_frame.h" +#include "src/core/loader/elf.h" + +class ElfViewer : public QTableWidget { + Q_OBJECT +public: + explicit ElfViewer(QWidget* parent = nullptr); + QStringList m_elf_list; + +private: + void CheckElfFolders(); + void OpenElfFiles(); + + Core::Loader::Elf m_elf_file; + QStringList dir_list; + QStringList elf_headers_list; + std::vector<std::string> dir_list_std; + + void SetTableItem(QTableWidget* game_list, int row, int column, QString itemStr) { + QTableWidgetItem* item = new QTableWidgetItem(); + QWidget* widget = new QWidget(this); + QVBoxLayout* layout = new QVBoxLayout(widget); + QLabel* label = new QLabel(itemStr, widget); + + label->setStyleSheet("color: white; font-size: 15px; font-weight: bold;"); + + // Create shadow effect + QGraphicsDropShadowEffect* shadowEffect = new QGraphicsDropShadowEffect(); + shadowEffect->setBlurRadius(5); // Set the blur radius of the shadow + shadowEffect->setColor(QColor(0, 0, 0, 160)); // Set the color and opacity of the shadow + shadowEffect->setOffset(2, 2); // Set the offset of the shadow + + label->setGraphicsEffect(shadowEffect); // Apply shadow effect to the QLabel + + layout->addWidget(label); + if (column != 8 && column != 1) + layout->setAlignment(Qt::AlignCenter); + widget->setLayout(layout); + game_list->setItem(row, column, item); + game_list->setCellWidget(row, column, widget); + } + +public slots: + void OpenElfFolder(); +}; diff --git a/src/qt_gui/game_grid_frame.cpp b/src/qt_gui/game_grid_frame.cpp index 0b57a162c..ca28e9ced 100644 --- a/src/qt_gui/game_grid_frame.cpp +++ b/src/qt_gui/game_grid_frame.cpp @@ -3,12 +3,9 @@ #include "game_grid_frame.h" -GameGridFrame::GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get, - std::shared_ptr<GuiSettings> m_gui_settings, QWidget* parent) - : QTableWidget(parent) { - m_game_info = game_info_get; - m_gui_settings_ = m_gui_settings; - icon_size = m_gui_settings->GetValue(gui::m_icon_size_grid).toInt(); +GameGridFrame::GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent) + : QTableWidget(parent), m_game_info(game_info_get) { + icon_size = Config::getIconSizeGrid(); windowWidth = parent->width(); this->setShowGrid(false); this->setEditTriggers(QAbstractItemView::NoEditTriggers); @@ -33,6 +30,12 @@ GameGridFrame::GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get, connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) { m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, this, false); }); + connect(this, &QTableWidget::cellClicked, this, [&]() { + cellClicked = true; + crtRow = this->currentRow(); + crtColumn = this->currentColumn(); + columnCnt = this->columnCount(); + }); } void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool fromSearch) { @@ -43,9 +46,7 @@ void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool from else m_games_ = m_game_info->m_games; m_games_shared = std::make_shared<QVector<GameInfo>>(m_games_); - - icon_size = m_gui_settings_->GetValue(gui::m_icon_size_grid) - .toInt(); // update icon size for resize event. + icon_size = Config::getIconSizeGrid(); // update icon size for resize event. int gamesPerRow = windowWidth / (icon_size + 20); // 2 x cell widget border size. int row = 0; @@ -62,10 +63,10 @@ void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool from QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(); QLabel* image_label = new QLabel(); - QPixmap icon = m_games_[gameCounter].icon.scaled( + QImage icon = m_games_[gameCounter].icon.scaled( QSize(icon_size, icon_size), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); image_label->setFixedSize(icon.width(), icon.height()); - image_label->setPixmap(icon); + image_label->setPixmap(QPixmap::fromImage(icon)); QLabel* name_label = new QLabel(QString::fromStdString(m_games_[gameCounter].serial)); name_label->setAlignment(Qt::AlignHCenter); layout->addWidget(image_label); @@ -86,8 +87,7 @@ void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool from "color: #000000;" "border: 1px solid #000000;" "padding: 2px;" - "font-size: 12px; }") - .arg(tooltipText); + "font-size: 12px; }"); widget->setStyleSheet(tooltipStyle); this->setCellWidget(row, column, widget); @@ -134,10 +134,12 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) { } void GameGridFrame::RefreshGridBackgroundImage() { - QPixmap blurredPixmap = QPixmap::fromImage(backgroundImage); - QPalette palette; - palette.setBrush(QPalette::Base, QBrush(blurredPixmap.scaled(size(), Qt::IgnoreAspectRatio))); - QColor transparentColor = QColor(135, 206, 235, 40); - palette.setColor(QPalette::Highlight, transparentColor); - this->setPalette(palette); + if (!backgroundImage.isNull()) { + QPalette palette; + palette.setBrush(QPalette::Base, + QBrush(backgroundImage.scaled(size(), Qt::IgnoreAspectRatio))); + QColor transparentColor = QColor(135, 206, 235, 40); + palette.setColor(QPalette::Highlight, transparentColor); + this->setPalette(palette); + } } \ No newline at end of file diff --git a/src/qt_gui/game_grid_frame.h b/src/qt_gui/game_grid_frame.h index 751184ceb..19ac531bc 100644 --- a/src/qt_gui/game_grid_frame.h +++ b/src/qt_gui/game_grid_frame.h @@ -14,6 +14,7 @@ #include <QVBoxLayout> #include <QWidget> #include <QtConcurrent/QtConcurrent> +#include "common/config.h" #include "game_info.h" #include "game_list_utils.h" #include "gui_context_menus.h" @@ -33,14 +34,16 @@ private: GameListUtils m_game_list_utils; GuiContextMenus m_gui_context_menus; std::shared_ptr<GameInfoClass> m_game_info; - std::shared_ptr<GuiSettings> m_gui_settings_; std::shared_ptr<QVector<GameInfo>> m_games_shared; public: - explicit GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get, - std::shared_ptr<GuiSettings> m_gui_settings, QWidget* parent = nullptr); + explicit GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent = nullptr); void PopulateGameGrid(QVector<GameInfo> m_games, bool fromSearch); + bool cellClicked = false; int icon_size; int windowWidth; + int crtRow; + int crtColumn; + int columnCnt; }; diff --git a/src/qt_gui/game_info.cpp b/src/qt_gui/game_info.cpp index f48ab327e..39cdeb75a 100644 --- a/src/qt_gui/game_info.cpp +++ b/src/qt_gui/game_info.cpp @@ -3,25 +3,43 @@ #include <future> #include <thread> +#include <QProgressDialog> +#include <QtConcurrent/QtConcurrent> #include "game_info.h" -void GameInfoClass::GetGameInfo() { - QString installDir = m_gui_settings->GetValue(gui::settings_install_dir).toString(); - std::filesystem::path parent_folder(installDir.toStdString()); - std::vector<std::string> filePaths; - for (const auto& dir : std::filesystem::directory_iterator(parent_folder)) { - if (dir.is_directory()) { - filePaths.push_back(dir.path().string()); +GameInfoClass::GameInfoClass() = default; +GameInfoClass::~GameInfoClass() = default; + +void GameInfoClass::GetGameInfo(QWidget* parent) { + QString installDir = QString::fromStdString(Config::getGameInstallDir()); + QStringList filePaths; + QDir parentFolder(installDir); + QFileInfoList fileList = parentFolder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const auto& fileInfo : fileList) { + if (fileInfo.isDir()) { + filePaths.append(fileInfo.absoluteFilePath()); } } - std::vector<std::future<GameInfo>> futures; + m_games = QtConcurrent::mapped(filePaths, [&](const QString& path) { + return readGameInfo(path.toStdString()); + }).results(); - for (const auto& filePath : filePaths) { - futures.emplace_back(std::async(std::launch::async, readGameInfo, filePath)); - } + // Progress bar, please be patient :) + QProgressDialog dialog("Loading game list, please wait :3", "Cancel", 0, 0, parent); + dialog.setWindowTitle("Loading..."); - for (auto& future : futures) { - m_games.push_back(future.get()); - } - std::sort(m_games.begin(), m_games.end(), CompareStrings); + QFutureWatcher<void> futureWatcher; + GameListUtils game_util; + bool finished = false; + futureWatcher.setFuture(QtConcurrent::map(m_games, game_util.GetFolderSize)); + connect(&futureWatcher, &QFutureWatcher<void>::finished, [&]() { + dialog.reset(); + std::sort(m_games.begin(), m_games.end(), CompareStrings); + }); + connect(&dialog, &QProgressDialog::canceled, &futureWatcher, &QFutureWatcher<void>::cancel); + dialog.setRange(0, m_games.size()); + connect(&futureWatcher, &QFutureWatcher<void>::progressValueChanged, &dialog, + &QProgressDialog::setValue); + + dialog.exec(); } \ No newline at end of file diff --git a/src/qt_gui/game_info.h b/src/qt_gui/game_info.h index 824772878..c137a5a60 100644 --- a/src/qt_gui/game_info.h +++ b/src/qt_gui/game_info.h @@ -3,34 +3,20 @@ #pragma once -#include <string> #include <QFuture> +#include <QObject> #include <QPixmap> #include <QtConcurrent/QtConcurrent> +#include "common/config.h" #include "core/file_format/psf.h" #include "game_list_utils.h" -#include "gui_settings.h" - -struct GameInfo { - std::string path; // root path of game directory (normaly directory that contains eboot.bin) - std::string icon_path; // path of icon0.png - std::string pic_path; // path of pic1.png - QPixmap icon; - std::string size; - // variables extracted from param.sfo - std::string name = "Unknown"; - std::string serial = "Unknown"; - std::string version = "Unknown"; - std::string category = "Unknown"; - std::string fw = "Unknown"; -}; - -class GameInfoClass { +class GameInfoClass : public QObject { + Q_OBJECT public: - void GetGameInfo(); - - std::shared_ptr<GuiSettings> m_gui_settings = std::make_shared<GuiSettings>(); + GameInfoClass(); + ~GameInfoClass(); + void GetGameInfo(QWidget* parent = nullptr); QVector<GameInfo> m_games; static bool CompareStrings(GameInfo& a, GameInfo& b) { @@ -39,19 +25,17 @@ public: static GameInfo readGameInfo(const std::string& filePath) { GameInfo game; - GameListUtils game_util; - game.size = game_util.GetFolderSize(QDir(QString::fromStdString(filePath))).toStdString(); game.path = filePath; PSF psf; - if (psf.open(game.path + "/sce_sys/param.sfo")) { - QString iconpath(QString::fromStdString(game.path) + "/sce_sys/icon0.png"); - QString picpath(QString::fromStdString(game.path) + "/sce_sys/pic1.png"); - game.icon_path = iconpath.toStdString(); - game.icon = QPixmap(iconpath); - game.pic_path = picpath.toStdString(); + if (psf.open(game.path + "/sce_sys/param.sfo", {})) { + game.icon_path = game.path + "/sce_sys/icon0.png"; + QString iconpath = QString::fromStdString(game.icon_path); + game.icon = QImage(iconpath); + game.pic_path = game.path + "/sce_sys/pic1.png"; game.name = psf.GetString("TITLE"); game.serial = psf.GetString("TITLE_ID"); + game.region = GameListUtils::GetRegion(psf.GetString("CONTENT_ID").at(0)).toStdString(); u32 fw_int = psf.GetInteger("SYSTEM_VER"); QString fw = QString::number(fw_int, 16); QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.') diff --git a/src/qt_gui/game_install_dialog.cpp b/src/qt_gui/game_install_dialog.cpp index 3ae78da99..ab4fc2734 100644 --- a/src/qt_gui/game_install_dialog.cpp +++ b/src/qt_gui/game_install_dialog.cpp @@ -13,10 +13,8 @@ #include <QMessageBox> #include <QPushButton> #include <QVBoxLayout> -#include "gui_settings.h" -GameInstallDialog::GameInstallDialog(std::shared_ptr<GuiSettings> gui_settings) - : m_gamesDirectory(nullptr), m_gui_settings(std::move(gui_settings)) { +GameInstallDialog::GameInstallDialog() : m_gamesDirectory(nullptr) { auto layout = new QVBoxLayout(this); layout->addWidget(SetupGamesDirectory()); @@ -43,7 +41,7 @@ QWidget* GameInstallDialog::SetupGamesDirectory() { // Input. m_gamesDirectory = new QLineEdit(); - m_gamesDirectory->setText(m_gui_settings->GetValue(gui::settings_install_dir).toString()); + m_gamesDirectory->setText(QString::fromStdString(Config::getGameInstallDir())); m_gamesDirectory->setMinimumWidth(400); layout->addWidget(m_gamesDirectory); @@ -78,7 +76,8 @@ void GameInstallDialog::Save() { return; } - m_gui_settings->SetValue(gui::settings_install_dir, QDir::toNativeSeparators(gamesDirectory)); - + Config::setGameInstallDir(gamesDirectory.toStdString()); + const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); + Config::save(config_dir / "config.toml"); accept(); } diff --git a/src/qt_gui/game_install_dialog.h b/src/qt_gui/game_install_dialog.h index b75aaaf64..abd447d93 100644 --- a/src/qt_gui/game_install_dialog.h +++ b/src/qt_gui/game_install_dialog.h @@ -4,13 +4,14 @@ #pragma once #include <QDialog> -#include "gui_settings.h" +#include "common/config.h" +#include "common/path_util.h" class QLineEdit; class GameInstallDialog final : public QDialog { public: - GameInstallDialog(std::shared_ptr<GuiSettings> gui_settings); + GameInstallDialog(); ~GameInstallDialog(); private slots: @@ -23,5 +24,4 @@ private: private: QLineEdit* m_gamesDirectory; - std::shared_ptr<GuiSettings> m_gui_settings; }; \ No newline at end of file diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index 47e037890..f0ba113ef 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -1,14 +1,12 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "game_list_frame.h" -GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get, - std::shared_ptr<GuiSettings> m_gui_settings, QWidget* parent) - : QTableWidget(parent) { - m_game_info = game_info_get; - icon_size = m_gui_settings->GetValue(gui::m_icon_size).toInt(); - +GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent) + : QTableWidget(parent), m_game_info(game_info_get) { + icon_size = Config::getIconSize(); this->setShowGrid(false); this->setEditTriggers(QAbstractItemView::NoEditTriggers); this->setSelectionBehavior(QAbstractItemView::SelectRows); @@ -25,17 +23,19 @@ GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get, this->horizontalHeader()->setSortIndicatorShown(true); this->horizontalHeader()->setStretchLastSection(true); this->setContextMenuPolicy(Qt::CustomContextMenu); - this->setColumnCount(8); + this->setColumnCount(9); this->setColumnWidth(1, 250); this->setColumnWidth(2, 110); this->setColumnWidth(3, 80); this->setColumnWidth(4, 90); this->setColumnWidth(5, 80); this->setColumnWidth(6, 80); + this->setColumnWidth(7, 80); QStringList headers; headers << "Icon" << "Name" << "Serial" + << "Region" << "Firmware" << "Size" << "Version" @@ -81,13 +81,14 @@ void GameListFrame::PopulateGameList() { ResizeIcons(icon_size); for (int i = 0; i < m_game_info->m_games.size(); i++) { - SetTableItem(this, i, 1, QString::fromStdString(m_game_info->m_games[i].name)); - SetTableItem(this, i, 2, QString::fromStdString(m_game_info->m_games[i].serial)); - SetTableItem(this, i, 3, QString::fromStdString(m_game_info->m_games[i].fw)); - SetTableItem(this, i, 4, QString::fromStdString(m_game_info->m_games[i].size)); - SetTableItem(this, i, 5, QString::fromStdString(m_game_info->m_games[i].version)); - SetTableItem(this, i, 6, QString::fromStdString(m_game_info->m_games[i].category)); - SetTableItem(this, i, 7, QString::fromStdString(m_game_info->m_games[i].path)); + SetTableItem(i, 1, QString::fromStdString(m_game_info->m_games[i].name)); + SetTableItem(i, 2, QString::fromStdString(m_game_info->m_games[i].serial)); + SetRegionFlag(i, 3, QString::fromStdString(m_game_info->m_games[i].region)); + SetTableItem(i, 4, QString::fromStdString(m_game_info->m_games[i].fw)); + SetTableItem(i, 5, QString::fromStdString(m_game_info->m_games[i].size)); + SetTableItem(i, 6, QString::fromStdString(m_game_info->m_games[i].version)); + SetTableItem(i, 7, QString::fromStdString(m_game_info->m_games[i].category)); + SetTableItem(i, 8, QString::fromStdString(m_game_info->m_games[i].path)); } } @@ -119,12 +120,14 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) { } void GameListFrame::RefreshListBackgroundImage() { - QPixmap blurredPixmap = QPixmap::fromImage(backgroundImage); - QPalette palette; - palette.setBrush(QPalette::Base, QBrush(blurredPixmap.scaled(size(), Qt::IgnoreAspectRatio))); - QColor transparentColor = QColor(135, 206, 235, 40); - palette.setColor(QPalette::Highlight, transparentColor); - this->setPalette(palette); + if (!backgroundImage.isNull()) { + QPalette palette; + palette.setBrush(QPalette::Base, + QBrush(backgroundImage.scaled(size(), Qt::IgnoreAspectRatio))); + QColor transparentColor = QColor(135, 206, 235, 40); + palette.setColor(QPalette::Highlight, transparentColor); + this->setPalette(palette); + } } void GameListFrame::SortNameAscending(int columnIndex) { @@ -142,22 +145,66 @@ void GameListFrame::SortNameDescending(int columnIndex) { } void GameListFrame::ResizeIcons(int iconSize) { - QList<int> indices; - for (int i = 0; i < m_game_info->m_games.size(); i++) { - indices.append(i); + for (int index = 0; auto& game : m_game_info->m_games) { + QImage scaledPixmap = game.icon.scaled(QSize(iconSize, iconSize), Qt::KeepAspectRatio, + Qt::SmoothTransformation); + QTableWidgetItem* iconItem = new QTableWidgetItem(); + this->verticalHeader()->resizeSection(index, scaledPixmap.height()); + this->horizontalHeader()->resizeSection(0, scaledPixmap.width()); + iconItem->setData(Qt::DecorationRole, scaledPixmap); + this->setItem(index, 0, iconItem); + index++; } - std::future<void> future = std::async(std::launch::async, [=, this]() { - for (int index : indices) { - QPixmap scaledPixmap = m_game_info->m_games[index].icon.scaled( - QSize(iconSize, iconSize), Qt::KeepAspectRatio, Qt::SmoothTransformation); - - QTableWidgetItem* iconItem = new QTableWidgetItem(); - this->verticalHeader()->resizeSection(index, scaledPixmap.height()); - this->horizontalHeader()->resizeSection(0, scaledPixmap.width()); - iconItem->setData(Qt::DecorationRole, scaledPixmap); - this->setItem(index, 0, iconItem); - } - }); - future.wait(); this->horizontalHeader()->setSectionResizeMode(7, QHeaderView::ResizeToContents); +} + +void GameListFrame::SetTableItem(int row, int column, QString itemStr) { + QTableWidgetItem* item = new QTableWidgetItem(); + QWidget* widget = new QWidget(this); + QVBoxLayout* layout = new QVBoxLayout(widget); + QLabel* label = new QLabel(itemStr, widget); + + label->setStyleSheet("color: white; font-size: 15px; font-weight: bold;"); + + // Create shadow effect + QGraphicsDropShadowEffect* shadowEffect = new QGraphicsDropShadowEffect(); + shadowEffect->setBlurRadius(5); // Set the blur radius of the shadow + shadowEffect->setColor(QColor(0, 0, 0, 160)); // Set the color and opacity of the shadow + shadowEffect->setOffset(2, 2); // Set the offset of the shadow + + label->setGraphicsEffect(shadowEffect); // Apply shadow effect to the QLabel + + layout->addWidget(label); + if (column != 8 && column != 1) + layout->setAlignment(Qt::AlignCenter); + widget->setLayout(layout); + this->setItem(row, column, item); + this->setCellWidget(row, column, widget); +} + +void GameListFrame::SetRegionFlag(int row, int column, QString itemStr) { + QTableWidgetItem* item = new QTableWidgetItem(); + QImage scaledPixmap; + if (itemStr == "Japan") { + scaledPixmap = QImage(":/images/flag_jp.png"); + } else if (itemStr == "Europe") { + scaledPixmap = QImage(":/images/flag_eu.png"); + } else if (itemStr == "USA") { + scaledPixmap = QImage(":/images/flag_us.png"); + } else if (itemStr == "Asia") { + scaledPixmap = QImage(":/images/flag_china.png"); + } else if (itemStr == "World") { + scaledPixmap = QImage(":/images/flag_world.png"); + } else { + scaledPixmap = QImage(":/images/flag_unk.png"); + } + QWidget* widget = new QWidget(this); + QVBoxLayout* layout = new QVBoxLayout(widget); + QLabel* label = new QLabel(widget); + label->setPixmap(QPixmap::fromImage(scaledPixmap)); + layout->setAlignment(Qt::AlignCenter); + layout->addWidget(label); + widget->setLayout(layout); + this->setItem(row, column, item); + this->setCellWidget(row, column, widget); } \ No newline at end of file diff --git a/src/qt_gui/game_list_frame.h b/src/qt_gui/game_list_frame.h index 24ef80812..e9f75afd4 100644 --- a/src/qt_gui/game_list_frame.h +++ b/src/qt_gui/game_list_frame.h @@ -23,8 +23,7 @@ class GameListFrame : public QTableWidget { Q_OBJECT public: - explicit GameListFrame(std::shared_ptr<GameInfoClass> game_info_get, - std::shared_ptr<GuiSettings> m_gui_settings, QWidget* parent = nullptr); + explicit GameListFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent = nullptr); Q_SIGNALS: void GameListFrameClosed(); @@ -35,6 +34,8 @@ public Q_SLOTS: void SortNameDescending(int columnIndex); private: + void SetTableItem(int row, int column, QString itemStr); + void SetRegionFlag(int row, int column, QString itemStr); QList<QAction*> m_columnActs; GameInfoClass* game_inf_get = nullptr; bool ListSortedAsc = true; @@ -50,30 +51,6 @@ public: int icon_size; - void SetTableItem(QTableWidget* game_list, int row, int column, QString itemStr) { - QWidget* widget = new QWidget(); - QVBoxLayout* layout = new QVBoxLayout(); - QLabel* label = new QLabel(itemStr); - QTableWidgetItem* item = new QTableWidgetItem(); - - label->setStyleSheet("color: white; font-size: 15px; font-weight: bold;"); - - // Create shadow effect - QGraphicsDropShadowEffect* shadowEffect = new QGraphicsDropShadowEffect(); - shadowEffect->setBlurRadius(5); // Set the blur radius of the shadow - shadowEffect->setColor(QColor(0, 0, 0, 160)); // Set the color and opacity of the shadow - shadowEffect->setOffset(2, 2); // Set the offset of the shadow - - label->setGraphicsEffect(shadowEffect); // Apply shadow effect to the QLabel - - layout->addWidget(label); - if (column != 7 && column != 1) - layout->setAlignment(Qt::AlignCenter); - widget->setLayout(layout); - game_list->setItem(row, column, item); - game_list->setCellWidget(row, column, widget); - } - static bool CompareStringsAscending(GameInfo a, GameInfo b, int columnIndex) { if (columnIndex == 1) { return a.name < b.name; diff --git a/src/qt_gui/game_list_utils.h b/src/qt_gui/game_list_utils.h index 2561f2d75..2e25f1220 100644 --- a/src/qt_gui/game_list_utils.h +++ b/src/qt_gui/game_list_utils.h @@ -8,6 +8,21 @@ #include <QImage> #include <QString> +struct GameInfo { + std::string path; // root path of game directory (normaly directory that contains eboot.bin) + std::string icon_path; // path of icon0.png + std::string pic_path; // path of pic1.png + QImage icon; + std::string size; + // variables extracted from param.sfo + std::string name = "Unknown"; + std::string serial = "Unknown"; + std::string version = "Unknown"; + std::string region = "Unknown"; + std::string category = "Unknown"; + std::string fw = "Unknown"; +}; + class GameListUtils { public: static QString FormatSize(qint64 size) { @@ -33,25 +48,49 @@ public: return sizeString + " " + suffixes[suffixIndex]; } - static QString GetFolderSize(const QDir& dir) { - + static void GetFolderSize(GameInfo& game) { + QDir dir(QString::fromStdString(game.path)); QDirIterator it(dir.absolutePath(), QDirIterator::Subdirectories); qint64 total = 0; - while (it.hasNext()) { - // check if entry is file - if (it.fileInfo().isFile()) { - total += it.fileInfo().size(); - } - it.next(); // this is heavy. - } - - // if there is a file left "at the end" get it's size - if (it.fileInfo().isFile()) { + it.next(); total += it.fileInfo().size(); } + game.size = FormatSize(total).toStdString(); + } - return FormatSize(total); + static QString GetRegion(char region) { + switch (region) { + case 'U': + return "USA"; + case 'E': + return "Europe"; + case 'J': + return "Japan"; + case 'H': + return "Asia"; + case 'I': + return "World"; + default: + return "Unknown"; + } + } + + static QString GetAppType(int type) { + switch (type) { + case 0: + return "Not Specified"; + case 1: + return "FULL APP"; + case 2: + return "UPGRADABLE"; + case 3: + return "DEMO"; + case 4: + return "FREEMIUM"; + default: + return "Unknown"; + } } QImage BlurImage(const QImage& image, const QRect& rect, int radius) { diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index 06d4334ac..c73495001 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -10,8 +10,10 @@ #include <QTreeWidget> #include <QTreeWidgetItem> #include "game_info.h" +#include "trophy_viewer.h" -class GuiContextMenus { +class GuiContextMenus : public QObject { + Q_OBJECT public: void RequestGameMenu(const QPoint& pos, QVector<GameInfo> m_games, QTableWidget* widget, bool isList) { @@ -27,9 +29,11 @@ public: QMenu menu(widget); QAction openFolder("Open Game Folder", widget); QAction openSfoViewer("SFO Viewer", widget); + QAction openTrophyViewer("Trophy Viewer", widget); menu.addAction(&openFolder); menu.addAction(&openSfoViewer); + menu.addAction(&openTrophyViewer); // Show menu. auto selected = menu.exec(global_pos); if (!selected) { @@ -43,9 +47,13 @@ public: if (selected == &openSfoViewer) { PSF psf; - if (psf.open(m_games[itemID].path + "/sce_sys/param.sfo")) { + if (psf.open(m_games[itemID].path + "/sce_sys/param.sfo", {})) { int rows = psf.map_strings.size() + psf.map_integers.size(); QTableWidget* tableWidget = new QTableWidget(rows, 2); + tableWidget->setAttribute(Qt::WA_DeleteOnClose); + connect(widget->parent(), &QWidget::destroyed, tableWidget, + [widget, tableWidget]() { tableWidget->deleteLater(); }); + tableWidget->verticalHeader()->setVisible(false); // Hide vertical header int row = 0; @@ -88,6 +96,15 @@ public: tableWidget->show(); } } + + if (selected == &openTrophyViewer) { + QString trophyPath = QString::fromStdString(m_games[itemID].serial); + QString gameTrpPath = QString::fromStdString(m_games[itemID].path); + TrophyViewer* trophyViewer = new TrophyViewer(trophyPath, gameTrpPath); + trophyViewer->show(); + connect(widget->parent(), &QWidget::destroyed, trophyViewer, + [widget, trophyViewer]() { trophyViewer->deleteLater(); }); + } } int GetRowIndex(QTreeWidget* treeWidget, QTreeWidgetItem* item) { @@ -112,9 +129,9 @@ public: return -1; } - void RequestGameMenuPKGViewer(const QPoint& pos, QStringList m_pkg_app_list, - QTreeWidget* treeWidget, - std::function<void(std::string, int, int)> InstallDragDropPkg) { + void RequestGameMenuPKGViewer( + const QPoint& pos, QStringList m_pkg_app_list, QTreeWidget* treeWidget, + std::function<void(std::filesystem::path, int, int)> InstallDragDropPkg) { QPoint global_pos = treeWidget->viewport()->mapToGlobal(pos); // context menu position QTreeWidgetItem* currentItem = treeWidget->currentItem(); // current clicked item int itemIndex = GetRowIndex(treeWidget, currentItem); // row @@ -131,15 +148,11 @@ public: if (selected == &installPackage) { QStringList pkg_app_ = m_pkg_app_list[itemIndex].split(";;"); - std::string pkg_to_install = pkg_app_[9].toStdString(); - InstallDragDropPkg(pkg_to_install, 1, 1); - - QFile file("log.txt"); - if (!file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) - return; - - QTextStream stream(&file); - stream << QString::fromStdString(pkg_to_install) << Qt::endl; + std::filesystem::path path(pkg_app_[9].toStdString()); +#ifdef _WIN32 + path = std::filesystem::path(pkg_app_[9].toStdWString()); +#endif + InstallDragDropPkg(path, 1, 1); } } }; diff --git a/src/qt_gui/gui_save.h b/src/qt_gui/gui_save.h deleted file mode 100644 index e2434f752..000000000 --- a/src/qt_gui/gui_save.h +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <QString> -#include <QVariant> - -struct GuiSave { - QString key; - QString name; - QVariant def; - - GuiSave() { - key = ""; - name = ""; - def = QVariant(); - } - - GuiSave(const QString& k, const QString& n, const QVariant& d) { - key = k; - name = n; - def = d; - } - - bool operator==(const GuiSave& rhs) const noexcept { - return key == rhs.key && name == rhs.name && def == rhs.def; - } -}; diff --git a/src/qt_gui/gui_settings.cpp b/src/qt_gui/gui_settings.cpp deleted file mode 100644 index d2fd3b3bf..000000000 --- a/src/qt_gui/gui_settings.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "gui_settings.h" - -GuiSettings::GuiSettings(QObject* parent) { - m_settings.reset(new QSettings("shadps4qt.ini", QSettings::Format::IniFormat, - parent)); // TODO make the path configurable -} - -void GuiSettings::SetGamelistColVisibility(int col, bool val) const { - SetValue(GetGuiSaveForColumn(col), val); -} - -bool GuiSettings::GetGamelistColVisibility(int col) const { - return GetValue(GetGuiSaveForColumn(col)).toBool(); -} - -GuiSave GuiSettings::GetGuiSaveForColumn(int col) { - return GuiSave{gui::game_list, - "visibility_" + - gui::get_game_list_column_name(static_cast<gui::game_list_columns>(col)), - true}; -} \ No newline at end of file diff --git a/src/qt_gui/gui_settings.h b/src/qt_gui/gui_settings.h deleted file mode 100644 index d56fa54a3..000000000 --- a/src/qt_gui/gui_settings.h +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <QColor> - -#include "settings.h" - -namespace gui { -enum custom_roles { - game_role = Qt::UserRole + 1337, -}; - -enum game_list_columns { - column_icon, - column_name, - column_serial, - column_firmware, - column_size, - column_version, - column_category, - column_path, - column_count -}; - -inline QString get_game_list_column_name(game_list_columns col) { - switch (col) { - case column_icon: - return "column_icon"; - case column_name: - return "column_name"; - case column_serial: - return "column_serial"; - case column_firmware: - return "column_firmware"; - case column_size: - return "column_size"; - case column_version: - return "column_version"; - case column_category: - return "column_category"; - case column_path: - return "column_path"; - case column_count: - return ""; - } - - throw std::runtime_error("get_game_list_column_name: Invalid column"); -} - -const QString main_window = "main_window"; -const QString game_list = "GameList"; -const QString settings = "Settings"; -const QString themes = "Themes"; - -const GuiSave main_window_gamelist_visible = GuiSave(main_window, "gamelistVisible", true); -const GuiSave main_window_geometry = GuiSave(main_window, "geometry", QByteArray()); -const GuiSave main_window_windowState = GuiSave(main_window, "windowState", QByteArray()); -const GuiSave main_window_mwState = GuiSave(main_window, "mwState", QByteArray()); - -const GuiSave game_list_state = GuiSave(game_list, "state", QByteArray()); -const GuiSave game_list_listMode = GuiSave(game_list, "listMode", true); -const GuiSave settings_install_dir = GuiSave(settings, "installDirectory", ""); -const GuiSave mw_themes = GuiSave(themes, "Themes", 0); -const GuiSave m_icon_size = GuiSave(game_list, "iconSize", 36); -const GuiSave m_icon_size_grid = GuiSave(game_list, "iconSizeGrid", 69); -const GuiSave m_slide_pos = GuiSave(game_list, "sliderPos", 0); -const GuiSave m_slide_pos_grid = GuiSave(game_list, "sliderPosGrid", 0); -const GuiSave m_table_mode = GuiSave(main_window, "tableMode", 0); -const GuiSave m_window_size = GuiSave(main_window, "windowSize", QSize(1280, 720)); -const GuiSave m_pkg_viewer = GuiSave("pkg_viewer", "pkgDir", QStringList()); -const GuiSave m_pkg_viewer_pkg_list = GuiSave("pkg_viewer", "pkgList", QStringList()); - -} // namespace gui - -class GuiSettings : public Settings { - Q_OBJECT - -public: - explicit GuiSettings(QObject* parent = nullptr); - - bool GetGamelistColVisibility(int col) const; - -public Q_SLOTS: - void SetGamelistColVisibility(int col, bool val) const; - static GuiSave GetGuiSaveForColumn(int col); -}; diff --git a/src/qt_gui/main.cpp b/src/qt_gui/main.cpp index 5edefc01c..3fc1452c3 100644 --- a/src/qt_gui/main.cpp +++ b/src/qt_gui/main.cpp @@ -3,21 +3,33 @@ #include <QtWidgets/QApplication> +#include "common/config.h" +#include "core/file_sys/fs.h" #include "qt_gui/game_install_dialog.h" -#include "qt_gui/gui_settings.h" #include "qt_gui/main_window.h" -#include "src/sdl_window.h" -Frontend::WindowSDL* g_window; +void customMessageHandler(QtMsgType, const QMessageLogContext&, const QString&) {} int main(int argc, char* argv[]) { QApplication a(argc, argv); - auto m_gui_settings = std::make_shared<GuiSettings>(); - if (m_gui_settings->GetValue(gui::settings_install_dir) == "") { - GameInstallDialog dlg(m_gui_settings); + const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); + Config::load(config_dir / "config.toml"); + QString gameDataPath = qApp->applicationDirPath() + "/game_data/"; + std::string stdStr = gameDataPath.toStdString(); + std::filesystem::path path(stdStr); +#ifdef _WIN64 + std::wstring wstdStr = gameDataPath.toStdWString(); + path = std::filesystem::path(wstdStr); +#endif + std::filesystem::create_directory(path); + + if (Config::getGameInstallDir() == "") { + GameInstallDialog dlg; dlg.exec(); } - MainWindow* m_main_window = new MainWindow(m_gui_settings, nullptr); + qInstallMessageHandler(customMessageHandler); // ignore qt logs. + + MainWindow* m_main_window = new MainWindow(nullptr); m_main_window->Init(); return a.exec(); diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 6f8dc148f..ee7ab650a 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -13,38 +13,41 @@ #include "core/file_format/pkg.h" #include "core/loader.h" #include "game_install_dialog.h" -#include "gui_settings.h" #include "main_window.h" -MainWindow::MainWindow(std::shared_ptr<GuiSettings> gui_settings, QWidget* parent) - : QMainWindow(parent), ui(new Ui::MainWindow), m_gui_settings(std::move(gui_settings)) { +MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); } MainWindow::~MainWindow() { SaveWindowState(); + const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); + Config::save(config_dir / "config.toml"); } bool MainWindow::Init() { auto start = std::chrono::steady_clock::now(); + // setup ui AddUiWidgets(); CreateActions(); + CreateRecentGameActions(); + ConfigureGuiFromSettings(); CreateDockWindows(); CreateConnects(); SetLastUsedTheme(); SetLastIconSizeBullet(); - ConfigureGuiFromSettings(); - LoadGameLists(); - + // show ui setMinimumSize(350, minimumSizeHint().height()); setWindowTitle(QString::fromStdString("shadPS4 v" + std::string(Common::VERSION))); - show(); + this->show(); + // load game list + LoadGameLists(); auto end = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); - statusBar = new QStatusBar(this); - m_main_window->setStatusBar(statusBar); + statusBar.reset(new QStatusBar); + this->setStatusBar(statusBar.data()); // Update status bar int numGames = m_game_info->m_games.size(); QString statusMessage = "Games: " + QString::number(numGames) + " (" + @@ -93,45 +96,59 @@ void MainWindow::AddUiWidgets() { } void MainWindow::CreateDockWindows() { - m_main_window = new QMainWindow(); - m_main_window->setContextMenuPolicy(Qt::PreventContextMenu); + // place holder widget is needed for good health they say :) + QWidget* phCentralWidget = new QWidget(this); + setCentralWidget(phCentralWidget); - // resize window to last W and H - QSize window_size = m_gui_settings->GetValue(gui::m_window_size).toSize(); - m_main_window->resize(window_size.width(), window_size.height()); - - // Add the game table. - m_dock_widget = new QDockWidget("Game List", m_main_window); - m_game_list_frame = new GameListFrame(m_game_info, m_gui_settings, m_main_window); + m_dock_widget.reset(new QDockWidget("Game List", this)); + m_game_list_frame.reset(new GameListFrame(m_game_info, this)); m_game_list_frame->setObjectName("gamelist"); - m_game_grid_frame = new GameGridFrame(m_game_info, m_gui_settings, m_main_window); + m_game_grid_frame.reset(new GameGridFrame(m_game_info, this)); m_game_grid_frame->setObjectName("gamegridlist"); + m_elf_viewer.reset(new ElfViewer(this)); + m_elf_viewer->setObjectName("elflist"); - int table_mode = m_gui_settings->GetValue(gui::m_table_mode).toInt(); + int table_mode = Config::getTableMode(); int slider_pos = 0; if (table_mode == 0) { // List m_game_grid_frame->hide(); - m_dock_widget->setWidget(m_game_list_frame); - slider_pos = m_gui_settings->GetValue(gui::m_slide_pos).toInt(); + m_elf_viewer->hide(); + m_game_list_frame->show(); + m_dock_widget->setWidget(m_game_list_frame.data()); + slider_pos = Config::getSliderPositon(); ui->sizeSlider->setSliderPosition(slider_pos); // set slider pos at start; isTableList = true; - } else { // Grid + } else if (table_mode == 1) { // Grid m_game_list_frame->hide(); - m_dock_widget->setWidget(m_game_grid_frame); - slider_pos = m_gui_settings->GetValue(gui::m_slide_pos_grid).toInt(); + m_elf_viewer->hide(); + m_game_grid_frame->show(); + m_dock_widget->setWidget(m_game_grid_frame.data()); + slider_pos = Config::getSliderPositonGrid(); ui->sizeSlider->setSliderPosition(slider_pos); // set slider pos at start; isTableList = false; + } else { + m_game_list_frame->hide(); + m_game_grid_frame->hide(); + m_elf_viewer->show(); + m_dock_widget->setWidget(m_elf_viewer.data()); + isTableList = false; } - m_main_window->addDockWidget(Qt::LeftDockWidgetArea, m_dock_widget); - m_main_window->setDockNestingEnabled(true); + m_dock_widget->setAllowedAreas(Qt::AllDockWidgetAreas); + m_dock_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_dock_widget->resize(this->width(), this->height()); + addDockWidget(Qt::LeftDockWidgetArea, m_dock_widget.data()); + this->setDockNestingEnabled(true); - setCentralWidget(m_main_window); + // handle resize like this for now, we deal with it when we add more docks + connect(this, &MainWindow::WindowResized, this, [&]() { + this->resizeDocks({m_dock_widget.data()}, {this->width()}, Qt::Orientation::Horizontal); + }); } void MainWindow::LoadGameLists() { // Get game info from game folders. - m_game_info->GetGameInfo(); + m_game_info->GetGameInfo(this); if (isTableList) { m_game_list_frame->PopulateGameList(); } else { @@ -141,111 +158,150 @@ void MainWindow::LoadGameLists() { void MainWindow::CreateConnects() { connect(this, &MainWindow::WindowResized, this, &MainWindow::HandleResize); - connect(ui->mw_searchbar, &QLineEdit::textChanged, this, &MainWindow::SearchGameTable); - connect(ui->exitAct, &QAction::triggered, this, &QWidget::close); - connect(ui->refreshGameListAct, &QAction::triggered, this, &MainWindow::RefreshGameTable); + connect(this, &MainWindow::ExtractionFinished, this, &MainWindow::RefreshGameTable); connect(ui->sizeSlider, &QSlider::valueChanged, this, [this](int value) { if (isTableList) { m_game_list_frame->icon_size = 36 + value; // 36 is the minimum icon size to use due to text disappearing. m_game_list_frame->ResizeIcons(36 + value); - m_gui_settings->SetValue(gui::m_icon_size, 36 + value); - m_gui_settings->SetValue(gui::m_slide_pos, value); + Config::setIconSize(36 + value); + Config::setSliderPositon(value); } else { m_game_grid_frame->icon_size = 69 + value; m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); - m_gui_settings->SetValue(gui::m_icon_size_grid, 69 + value); - m_gui_settings->SetValue(gui::m_slide_pos_grid, value); + Config::setIconSizeGrid(69 + value); + Config::setSliderPositonGrid(value); } }); - connect(ui->setIconSizeTinyAct, &QAction::triggered, this, [this](int value) { + connect(ui->playButton, &QPushButton::clicked, this, [this]() { + QString gamePath = ""; + int table_mode = Config::getTableMode(); + if (table_mode == 0) { + if (m_game_list_frame->currentItem()) { + int itemID = m_game_list_frame->currentItem()->row(); + gamePath = QString::fromStdString(m_game_info->m_games[itemID].path + "/eboot.bin"); + } + } else if (table_mode == 1) { + if (m_game_grid_frame->cellClicked) { + int itemID = (m_game_grid_frame->crtRow * m_game_grid_frame->columnCnt) + + m_game_grid_frame->crtColumn; + gamePath = QString::fromStdString(m_game_info->m_games[itemID].path + "/eboot.bin"); + } + } else { + if (m_elf_viewer->currentItem()) { + int itemID = m_elf_viewer->currentItem()->row(); + gamePath = QString::fromStdString(m_elf_viewer->m_elf_list[itemID].toStdString()); + } + } + if (gamePath != "") { + AddRecentFiles(gamePath); + Core::Emulator emulator; + emulator.Run(gamePath.toUtf8().constData()); + } + }); + + connect(ui->setIconSizeTinyAct, &QAction::triggered, this, [this]() { if (isTableList) { m_game_list_frame->icon_size = 36; // 36 is the minimum icon size to use due to text disappearing. - m_gui_settings->SetValue(gui::m_icon_size, 36); ui->sizeSlider->setValue(0); // icone_size - 36 - m_gui_settings->SetValue(gui::m_slide_pos, 0); + Config::setIconSize(36); + Config::setSliderPositon(0); } else { - m_gui_settings->SetValue(gui::m_icon_size_grid, 69); // nice :3 - ui->sizeSlider->setValue(0); // icone_size - 36 - m_gui_settings->SetValue(gui::m_slide_pos_grid, 0); + ui->sizeSlider->setValue(0); // icone_size - 36 + Config::setIconSizeGrid(69); + Config::setSliderPositonGrid(0); } }); - connect(ui->setIconSizeSmallAct, &QAction::triggered, this, [this](int value) { + connect(ui->setIconSizeSmallAct, &QAction::triggered, this, [this]() { if (isTableList) { m_game_list_frame->icon_size = 64; - m_gui_settings->SetValue(gui::m_icon_size, 64); ui->sizeSlider->setValue(28); - m_gui_settings->SetValue(gui::m_slide_pos, 28); + Config::setIconSize(64); + Config::setSliderPositon(28); } else { - m_gui_settings->SetValue(gui::m_icon_size_grid, 97); ui->sizeSlider->setValue(28); - m_gui_settings->SetValue(gui::m_slide_pos_grid, 28); + Config::setIconSizeGrid(97); + Config::setSliderPositonGrid(28); } }); - connect(ui->setIconSizeMediumAct, &QAction::triggered, this, [this](int value) { + connect(ui->setIconSizeMediumAct, &QAction::triggered, this, [this]() { if (isTableList) { m_game_list_frame->icon_size = 128; - m_gui_settings->SetValue(gui::m_icon_size, 128); ui->sizeSlider->setValue(92); - m_gui_settings->SetValue(gui::m_slide_pos, 92); + Config::setIconSize(128); + Config::setSliderPositon(92); } else { - m_gui_settings->SetValue(gui::m_icon_size_grid, 160); ui->sizeSlider->setValue(92); - m_gui_settings->SetValue(gui::m_slide_pos_grid, 92); + Config::setIconSizeGrid(160); + Config::setSliderPositonGrid(91); } }); - connect(ui->setIconSizeLargeAct, &QAction::triggered, this, [this](int value) { + connect(ui->setIconSizeLargeAct, &QAction::triggered, this, [this]() { if (isTableList) { m_game_list_frame->icon_size = 256; - m_gui_settings->SetValue(gui::m_icon_size, 256); ui->sizeSlider->setValue(220); - m_gui_settings->SetValue(gui::m_slide_pos, 220); + Config::setIconSize(256); + Config::setSliderPositon(220); } else { - m_gui_settings->SetValue(gui::m_icon_size_grid, 256); ui->sizeSlider->setValue(220); - m_gui_settings->SetValue(gui::m_slide_pos_grid, 220); + Config::setIconSizeGrid(256); + Config::setSliderPositonGrid(220); } }); - - connect(ui->setlistModeListAct, &QAction::triggered, m_dock_widget, [this]() { - m_dock_widget->setWidget(m_game_list_frame); - m_game_list_frame->show(); + // List + connect(ui->setlistModeListAct, &QAction::triggered, m_dock_widget.data(), [this]() { + m_dock_widget->setWidget(m_game_list_frame.data()); m_game_grid_frame->hide(); + m_elf_viewer->hide(); + m_game_list_frame->show(); if (m_game_list_frame->item(0, 0) == nullptr) { m_game_list_frame->clearContents(); m_game_list_frame->PopulateGameList(); } isTableList = true; - m_gui_settings->SetValue(gui::m_table_mode, 0); // save table mode - int slider_pos = m_gui_settings->GetValue(gui::m_slide_pos).toInt(); + Config::setTableMode(0); + int slider_pos = Config::getSliderPositon(); + ui->sizeSlider->setEnabled(true); ui->sizeSlider->setSliderPosition(slider_pos); }); - - connect(ui->setlistModeGridAct, &QAction::triggered, m_dock_widget, [this]() { - m_dock_widget->setWidget(m_game_grid_frame); + // Grid + connect(ui->setlistModeGridAct, &QAction::triggered, m_dock_widget.data(), [this]() { + m_dock_widget->setWidget(m_game_grid_frame.data()); m_game_grid_frame->show(); m_game_list_frame->hide(); + m_elf_viewer->hide(); if (m_game_grid_frame->item(0, 0) == nullptr) { m_game_grid_frame->clearContents(); m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); } isTableList = false; - m_gui_settings->SetValue(gui::m_table_mode, 1); // save table mode - int slider_pos_grid = m_gui_settings->GetValue(gui::m_slide_pos_grid).toInt(); + Config::setTableMode(1); + int slider_pos_grid = Config::getSliderPositonGrid(); + ui->sizeSlider->setEnabled(true); ui->sizeSlider->setSliderPosition(slider_pos_grid); }); + // Elf + connect(ui->setlistElfAct, &QAction::triggered, m_dock_widget.data(), [this]() { + m_dock_widget->setWidget(m_elf_viewer.data()); + m_game_grid_frame->hide(); + m_game_list_frame->hide(); + m_elf_viewer->show(); + isTableList = false; + ui->sizeSlider->setDisabled(true); + Config::setTableMode(2); + }); // Dump game list. - connect(ui->dumpGameListAct, &QAction::triggered, this, [this] { + connect(ui->dumpGameListAct, &QAction::triggered, this, [&] { QString filePath = qApp->applicationDirPath().append("/GameList.txt"); QFile file(filePath); QTextStream out(&file); @@ -270,21 +326,26 @@ void MainWindow::CreateConnects() { }); // Package install. - connect(ui->bootInstallPkgAct, &QAction::triggered, this, [this] { InstallPkg(); }); - connect(ui->gameInstallPathAct, &QAction::triggered, this, [this] { InstallDirectory(); }); + connect(ui->bootInstallPkgAct, &QAction::triggered, this, &MainWindow::InstallPkg); + connect(ui->gameInstallPathAct, &QAction::triggered, this, &MainWindow::InstallDirectory); + + // elf viewer + connect(ui->addElfFolderAct, &QAction::triggered, m_elf_viewer.data(), + &ElfViewer::OpenElfFolder); + // Package Viewer. connect(ui->pkgViewerAct, &QAction::triggered, this, [this]() { - PKGViewer* pkgViewer = new PKGViewer(m_game_info, m_gui_settings, - [this](std::string file, int pkgNum, int nPkg) { - this->InstallDragDropPkg(file, pkgNum, nPkg); - }); + PKGViewer* pkgViewer = new PKGViewer( + m_game_info, this, [this](std::filesystem::path file, int pkgNum, int nPkg) { + this->InstallDragDropPkg(file, pkgNum, nPkg); + }); pkgViewer->show(); }); // Themes connect(ui->setThemeLight, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Light, ui->mw_searchbar); - m_gui_settings->SetValue(gui::mw_themes, static_cast<int>(Theme::Light)); + Config::setMainWindowTheme(static_cast<int>(Theme::Light)); if (!isIconBlack) { SetUiIcons(true); isIconBlack = true; @@ -292,7 +353,7 @@ void MainWindow::CreateConnects() { }); connect(ui->setThemeDark, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Dark, ui->mw_searchbar); - m_gui_settings->SetValue(gui::mw_themes, static_cast<int>(Theme::Dark)); + Config::setMainWindowTheme(static_cast<int>(Theme::Dark)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; @@ -300,7 +361,7 @@ void MainWindow::CreateConnects() { }); connect(ui->setThemeGreen, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Green, ui->mw_searchbar); - m_gui_settings->SetValue(gui::mw_themes, static_cast<int>(Theme::Green)); + Config::setMainWindowTheme(static_cast<int>(Theme::Green)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; @@ -308,7 +369,7 @@ void MainWindow::CreateConnects() { }); connect(ui->setThemeBlue, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Blue, ui->mw_searchbar); - m_gui_settings->SetValue(gui::mw_themes, static_cast<int>(Theme::Blue)); + Config::setMainWindowTheme(static_cast<int>(Theme::Blue)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; @@ -316,7 +377,7 @@ void MainWindow::CreateConnects() { }); connect(ui->setThemeViolet, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Violet, ui->mw_searchbar); - m_gui_settings->SetValue(gui::mw_themes, static_cast<int>(Theme::Violet)); + Config::setMainWindowTheme(static_cast<int>(Theme::Violet)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; @@ -345,8 +406,8 @@ void MainWindow::SearchGameTable(const QString& text) { } void MainWindow::RefreshGameTable() { - m_game_info->m_games.clear(); - m_game_info->GetGameInfo(); + // m_game_info->m_games.clear(); + m_game_info->GetGameInfo(this); m_game_list_frame->clearContents(); m_game_list_frame->PopulateGameList(); m_game_grid_frame->clearContents(); @@ -358,16 +419,10 @@ void MainWindow::RefreshGameTable() { } void MainWindow::ConfigureGuiFromSettings() { - // Restore GUI state if needed. We need to if they exist. - if (!restoreGeometry(m_gui_settings->GetValue(gui::main_window_geometry).toByteArray())) { - resize(QGuiApplication::primaryScreen()->availableSize() * 0.7); - } - - m_main_window->restoreState(m_gui_settings->GetValue(gui::main_window_mwState).toByteArray()); - - ui->showGameListAct->setChecked( - m_gui_settings->GetValue(gui::main_window_gamelist_visible).toBool()); + setGeometry(Config::getMainWindowGeometryX(), Config::getMainWindowGeometryY(), + Config::getMainWindowGeometryW(), Config::getMainWindowGeometryH()); + ui->showGameListAct->setChecked(true); if (isTableList) { ui->setlistModeListAct->setChecked(true); } else { @@ -376,87 +431,155 @@ void MainWindow::ConfigureGuiFromSettings() { } void MainWindow::SaveWindowState() const { - // Save gui settings - m_gui_settings->SetValue(gui::main_window_geometry, saveGeometry()); - m_gui_settings->SetValue(gui::main_window_windowState, saveState()); - m_gui_settings->SetValue(gui::m_window_size, - QSize(m_main_window->width(), m_main_window->height())); - m_gui_settings->SetValue(gui::main_window_mwState, m_main_window->saveState()); + Config::setMainWindowWidth(this->width()); + Config::setMainWindowHeight(this->height()); + Config::setMainWindowGeometry(this->geometry().x(), this->geometry().y(), + this->geometry().width(), this->geometry().height()); } void MainWindow::InstallPkg() { - QStringList fileNames = QFileDialog::getOpenFileNames( - this, tr("Install PKG Files"), QDir::currentPath(), tr("PKG File (*.PKG)")); - int nPkg = fileNames.size(); - int pkgNum = 0; - for (const QString& file : fileNames) { - pkgNum++; - MainWindow::InstallDragDropPkg(file.toStdString(), pkgNum, nPkg); + QFileDialog dialog; + dialog.setFileMode(QFileDialog::ExistingFiles); + dialog.setNameFilter(tr("PKG File (*.PKG)")); + if (dialog.exec()) { + QStringList fileNames = dialog.selectedFiles(); + int nPkg = fileNames.size(); + int pkgNum = 0; + for (const QString& file : fileNames) { + ++pkgNum; + std::filesystem::path path(file.toStdString()); +#ifdef _WIN64 + path = std::filesystem::path(file.toStdWString()); +#endif + MainWindow::InstallDragDropPkg(path, pkgNum, nPkg); + } } } -void MainWindow::InstallDragDropPkg(std::string file, int pkgNum, int nPkg) { +void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int nPkg) { if (Loader::DetectFileType(file) == Loader::FileTypes::Pkg) { - PKG pkg; + pkg = PKG(); pkg.Open(file); std::string failreason; const auto extract_path = - std::filesystem::path( - m_gui_settings->GetValue(gui::settings_install_dir).toString().toStdString()) / - pkg.GetTitleID(); + std::filesystem::path(Config::getGameInstallDir()) / pkg.GetTitleID(); + QString pkgType = QString::fromStdString(pkg.GetPkgFlags()); + QDir game_dir(QString::fromStdString(extract_path.string())); + if (game_dir.exists()) { + QMessageBox msgBox; + msgBox.setWindowTitle("PKG Extraction"); + if (pkgType.contains("PATCH")) { + psf.open("", pkg.sfo); + QString pkg_app_version = QString::fromStdString(psf.GetString("APP_VER")); + psf.open(extract_path.string() + "/sce_sys/param.sfo", {}); + QString game_app_version = QString::fromStdString(psf.GetString("APP_VER")); + double appD = game_app_version.toDouble(); + double pkgD = pkg_app_version.toDouble(); + if (pkgD == appD) { + msgBox.setText( + QString("Patch detected!\nPKG and Game versions match!: %1\nWould you like " + "to overwrite?") + .arg(pkg_app_version)); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::No); + } else if (pkgD < appD) { + QMessageBox::information(this, "PKG Extraction", + QString("Patch detected!\nPKG Version %1 is older " + "than installed version!: %2\nWould you like " + "to overwrite?") + .arg(pkg_app_version, game_app_version)); + return; + } else { + msgBox.setText(QString("Patch detected!\nGame is installed: %1\nWould you like " + "to install Patch: %2?") + .arg(game_app_version, pkg_app_version)); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::No); + } + int result = msgBox.exec(); + if (result == QMessageBox::Yes) { + // Do nothing. + } else { + return; + } + } else { + msgBox.setText(QString("Game already installed\n%1\nWould you like to overwrite?") + .arg(QString::fromStdString(extract_path.string()))); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::No); + int result = msgBox.exec(); + if (result == QMessageBox::Yes) { + // Do nothing. + } else { + return; + } + } + } else { + // Do nothing; + if (pkgType.contains("PATCH")) { + QMessageBox::information(this, "PKG Extraction", + "PKG is a patch, please install the game first!"); + return; + } + // what else? + } + if (!pkg.Extract(file, extract_path, failreason)) { - QMessageBox::critical(this, "PKG ERROR", QString::fromStdString(failreason), - QMessageBox::Ok); + QMessageBox::critical(this, "PKG ERROR", QString::fromStdString(failreason)); } else { int nfiles = pkg.GetNumberOfFiles(); - QList<int> indices; + QVector<int> indices; for (int i = 0; i < nfiles; i++) { indices.append(i); } QProgressDialog dialog; dialog.setWindowTitle("PKG Extraction"); + dialog.setWindowModality(Qt::WindowModal); QString extractmsg = QString("Extracting PKG %1/%2").arg(pkgNum).arg(nPkg); dialog.setLabelText(extractmsg); + dialog.setAutoClose(true); + dialog.setRange(0, nfiles); - // Create a QFutureWatcher and connect signals and slots. QFutureWatcher<void> futureWatcher; - QObject::connect(&futureWatcher, SIGNAL(finished()), &dialog, SLOT(reset())); - QObject::connect(&dialog, SIGNAL(canceled()), &futureWatcher, SLOT(cancel())); - QObject::connect(&futureWatcher, SIGNAL(progressRangeChanged(int, int)), &dialog, - SLOT(setRange(int, int))); - QObject::connect(&futureWatcher, SIGNAL(progressValueChanged(int)), &dialog, - SLOT(setValue(int))); - - futureWatcher.setFuture(QtConcurrent::map( - indices, std::bind(&PKG::ExtractFiles, pkg, std::placeholders::_1))); - - // Display the dialog and start the event loop. + connect(&futureWatcher, &QFutureWatcher<void>::finished, this, [=, this]() { + if (pkgNum == nPkg) { + QString path = QString::fromStdString(Config::getGameInstallDir()); + QMessageBox extractMsgBox(this); + extractMsgBox.setWindowTitle("Extraction Finished"); + extractMsgBox.setText(QString("Game successfully installed at %1").arg(path)); + extractMsgBox.addButton(QMessageBox::Ok); + extractMsgBox.setDefaultButton(QMessageBox::Ok); + connect(&extractMsgBox, &QMessageBox::buttonClicked, this, + [&](QAbstractButton* button) { + if (extractMsgBox.button(QMessageBox::Ok) == button) { + extractMsgBox.close(); + emit ExtractionFinished(); + } + }); + extractMsgBox.exec(); + } + }); + connect(&dialog, &QProgressDialog::canceled, [&]() { futureWatcher.cancel(); }); + connect(&futureWatcher, &QFutureWatcher<void>::progressValueChanged, &dialog, + &QProgressDialog::setValue); + futureWatcher.setFuture( + QtConcurrent::map(indices, [&](int index) { pkg.ExtractFiles(index); })); dialog.exec(); - futureWatcher.waitForFinished(); - - auto path = m_gui_settings->GetValue(gui::settings_install_dir).toString(); - if (pkgNum == nPkg) { - QMessageBox::information(this, "Extraction Finished", - "Game successfully installed at " + path, QMessageBox::Ok); - // Refresh game table after extraction. - RefreshGameTable(); - } } } else { - QMessageBox::critical(this, "PKG ERROR", "File doesn't appear to be a valid PKG file", - QMessageBox::Ok); + QMessageBox::critical(this, "PKG ERROR", "File doesn't appear to be a valid PKG file"); } } void MainWindow::InstallDirectory() { - GameInstallDialog dlg(m_gui_settings); + GameInstallDialog dlg; dlg.exec(); } void MainWindow::SetLastUsedTheme() { - Theme lastTheme = static_cast<Theme>(m_gui_settings->GetValue(gui::mw_themes).toInt()); + Theme lastTheme = static_cast<Theme>(Config::getMainWindowTheme()); m_window_themes.SetWindowTheme(lastTheme, ui->mw_searchbar); switch (lastTheme) { @@ -489,7 +612,7 @@ void MainWindow::SetLastUsedTheme() { void MainWindow::SetLastIconSizeBullet() { // set QAction bullet point if applicable - int lastSize = m_gui_settings->GetValue(gui::m_icon_size).toInt(); + int lastSize = Config::getIconSize(); switch (lastSize) { case 36: ui->setIconSizeTinyAct->setChecked(true); @@ -507,47 +630,30 @@ void MainWindow::SetLastIconSizeBullet() { } QIcon MainWindow::RecolorIcon(const QIcon& icon, bool isWhite) { - QPixmap pixmap(icon.pixmap(icon.actualSize(QSize(120, 120)), QIcon::Normal)); + QPixmap pixmap(icon.pixmap(icon.actualSize(QSize(120, 120)))); QColor clr(isWhite ? Qt::white : Qt::black); QBitmap mask = pixmap.createMaskFromColor(clr, Qt::MaskOutColor); pixmap.fill(QColor(isWhite ? Qt::black : Qt::white)); pixmap.setMask(mask); - QIcon newIcon(pixmap); - return newIcon; + return QIcon(pixmap); } void MainWindow::SetUiIcons(bool isWhite) { - QIcon icon; - icon = RecolorIcon(ui->bootInstallPkgAct->icon(), isWhite); - ui->bootInstallPkgAct->setIcon(icon); - icon = RecolorIcon(ui->exitAct->icon(), isWhite); - ui->exitAct->setIcon(icon); - icon = RecolorIcon(ui->setlistModeListAct->icon(), isWhite); - ui->setlistModeListAct->setIcon(icon); - icon = RecolorIcon(ui->setlistModeGridAct->icon(), isWhite); - ui->setlistModeGridAct->setIcon(icon); - icon = RecolorIcon(ui->gameInstallPathAct->icon(), isWhite); - ui->gameInstallPathAct->setIcon(icon); - icon = RecolorIcon(ui->menuThemes->icon(), isWhite); - ui->menuThemes->setIcon(icon); - icon = RecolorIcon(ui->menuGame_List_Icons->icon(), isWhite); - ui->menuGame_List_Icons->setIcon(icon); - icon = RecolorIcon(ui->playButton->icon(), isWhite); - ui->playButton->setIcon(icon); - icon = RecolorIcon(ui->pauseButton->icon(), isWhite); - ui->pauseButton->setIcon(icon); - icon = RecolorIcon(ui->stopButton->icon(), isWhite); - ui->stopButton->setIcon(icon); - icon = RecolorIcon(ui->settingsButton->icon(), isWhite); - ui->settingsButton->setIcon(icon); - icon = RecolorIcon(ui->controllerButton->icon(), isWhite); - ui->controllerButton->setIcon(icon); - icon = RecolorIcon(ui->refreshGameListAct->icon(), isWhite); - ui->refreshGameListAct->setIcon(icon); - icon = RecolorIcon(ui->menuGame_List_Mode->icon(), isWhite); - ui->menuGame_List_Mode->setIcon(icon); - icon = RecolorIcon(ui->pkgViewerAct->icon(), isWhite); - ui->pkgViewerAct->setIcon(icon); + ui->bootInstallPkgAct->setIcon(RecolorIcon(ui->bootInstallPkgAct->icon(), isWhite)); + ui->exitAct->setIcon(RecolorIcon(ui->exitAct->icon(), isWhite)); + ui->setlistModeListAct->setIcon(RecolorIcon(ui->setlistModeListAct->icon(), isWhite)); + ui->setlistModeGridAct->setIcon(RecolorIcon(ui->setlistModeGridAct->icon(), isWhite)); + ui->gameInstallPathAct->setIcon(RecolorIcon(ui->gameInstallPathAct->icon(), isWhite)); + ui->menuThemes->setIcon(RecolorIcon(ui->menuThemes->icon(), isWhite)); + ui->menuGame_List_Icons->setIcon(RecolorIcon(ui->menuGame_List_Icons->icon(), isWhite)); + ui->playButton->setIcon(RecolorIcon(ui->playButton->icon(), isWhite)); + ui->pauseButton->setIcon(RecolorIcon(ui->pauseButton->icon(), isWhite)); + ui->stopButton->setIcon(RecolorIcon(ui->stopButton->icon(), isWhite)); + ui->settingsButton->setIcon(RecolorIcon(ui->settingsButton->icon(), isWhite)); + ui->controllerButton->setIcon(RecolorIcon(ui->controllerButton->icon(), isWhite)); + ui->refreshGameListAct->setIcon(RecolorIcon(ui->refreshGameListAct->icon(), isWhite)); + ui->menuGame_List_Mode->setIcon(RecolorIcon(ui->menuGame_List_Mode->icon(), isWhite)); + ui->pkgViewerAct->setIcon(RecolorIcon(ui->pkgViewerAct->icon(), isWhite)); } void MainWindow::resizeEvent(QResizeEvent* event) { @@ -563,4 +669,44 @@ void MainWindow::HandleResize(QResizeEvent* event) { m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false); m_game_grid_frame->RefreshGridBackgroundImage(); } +} + +void MainWindow::AddRecentFiles(QString filePath) { + std::vector<std::string> vec = Config::getRecentFiles(); + if (!vec.empty()) { + if (filePath.toStdString() == vec.at(0)) { + return; + } + auto it = std::find(vec.begin(), vec.end(), filePath.toStdString()); + if (it != vec.end()) { + vec.erase(it); + } + } + vec.insert(vec.begin(), filePath.toStdString()); + if (vec.size() > 6) { + vec.pop_back(); + } + Config::setRecentFiles(vec); + const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); + Config::save(config_dir / "config.toml"); + CreateRecentGameActions(); // Refresh the QActions. +} + +void MainWindow::CreateRecentGameActions() { + m_recent_files_group = new QActionGroup(this); + ui->menuRecent->clear(); + std::vector<std::string> vec = Config::getRecentFiles(); + for (int i = 0; i < vec.size(); i++) { + QAction* recentFileAct = new QAction(this); + recentFileAct->setText(QString::fromStdString(vec.at(i))); + ui->menuRecent->addAction(recentFileAct); + m_recent_files_group->addAction(recentFileAct); + } + + connect(m_recent_files_group, &QActionGroup::triggered, this, [this](QAction* action) { + QString gamePath = action->text(); + AddRecentFiles(gamePath); // Update the list. + Core::Emulator emulator; + emulator.Run(gamePath.toUtf8().constData()); + }); } \ No newline at end of file diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index dfe448b93..27d14b937 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -3,10 +3,19 @@ #pragma once +#include <QAbstractButton> #include <QActionGroup> #include <QDragEnterEvent> #include <QMainWindow> #include <QMimeData> +#include <QScopedPointer> +#include <emulator.h> +#include <fmt/core.h> +#include "common/config.h" +#include "common/path_util.h" +#include "core/file_format/psf.h" +#include "core/file_sys/fs.h" +#include "elf_viewer.h" #include "game_grid_frame.h" #include "game_info.h" #include "game_list_frame.h" @@ -15,26 +24,19 @@ #include "main_window_ui.h" #include "pkg_viewer.h" -class GuiSettings; class GameListFrame; class MainWindow : public QMainWindow { Q_OBJECT - - std::unique_ptr<Ui_MainWindow> ui; - - bool m_is_list_mode = true; - bool m_save_slider_pos = false; - int m_other_slider_pos = 0; signals: void WindowResized(QResizeEvent* event); + void ExtractionFinished(); public: - explicit MainWindow(std::shared_ptr<GuiSettings> gui_settings, QWidget* parent = nullptr); + explicit MainWindow(QWidget* parent = nullptr); ~MainWindow(); bool Init(); - void InstallPkg(); - void InstallDragDropPkg(std::string file, int pkgNum, int nPkg); + void InstallDragDropPkg(std::filesystem::path file, int pkgNum, int nPkg); void InstallDirectory(); private Q_SLOTS: @@ -45,38 +47,40 @@ private Q_SLOTS: void HandleResize(QResizeEvent* event); private: + Ui_MainWindow* ui; void AddUiWidgets(); void CreateActions(); + void CreateRecentGameActions(); void CreateDockWindows(); void LoadGameLists(); void CreateConnects(); void SetLastUsedTheme(); void SetLastIconSizeBullet(); void SetUiIcons(bool isWhite); + void InstallPkg(); + void AddRecentFiles(QString filePath); QIcon RecolorIcon(const QIcon& icon, bool isWhite); - bool isIconBlack = false; bool isTableList = true; - QActionGroup* m_icon_size_act_group = nullptr; QActionGroup* m_list_mode_act_group = nullptr; QActionGroup* m_theme_act_group = nullptr; - + QActionGroup* m_recent_files_group = nullptr; + PKG pkg; // Dockable widget frames - QMainWindow* m_main_window = nullptr; WindowThemes m_window_themes; GameListUtils m_game_list_utils; - QDockWidget* m_dock_widget = nullptr; + QScopedPointer<QDockWidget> m_dock_widget; // Game Lists - GameListFrame* m_game_list_frame = nullptr; - GameGridFrame* m_game_grid_frame = nullptr; - // Packge Viewer - PKGViewer* m_pkg_viewer = nullptr; + QScopedPointer<GameListFrame> m_game_list_frame; + QScopedPointer<GameGridFrame> m_game_grid_frame; + QScopedPointer<ElfViewer> m_elf_viewer; // Status Bar. - QStatusBar* statusBar = nullptr; + QScopedPointer<QStatusBar> statusBar; + + PSF psf; std::shared_ptr<GameInfoClass> m_game_info = std::make_shared<GameInfoClass>(); - std::shared_ptr<GuiSettings> m_gui_settings; protected: void dragEnterEvent(QDragEnterEvent* event1) override { @@ -93,7 +97,11 @@ protected: int nPkg = urlList.size(); for (const QUrl& url : urlList) { pkgNum++; - InstallDragDropPkg(url.toLocalFile().toStdString(), pkgNum, nPkg); + std::filesystem::path path(url.toLocalFile().toStdString()); +#ifdef _WIN64 + path = std::filesystem::path(url.toLocalFile().toStdWString()); +#endif + InstallDragDropPkg(path, pkgNum, nPkg); } } } diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 6d7b52608..7b5bf1816 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -30,6 +30,7 @@ QT_BEGIN_NAMESPACE class Ui_MainWindow { public: QAction* bootInstallPkgAct; + QAction* addElfFolderAct; QAction* exitAct; QAction* showGameListAct; QAction* refreshGameListAct; @@ -39,6 +40,7 @@ public: QAction* setIconSizeLargeAct; QAction* setlistModeListAct; QAction* setlistModeGridAct; + QAction* setlistElfAct; QAction* gameInstallPathAct; QAction* dumpGameListAct; QAction* pkgViewerAct; @@ -54,14 +56,13 @@ public: QPushButton* stopButton; QPushButton* settingsButton; QPushButton* controllerButton; - QWidget* emuRunWidget; - QHBoxLayout* emuRunLayer; QWidget* sizeSliderContainer; QHBoxLayout* sizeSliderContainer_layout; QSlider* sizeSlider; QMenuBar* menuBar; QMenu* menuFile; + QMenu* menuRecent; QMenu* menuView; QMenu* menuGame_List_Icons; QMenu* menuGame_List_Mode; @@ -91,6 +92,8 @@ public: bootInstallPkgAct = new QAction(MainWindow); bootInstallPkgAct->setObjectName("bootInstallPkgAct"); bootInstallPkgAct->setIcon(QIcon(":images/file_icon.png")); + addElfFolderAct = new QAction(MainWindow); + addElfFolderAct->setObjectName("addElfFolderAct"); exitAct = new QAction(MainWindow); exitAct->setObjectName("exitAct"); exitAct->setIcon(QIcon(":images/exit_icon.png")); @@ -99,7 +102,7 @@ public: showGameListAct->setCheckable(true); refreshGameListAct = new QAction(MainWindow); refreshGameListAct->setObjectName("refreshGameListAct"); - refreshGameListAct->setIcon(QIcon(":/images/refresh_icon.png")); + refreshGameListAct->setIcon(QIcon(":images/refresh_icon.png")); setIconSizeTinyAct = new QAction(MainWindow); setIconSizeTinyAct->setObjectName("setIconSizeTinyAct"); setIconSizeTinyAct->setCheckable(true); @@ -121,6 +124,9 @@ public: setlistModeGridAct->setObjectName("setlistModeGridAct"); setlistModeGridAct->setCheckable(true); setlistModeGridAct->setIcon(QIcon(":images/grid_icon.png")); + setlistElfAct = new QAction(MainWindow); + setlistElfAct->setObjectName("setlistModeGridAct"); + setlistElfAct->setCheckable(true); gameInstallPathAct = new QAction(MainWindow); gameInstallPathAct->setObjectName("gameInstallPathAct"); gameInstallPathAct->setIcon(QIcon(":images/folder_icon.png")); @@ -219,6 +225,8 @@ public: menuBar->setContextMenuPolicy(Qt::PreventContextMenu); menuFile = new QMenu(menuBar); menuFile->setObjectName("menuFile"); + menuRecent = new QMenu(menuFile); + menuRecent->setObjectName("menuRecent"); menuView = new QMenu(menuBar); menuView->setObjectName("menuView"); menuGame_List_Icons = new QMenu(menuView); @@ -243,6 +251,9 @@ public: menuBar->addAction(menuView->menuAction()); menuBar->addAction(menuSettings->menuAction()); menuFile->addAction(bootInstallPkgAct); + menuFile->addAction(addElfFolderAct); + menuFile->addSeparator(); + menuFile->addAction(menuRecent->menuAction()); menuFile->addSeparator(); menuFile->addAction(exitAct); menuView->addAction(showGameListAct); @@ -262,6 +273,7 @@ public: menuGame_List_Icons->addAction(setIconSizeLargeAct); menuGame_List_Mode->addAction(setlistModeListAct); menuGame_List_Mode->addAction(setlistModeGridAct); + menuGame_List_Mode->addAction(setlistElfAct); menuSettings->addAction(gameInstallPathAct); menuSettings->addAction(menuUtils->menuAction()); menuUtils->addAction(dumpGameListAct); @@ -274,12 +286,15 @@ public: void retranslateUi(QMainWindow* MainWindow) { MainWindow->setWindowTitle(QCoreApplication::translate("MainWindow", "Shadps4", nullptr)); + addElfFolderAct->setText( + QCoreApplication::translate("MainWindow", "Open/Add Elf Folder", nullptr)); bootInstallPkgAct->setText( QCoreApplication::translate("MainWindow", "Install Packages (PKG)", nullptr)); #if QT_CONFIG(tooltip) bootInstallPkgAct->setToolTip(QCoreApplication::translate( "MainWindow", "Install application from a .pkg file", nullptr)); #endif // QT_CONFIG(tooltip) + menuRecent->setTitle(QCoreApplication::translate("MainWindow", "Recent Games", nullptr)); exitAct->setText(QCoreApplication::translate("MainWindow", "Exit", nullptr)); #if QT_CONFIG(tooltip) exitAct->setToolTip(QCoreApplication::translate("MainWindow", "Exit Shadps4", nullptr)); @@ -300,6 +315,7 @@ public: QCoreApplication::translate("MainWindow", "List View", nullptr)); setlistModeGridAct->setText( QCoreApplication::translate("MainWindow", "Grid View", nullptr)); + setlistElfAct->setText(QCoreApplication::translate("MainWindow", "Elf Viewer", nullptr)); gameInstallPathAct->setText( QCoreApplication::translate("MainWindow", "Game Install Directory", nullptr)); dumpGameListAct->setText( @@ -307,8 +323,6 @@ public: pkgViewerAct->setText(QCoreApplication::translate("MainWindow", "PKG Viewer", nullptr)); mw_searchbar->setPlaceholderText( QCoreApplication::translate("MainWindow", "Search...", nullptr)); - // darkModeSwitch->setText( - // QCoreApplication::translate("MainWindow", "Game", nullptr)); menuFile->setTitle(QCoreApplication::translate("MainWindow", "File", nullptr)); menuView->setTitle(QCoreApplication::translate("MainWindow", "View", nullptr)); menuGame_List_Icons->setTitle( diff --git a/src/qt_gui/pkg_viewer.cpp b/src/qt_gui/pkg_viewer.cpp index 9bccb4822..cf0b21679 100644 --- a/src/qt_gui/pkg_viewer.cpp +++ b/src/qt_gui/pkg_viewer.cpp @@ -5,14 +5,16 @@ #include <QWidget> #include "pkg_viewer.h" -PKGViewer::PKGViewer(std::shared_ptr<GameInfoClass> game_info_get, - std::shared_ptr<GuiSettings> m_gui_settings, - std::function<void(std::string, int, int)> InstallDragDropPkg) - : QMainWindow() { +PKGViewer::PKGViewer(std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent, + std::function<void(std::filesystem::path, int, int)> InstallDragDropPkg) + : QMainWindow(), m_game_info(game_info_get) { this->resize(1280, 720); - m_gui_settings_ = m_gui_settings; - m_game_info = game_info_get; - dir_list = m_gui_settings->GetValue(gui::m_pkg_viewer).toStringList(); + this->setAttribute(Qt::WA_DeleteOnClose); + dir_list_std = Config::getPkgViewer(); + dir_list.clear(); + for (const auto& str : dir_list_std) { + dir_list.append(QString::fromStdString(str)); + } statusBar = new QStatusBar(treeWidget); this->setStatusBar(statusBar); treeWidget = new QTreeWidget(this); @@ -20,8 +22,8 @@ PKGViewer::PKGViewer(std::shared_ptr<GameInfoClass> game_info_get, QStringList headers; headers << "Name" << "Serial" - << "Size" << "Installed" + << "Size" << "Category" << "Type" << "App Ver" @@ -50,6 +52,8 @@ PKGViewer::PKGViewer(std::shared_ptr<GameInfoClass> game_info_get, m_gui_context_menus.RequestGameMenuPKGViewer(pos, m_full_pkg_list, treeWidget, InstallDragDropPkg); }); + + connect(parent, &QWidget::destroyed, this, [parent, this]() { this->deleteLater(); }); } PKGViewer::~PKGViewer() {} @@ -59,18 +63,21 @@ void PKGViewer::OpenPKGFolder() { QFileDialog::getExistingDirectory(this, tr("Open Folder"), QDir::homePath()); if (!dir_list.contains(folderPath)) { dir_list.append(folderPath); - if (!folderPath.isEmpty()) { - for (const auto& dir : std::filesystem::directory_iterator(folderPath.toStdString())) { - QString file_ext = - QString::fromStdString(dir.path().extension().string()).toLower(); - if (std::filesystem::is_regular_file(dir.path()) && file_ext == ".pkg") { - m_pkg_list.append(QString::fromStdString(dir.path().string())); - } + QDir directory(folderPath); + QFileInfoList fileInfoList = directory.entryInfoList(QDir::Files); + for (const QFileInfo& fileInfo : fileInfoList) { + QString file_ext = fileInfo.suffix(); + if (fileInfo.isFile() && file_ext == "pkg") { + m_pkg_list.append(fileInfo.absoluteFilePath()); } - std::sort(m_pkg_list.begin(), m_pkg_list.end()); - ProcessPKGInfo(); - m_gui_settings_->SetValue(gui::m_pkg_viewer, dir_list); } + std::sort(m_pkg_list.begin(), m_pkg_list.end()); + ProcessPKGInfo(); + dir_list_std.clear(); + for (auto dir : dir_list) { + dir_list_std.push_back(dir.toStdString()); + } + Config::setPkgViewer(dir_list_std); } else { // qDebug() << "Folder selection canceled."; } @@ -78,11 +85,13 @@ void PKGViewer::OpenPKGFolder() { void PKGViewer::CheckPKGFolders() { // Check for new PKG file additions. m_pkg_list.clear(); - for (const QString& paths : dir_list) { - for (const auto& dir : std::filesystem::directory_iterator(paths.toStdString())) { - QString file_ext = QString::fromStdString(dir.path().extension().string()).toLower(); - if (std::filesystem::is_regular_file(dir.path()) && file_ext == ".pkg") { - m_pkg_list.append(QString::fromStdString(dir.path().string())); + for (const QString& dir : dir_list) { + QDir directory(dir); + QFileInfoList fileInfoList = directory.entryInfoList(QDir::Files); + for (const QFileInfo& fileInfo : fileInfoList) { + QString file_ext = fileInfo.suffix(); + if (fileInfo.isFile() && file_ext == "pkg") { + m_pkg_list.append(fileInfo.absoluteFilePath()); } } } @@ -97,87 +106,46 @@ void PKGViewer::ProcessPKGInfo() { m_pkg_patch_list.clear(); m_full_pkg_list.clear(); for (int i = 0; i < m_pkg_list.size(); i++) { - Common::FS::IOFile file(m_pkg_list[i].toStdString(), Common::FS::FileAccessMode::Read); - if (!file.IsOpen()) { - // return false; - } - - file.ReadRaw<u8>(&pkgheader, sizeof(PKGHeader)); - file.Seek(0); - pkgSize = file.GetSize(); - pkg.resize(pkgheader.pkg_promote_size); - file.Read(pkg); - - u32 offset = pkgheader.pkg_table_entry_offset; - u32 n_files = pkgheader.pkg_table_entry_count; - - for (int i = 0; i < n_files; i++) { - std::memcpy(&entry, &pkg[offset + i * 0x20], sizeof(entry)); - const auto name = GetEntryNameByType(entry.id); - if (name == "param.sfo") { - psf.resize(entry.size); - int seek = entry.offset; - file.Seek(seek); - file.Read(psf); - std::memcpy(&header, psf.data(), sizeof(header)); - auto future = std::async(std::launch::async, [&]() { - for (u32 i = 0; i < header.index_table_entries; i++) { - PSFEntry psfentry; - std::memcpy(&psfentry, &psf[sizeof(PSFHeader) + i * sizeof(PSFEntry)], - sizeof(psfentry)); - const std::string key = - (char*)&psf[header.key_table_offset + psfentry.key_offset]; - if (psfentry.param_fmt == PSFEntry::Fmt::TextRaw || - psfentry.param_fmt == PSFEntry::Fmt::TextNormal) { - map_strings[key] = - (char*)&psf[header.data_table_offset + psfentry.data_offset]; - } - if (psfentry.param_fmt == PSFEntry::Fmt::Integer) { - u32 value; - std::memcpy(&value, - &psf[header.data_table_offset + psfentry.data_offset], - sizeof(value)); - map_integers[key] = value; - } - } - }); - future.wait(); - } - } - QString title_name = GetString("TITLE"); - QString title_id = GetString("TITLE_ID"); - QString app_type = GetAppType(GetInteger("APP_TYPE")); - QString app_version = GetString("APP_VER"); - QString title_category = GetString("CATEGORY"); - QString pkg_size = game_list_util.FormatSize(pkgheader.pkg_size); - pkg_content_flag = pkgheader.pkg_content_flags; + std::filesystem::path path(m_pkg_list[i].toStdString()); +#ifdef _WIN32 + path = std::filesystem::path(m_pkg_list[i].toStdWString()); +#endif + package.Open(path); + psf.open("", package.sfo); + QString title_name = QString::fromStdString(psf.GetString("TITLE")); + QString title_id = QString::fromStdString(psf.GetString("TITLE_ID")); + QString app_type = game_list_util.GetAppType(psf.GetInteger("APP_TYPE")); + QString app_version = QString::fromStdString(psf.GetString("APP_VER")); + QString title_category = QString::fromStdString(psf.GetString("CATEGORY")); + QString pkg_size = game_list_util.FormatSize(package.GetPkgHeader().pkg_size); + pkg_content_flag = package.GetPkgHeader().pkg_content_flags; QString flagss = ""; - for (const auto& flag : flagNames) { - if (isFlagSet(pkg_content_flag, flag.first)) { + for (const auto& flag : package.flagNames) { + if (package.isFlagSet(pkg_content_flag, flag.first)) { if (!flagss.isEmpty()) - flagss.append(", "); - flagss.append(QString::fromStdString(flag.second)); + flagss += (", "); + flagss += QString::fromStdString(flag.second.data()); } } - u32 fw_int = GetInteger("SYSTEM_VER"); + u32 fw_int = psf.GetInteger("SYSTEM_VER"); QString fw = QString::number(fw_int, 16); QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.') : fw.left(3).insert(1, '.'); fw_ = (fw_int == 0) ? "0.00" : fw_; - char region = pkgheader.pkg_content_id[0]; + char region = package.GetPkgHeader().pkg_content_id[0]; QString pkg_info = ""; - if (title_category == "gd") { + if (title_category == "gd" && !flagss.contains("PATCH")) { title_category = "App"; pkg_info = title_name + ";;" + title_id + ";;" + pkg_size + ";;" + title_category + ";;" + app_type + ";;" + app_version + ";;" + fw_ + ";;" + - GetRegion(region) + ";;" + flagss + ";;" + m_pkg_list[i]; + game_list_util.GetRegion(region) + ";;" + flagss + ";;" + m_pkg_list[i]; m_pkg_app_list.append(pkg_info); } else { title_category = "Patch"; pkg_info = title_name + ";;" + title_id + ";;" + pkg_size + ";;" + title_category + ";;" + app_type + ";;" + app_version + ";;" + fw_ + ";;" + - GetRegion(region) + ";;" + flagss + ";;" + m_pkg_list[i]; + game_list_util.GetRegion(region) + ";;" + flagss + ";;" + m_pkg_list[i]; m_pkg_patch_list.append(pkg_info); } } @@ -204,7 +172,7 @@ void PKGViewer::ProcessPKGInfo() { treeItem->setText(9, pkg_app_[8]); treeItem->setText(10, pkg_app_[9]); for (const GameInfo& info : m_game_info->m_games) { // Check if game is installed. - if (info.name == pkg_app_[0].toStdString()) { + if (info.serial == pkg_app_[1].toStdString()) { treeItem->setText(2, QChar(0x2713)); treeItem->setTextAlignment(2, Qt::AlignCenter); } @@ -233,7 +201,6 @@ void PKGViewer::ProcessPKGInfo() { } } } - std::sort(m_full_pkg_list.begin(), m_full_pkg_list.end()); for (int column = 0; column < treeWidget->columnCount() - 2; ++column) { // Resize the column to fit its contents @@ -244,18 +211,4 @@ void PKGViewer::ProcessPKGInfo() { int numPkgs = m_pkg_list.size(); QString statusMessage = QString::number(numPkgs) + " Package."; statusBar->showMessage(statusMessage); -} - -QString PKGViewer::GetString(const std::string& key) { - if (map_strings.find(key) != map_strings.end()) { - return QString::fromStdString(map_strings.at(key)); - } - return ""; -} - -u32 PKGViewer::GetInteger(const std::string& key) { - if (map_integers.find(key) != map_integers.end()) { - return map_integers.at(key); - } - return 0; } \ No newline at end of file diff --git a/src/qt_gui/pkg_viewer.h b/src/qt_gui/pkg_viewer.h index 3be2c5267..0e0a87062 100644 --- a/src/qt_gui/pkg_viewer.h +++ b/src/qt_gui/pkg_viewer.h @@ -22,33 +22,29 @@ #include "game_info.h" #include "game_list_utils.h" #include "gui_context_menus.h" -#include "gui_settings.h" class PKGViewer : public QMainWindow { Q_OBJECT public: - explicit PKGViewer(std::shared_ptr<GameInfoClass> game_info_get, - std::shared_ptr<GuiSettings> m_gui_settings, - std::function<void(std::string, int, int)> InstallDragDropPkg = nullptr); + explicit PKGViewer( + std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent, + std::function<void(std::filesystem::path, int, int)> InstallDragDropPkg = nullptr); ~PKGViewer(); void OpenPKGFolder(); void CheckPKGFolders(); void ProcessPKGInfo(); - QString GetString(const std::string& key); - u32 GetInteger(const std::string& key); private: GuiContextMenus m_gui_context_menus; - PSF psf_; + PKG package; + PSF psf; PKGHeader pkgheader; PKGEntry entry; PSFHeader header; PSFEntry psfentry; char pkgTitleID[9]; std::vector<u8> pkg; - std::vector<u8> psf; u64 pkgSize = 0; - std::shared_ptr<GuiSettings> m_gui_settings_; std::unordered_map<std::string, std::string> map_strings; std::unordered_map<std::string, u32> map_integers; @@ -58,18 +54,6 @@ private: // Status bar QStatusBar* statusBar; - std::vector<std::pair<PKGContentFlag, std::string>> flagNames = { - {PKGContentFlag::FIRST_PATCH, "FIRST_PATCH"}, - {PKGContentFlag::PATCHGO, "PATCHGO"}, - {PKGContentFlag::REMASTER, "REMASTER"}, - {PKGContentFlag::PS_CLOUD, "PS_CLOUD"}, - {PKGContentFlag::GD_AC, "GD_AC"}, - {PKGContentFlag::NON_GAME, "NON_GAME"}, - {PKGContentFlag::UNKNOWN_0x8000000, "UNKNOWN_0x8000000"}, - {PKGContentFlag::SUBSEQUENT_PATCH, "SUBSEQUENT_PATCH"}, - {PKGContentFlag::DELTA_PATCH, "DELTA_PATCH"}, - {PKGContentFlag::CUMULATIVE_PATCH, "CUMULATIVE_PATCH"}}; - std::vector<std::pair<int, QString>> appTypes = { {0, "FULL APP"}, {1, "UPGRADABLE"}, @@ -77,47 +61,11 @@ private: {3, "FREEMIUM"}, }; - bool isFlagSet(u32_be variable, PKGContentFlag flag) { - return (variable) & static_cast<u32>(flag); - } - - QString GetRegion(char region) { - switch (region) { - case 'U': - return "USA"; - case 'E': - return "Europe"; - case 'J': - return "Japan"; - case 'H': - return "Asia"; - case 'I': - return "World"; - default: - return "Unknown"; - } - } - - QString GetAppType(int region) { - switch (region) { - case 0: - return "Not Specified"; - case 1: - return "FULL APP"; - case 2: - return "UPGRADABLE"; - case 3: - return "DEMO"; - case 4: - return "FREEMIUM"; - default: - return "Unknown"; - } - } QStringList m_full_pkg_list; QStringList m_pkg_app_list; QStringList m_pkg_patch_list; QStringList m_pkg_list; QStringList dir_list; + std::vector<std::string> dir_list_std; QTreeWidget* treeWidget = nullptr; }; \ No newline at end of file diff --git a/src/qt_gui/settings.cpp b/src/qt_gui/settings.cpp deleted file mode 100644 index b428bcdab..000000000 --- a/src/qt_gui/settings.cpp +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "settings.h" - -Settings::Settings(QObject* parent) : QObject(parent), m_settings_dir(ComputeSettingsDir()) {} - -Settings::~Settings() { - if (m_settings) { - m_settings->sync(); - } -} - -QString Settings::GetSettingsDir() const { - return m_settings_dir.absolutePath(); -} - -QString Settings::ComputeSettingsDir() { - return ""; // TODO currently we configure same dir , make it configurable -} - -void Settings::RemoveValue(const QString& key, const QString& name) const { - if (m_settings) { - m_settings->beginGroup(key); - m_settings->remove(name); - m_settings->endGroup(); - } -} - -void Settings::RemoveValue(const GuiSave& entry) const { - RemoveValue(entry.key, entry.name); -} - -QVariant Settings::GetValue(const QString& key, const QString& name, const QVariant& def) const { - return m_settings ? m_settings->value(key + "/" + name, def) : def; -} - -QVariant Settings::GetValue(const GuiSave& entry) const { - return GetValue(entry.key, entry.name, entry.def); -} - -QVariant Settings::List2Var(const q_pair_list& list) { - QByteArray ba; - QDataStream stream(&ba, QIODevice::WriteOnly); - stream << list; - return QVariant(ba); -} - -q_pair_list Settings::Var2List(const QVariant& var) { - q_pair_list list; - QByteArray ba = var.toByteArray(); - QDataStream stream(&ba, QIODevice::ReadOnly); - stream >> list; - return list; -} - -void Settings::SetValue(const GuiSave& entry, const QVariant& value) const { - if (m_settings) { - m_settings->beginGroup(entry.key); - m_settings->setValue(entry.name, value); - m_settings->endGroup(); - } -} - -void Settings::SetValue(const QString& key, const QVariant& value) const { - if (m_settings) { - m_settings->setValue(key, value); - } -} - -void Settings::SetValue(const QString& key, const QString& name, const QVariant& value) const { - if (m_settings) { - m_settings->beginGroup(key); - m_settings->setValue(name, value); - m_settings->endGroup(); - } -} diff --git a/src/qt_gui/settings.h b/src/qt_gui/settings.h deleted file mode 100644 index 1e6d1a651..000000000 --- a/src/qt_gui/settings.h +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <memory> - -#include <QDir> -#include <QSettings> -#include <QSize> -#include <QVariant> - -#include "gui_save.h" - -typedef QPair<QString, QString> q_string_pair; -typedef QPair<QString, QSize> q_size_pair; -typedef QList<q_string_pair> q_pair_list; -typedef QList<q_size_pair> q_size_list; - -// Parent Class for GUI settings -class Settings : public QObject { - Q_OBJECT - -public: - explicit Settings(QObject* parent = nullptr); - ~Settings(); - - QString GetSettingsDir() const; - - QVariant GetValue(const QString& key, const QString& name, const QVariant& def) const; - QVariant GetValue(const GuiSave& entry) const; - static QVariant List2Var(const q_pair_list& list); - static q_pair_list Var2List(const QVariant& var); - -public Q_SLOTS: - /** Remove entry */ - void RemoveValue(const QString& key, const QString& name) const; - void RemoveValue(const GuiSave& entry) const; - - /** Write value to entry */ - void SetValue(const GuiSave& entry, const QVariant& value) const; - void SetValue(const QString& key, const QVariant& value) const; - void SetValue(const QString& key, const QString& name, const QVariant& value) const; - -protected: - static QString ComputeSettingsDir(); - - std::unique_ptr<QSettings> m_settings; - QDir m_settings_dir; -}; \ No newline at end of file diff --git a/src/qt_gui/trophy_viewer.cpp b/src/qt_gui/trophy_viewer.cpp new file mode 100644 index 000000000..8c28019ec --- /dev/null +++ b/src/qt_gui/trophy_viewer.cpp @@ -0,0 +1,145 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "trophy_viewer.h" + +TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath) : QMainWindow() { + this->setWindowTitle("Trophy Viewer"); + this->setAttribute(Qt::WA_DeleteOnClose); + tabWidget = new QTabWidget(this); + gameTrpPath_ = gameTrpPath; + headers << "Trophy" + << "Name" + << "Description" + << "ID" + << "Hidden" + << "Type" + << "PID"; + PopulateTrophyWidget(trophyPath); +} + +void TrophyViewer::PopulateTrophyWidget(QString title) { + QString trophyDir = qApp->applicationDirPath() + "/game_data/" + title + "/TrophyFiles"; + QDir dir(trophyDir); + if (!dir.exists()) { + std::filesystem::path path(gameTrpPath_.toStdString()); +#ifdef _WIN64 + path = std::filesystem::path(gameTrpPath_.toStdWString()); +#endif + if (!trp.Extract(path)) + return; + } + QFileInfoList dirList = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + if (dirList.isEmpty()) + return; + + for (const QFileInfo& dirInfo : dirList) { + QString tabName = dirInfo.fileName(); + QString trpDir = trophyDir + "/" + tabName; + + QString iconsPath = trpDir + "/Icons"; + QDir iconsDir(iconsPath); + QFileInfoList iconDirList = iconsDir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot); + std::vector<QImage> icons; + + for (const QFileInfo& iconInfo : iconDirList) { + QImage icon = + QImage(iconInfo.absoluteFilePath()) + .scaled(QSize(128, 128), Qt::KeepAspectRatio, Qt::SmoothTransformation); + icons.push_back(icon); + } + + QStringList trpId; + QStringList trpHidden; + QStringList trpType; + QStringList trpPid; + QStringList trophyNames; + QStringList trophyDetails; + + QString xmlPath = trpDir + "/Xml/TROP.XML"; + QFile file(xmlPath); + if (!file.open(QFile::ReadOnly | QFile::Text)) { + return; + } + + QXmlStreamReader reader(&file); + + while (!reader.atEnd() && !reader.hasError()) { + reader.readNext(); + if (reader.isStartElement() && reader.name().toString() == "trophy") { + trpId.append(reader.attributes().value("id").toString()); + trpHidden.append(reader.attributes().value("hidden").toString()); + trpType.append(reader.attributes().value("ttype").toString()); + trpPid.append(reader.attributes().value("pid").toString()); + } + + if (reader.name().toString() == "name" && !trpId.isEmpty()) { + trophyNames.append(reader.readElementText()); + } + + if (reader.name().toString() == "detail" && !trpId.isEmpty()) { + trophyDetails.append(reader.readElementText()); + } + } + QTableWidget* tableWidget = new QTableWidget(this); + tableWidget->setShowGrid(false); + tableWidget->setColumnCount(7); + tableWidget->setHorizontalHeaderLabels(headers); + tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); + tableWidget->setSelectionMode(QAbstractItemView::SingleSelection); + tableWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + tableWidget->horizontalHeader()->setStretchLastSection(true); + tableWidget->verticalHeader()->setVisible(false); + tableWidget->setRowCount(icons.size()); + for (int row = 0; auto& icon : icons) { + QTableWidgetItem* item = new QTableWidgetItem(); + item->setData(Qt::DecorationRole, icon); + item->setFlags(item->flags() & ~Qt::ItemIsEditable); + tableWidget->setItem(row, 0, item); + if (!trophyNames.isEmpty() && !trophyDetails.isEmpty()) { + SetTableItem(tableWidget, row, 1, trophyNames[row]); + SetTableItem(tableWidget, row, 2, trophyDetails[row]); + SetTableItem(tableWidget, row, 3, trpId[row]); + SetTableItem(tableWidget, row, 4, trpHidden[row]); + SetTableItem(tableWidget, row, 5, GetTrpType(trpType[row].at(0))); + SetTableItem(tableWidget, row, 6, trpPid[row]); + } + tableWidget->verticalHeader()->resizeSection(row, icon.height()); + row++; + } + tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + int width = 16; + for (int i = 0; i < 7; i++) { + width += tableWidget->horizontalHeader()->sectionSize(i); + } + tableWidget->resize(width, 720); + tabWidget->addTab(tableWidget, + tabName.insert(6, " ").replace(0, 1, tabName.at(0).toUpper())); + this->resize(width + 20, 720); + } + this->setCentralWidget(tabWidget); +} + +void TrophyViewer::SetTableItem(QTableWidget* parent, int row, int column, QString str) { + QWidget* widget = new QWidget(); + QVBoxLayout* layout = new QVBoxLayout(); + QLabel* label = new QLabel(str); + QTableWidgetItem* item = new QTableWidgetItem(); + label->setWordWrap(true); + label->setStyleSheet("color: white; font-size: 15px; font-weight: bold;"); + + // Create shadow effect + QGraphicsDropShadowEffect* shadowEffect = new QGraphicsDropShadowEffect(); + shadowEffect->setBlurRadius(5); // Set the blur radius of the shadow + shadowEffect->setColor(QColor(0, 0, 0, 160)); // Set the color and opacity of the shadow + shadowEffect->setOffset(2, 2); // Set the offset of the shadow + + label->setGraphicsEffect(shadowEffect); // Apply shadow effect to the QLabel + + layout->addWidget(label); + if (column != 1 && column != 2) + layout->setAlignment(Qt::AlignCenter); + widget->setLayout(layout); + parent->setItem(row, column, item); + parent->setCellWidget(row, column, widget); +} \ No newline at end of file diff --git a/src/qt_gui/trophy_viewer.h b/src/qt_gui/trophy_viewer.h new file mode 100644 index 000000000..ab79ac501 --- /dev/null +++ b/src/qt_gui/trophy_viewer.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <QApplication> +#include <QDir> +#include <QFileInfoList> +#include <QGraphicsBlurEffect> +#include <QHeaderView> +#include <QLabel> +#include <QMainWindow> +#include <QStyleOptionViewItem> +#include <QTableWidget> +#include <QTableWidgetItem> +#include <QVBoxLayout> +#include <QWidget> +#include <QXmlStreamReader> +#include "common/types.h" +#include "core/file_format/trp.h" + +class TrophyViewer : public QMainWindow { + Q_OBJECT +public: + explicit TrophyViewer(QString trophyPath, QString gameTrpPath); + +private: + void PopulateTrophyWidget(QString title); + void SetTableItem(QTableWidget* parent, int row, int column, QString str); + + QTabWidget* tabWidget = nullptr; + QStringList headers; + QString gameTrpPath_; + TRP trp; + + QString GetTrpType(const QChar trp_) { + switch (trp_.toLatin1()) { + case 'B': + return "Bronze"; + case 'S': + return "Silver"; + case 'G': + return "Gold"; + case 'P': + return "Platinum"; + } + return "Unknown"; + } +}; \ No newline at end of file diff --git a/src/shadps4.qrc b/src/shadps4.qrc index ac5b2dd6b..cdbae7861 100644 --- a/src/shadps4.qrc +++ b/src/shadps4.qrc @@ -15,5 +15,11 @@ <file>images/controller_icon.png</file> <file>images/refresh_icon.png</file> <file>images/list_mode_icon.png</file> + <file>images/flag_jp.png</file> + <file>images/flag_eu.png</file> + <file>images/flag_unk.png</file> + <file>images/flag_us.png</file> + <file>images/flag_world.png</file> + <file>images/flag_china.png</file> </qresource> </RCC> From 1abccb29f99cef4d23988e95e1389bade216ffc5 Mon Sep 17 00:00:00 2001 From: raziel1000 <ckraziel@gmail.com> Date: Mon, 10 Jun 2024 20:44:16 -0600 Subject: [PATCH 05/17] clang format --- src/common/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/config.h b/src/common/config.h index c41c8c294..0a3b4905e 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -4,8 +4,8 @@ #pragma once #include <filesystem> -#include "types.h" #include <vector> +#include "types.h" namespace Config { void load(const std::filesystem::path& path); From e89b2d1cddb7129f38ac6178da331adb9e900a33 Mon Sep 17 00:00:00 2001 From: psucien <bad_cast@protonmail.com> Date: Mon, 10 Jun 2024 17:20:49 +0200 Subject: [PATCH 06/17] Added Tracy profiler --- .gitmodules | 3 +++ .reuse/dep5 | 1 + CMakeLists.txt | 2 +- externals/CMakeLists.txt | 8 ++++++++ externals/tracy | 1 + src/common/debug.h | 2 ++ 6 files changed, 16 insertions(+), 1 deletion(-) create mode 160000 externals/tracy diff --git a/.gitmodules b/.gitmodules index 7ac7ad42b..3284ecce5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -55,3 +55,6 @@ [submodule "externals/xxhash"] path = externals/xxhash url = https://github.com/Cyan4973/xxHash.git +[submodule "externals/tracy"] + path = externals/tracy + url = https://github.com/shadps4-emu/tracy diff --git a/.reuse/dep5 b/.reuse/dep5 index 283c680b4..ed58eb798 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -34,5 +34,6 @@ Files: CMakeSettings.json src/shadps4.rc src/shadps4.qrc externals/stb_image.h + externals/tracy/* Copyright: shadPS4 Emulator Project License: GPL-2.0-or-later diff --git a/CMakeLists.txt b/CMakeLists.txt index 7bb549d2c..a9f47acc8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -500,7 +500,7 @@ endif() create_target_directory_groups(shadps4) -target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak) +target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak Tracy::TracyClient) target_link_libraries(shadps4 PRIVATE discord-rpc boost vma sirit vulkan-headers xxhash Zydis SPIRV glslang SDL3-shared) if (NOT ENABLE_QT_GUI) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index b2d348b7f..4e60988c2 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -93,3 +93,11 @@ add_subdirectory(sirit EXCLUDE_FROM_ALL) if (WIN32) target_compile_options(sirit PUBLIC "-Wno-error=unused-command-line-argument") endif() + +# Tracy +option(TRACY_ENABLE "" ON) +option(TRACY_NO_CRASH_HANDLER "" ON) # Otherwise texture cache exceptions will be treaten as a crash +option(TRACY_ON_DEMAND "" ON) +option(TRACY_NO_FRAME_IMAGE "" ON) +option(TRACY_FIBERS "" ON) # For AmdGpu frontend profiling +add_subdirectory(tracy EXCLUDE_FROM_ALL) diff --git a/externals/tracy b/externals/tracy new file mode 160000 index 000000000..ef96964f7 --- /dev/null +++ b/externals/tracy @@ -0,0 +1 @@ +Subproject commit ef96964f71885e9ff177253ce0465569787e4a4c diff --git a/src/common/debug.h b/src/common/debug.h index e1c898a38..f89590aaf 100644 --- a/src/common/debug.h +++ b/src/common/debug.h @@ -10,3 +10,5 @@ #else #error What the fuck is this compiler #endif + +#include <tracy/Tracy.hpp> From 04b1226e9c1aabc58f5643026d848bf10547801d Mon Sep 17 00:00:00 2001 From: psucien <bad_cast@protonmail.com> Date: Tue, 11 Jun 2024 12:14:33 +0200 Subject: [PATCH 07/17] tracy: basic markup and project palette --- src/common/debug.h | 37 +++++++++++++++++++ src/common/logging/backend.cpp | 19 ++++++++++ src/core/memory.cpp | 3 ++ src/emulator.cpp | 2 + src/video_core/amdgpu/liverpool.cpp | 23 ++++++++++++ .../renderer_vulkan/vk_rasterizer.cpp | 5 +++ 6 files changed, 89 insertions(+) diff --git a/src/common/debug.h b/src/common/debug.h index f89590aaf..98f6d3eb2 100644 --- a/src/common/debug.h +++ b/src/common/debug.h @@ -12,3 +12,40 @@ #endif #include <tracy/Tracy.hpp> + +static inline bool IsProfilerConnected() { + return tracy::GetProfiler().IsConnected(); +} + +#define CUSTOM_LOCK(type, varname) \ + tracy::LockableCtx varname { \ + []() -> const tracy::SourceLocationData* { \ + static constexpr tracy::SourceLocationData srcloc{nullptr, #type " " #varname, \ + TracyFile, TracyLine, 0}; \ + return &srcloc; \ + }() \ + } + +#define TRACK_ALLOC(ptr, size, pool) TracyAllocN(std::bit_cast<void*>(ptr), (size), (pool)) +#define TRACK_FREE(ptr, pool) TracyFreeN(std::bit_cast<void*>(ptr), (pool)) + +enum MarkersPallete : int { + EmulatorMarkerColor = 0x264653, + RendererMarkerColor = 0x2a9d8f, + HleMarkerColor = 0xe9c46a, + Reserved0 = 0xf4a261, + Reserved1 = 0xe76f51, +}; + +#define EMULATOR_TRACE ZoneScopedC(EmulatorMarkerColor) +#define RENDERER_TRACE ZoneScopedC(RendererMarkerColor) +#define HLE_TRACE ZoneScopedC(HleMarkerColor) + +#define TRACE_WARN(msg) \ + [](const auto& msg) { TracyMessageC(msg.c_str(), msg.size(), tracy::Color::DarkOrange); }(msg); +#define TRACE_ERROR(msg) \ + [](const auto& msg) { TracyMessageC(msg.c_str(), msg.size(), tracy::Color::Red); }(msg) +#define TRACE_CRIT(msg) \ + [](const auto& msg) { TracyMessageC(msg.c_str(), msg.size(), tracy::Color::HotPink); }(msg) + +#define FRAME_END FrameMark diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 0fd344b55..460b73768 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -13,6 +13,7 @@ #include "common/bounded_threadsafe_queue.h" #include "common/config.h" +#include "common/debug.h" #include "common/io_file.h" #include "common/logging/backend.h" #include "common/logging/log.h" @@ -167,6 +168,24 @@ public: void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, std::string message) { + // Propagate important log messages to the profiler + if (IsProfilerConnected()) { + const auto& msg_str = std::format("[{}] {}", GetLogClassName(log_class), message); + switch (log_level) { + case Level::Warning: + TRACE_WARN(msg_str); + break; + case Level::Error: + TRACE_ERROR(msg_str); + break; + case Level::Critical: + TRACE_CRIT(msg_str); + break; + default: + break; + } + } + if (!filter.CheckMessage(log_class, log_level)) { return; } diff --git a/src/core/memory.cpp b/src/core/memory.cpp index a60680539..5029f82ca 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -3,6 +3,7 @@ #include "common/alignment.h" #include "common/assert.h" +#include "common/debug.h" #include "common/scope_exit.h" #include "core/libraries/error_codes.h" #include "core/libraries/kernel/memory_management.h" @@ -123,6 +124,7 @@ int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, M // Perform the mapping. *out_addr = impl.Map(mapped_addr, size, alignment, phys_addr, is_exec); + TRACK_ALLOC(*out_addr, size, "VMEM"); return ORBIS_OK; } @@ -149,6 +151,7 @@ void MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) { // Unmap the memory region. impl.Unmap(virtual_addr, size, phys_addr); + TRACK_FREE(virtual_addr, "VMEM"); } int MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* prot) { diff --git a/src/emulator.cpp b/src/emulator.cpp index 793d996a1..c5facd191 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -8,6 +8,7 @@ #include <core/libraries/videoout/video_out.h> #include <fmt/core.h> #include "common/config.h" +#include "common/debug.h" #include "common/logging/backend.h" #include "common/path_util.h" #include "common/singleton.h" @@ -121,6 +122,7 @@ void Emulator::Run(const std::filesystem::path& file) { window.waitEvent(); Libraries::VideoOut::Flip(FlipPeriod); Libraries::VideoOut::Vblank(); + FRAME_END; } std::exit(0); diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index 7f275e8c3..e0cf86aa5 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/assert.h" +#include "common/debug.h" #include "common/thread.h" #include "video_core/amdgpu/liverpool.h" #include "video_core/amdgpu/pm4_cmds.h" @@ -9,6 +10,10 @@ namespace AmdGpu { +static const char* dcb_task_name{"DCB_TASK"}; +static const char* ccb_task_name{"CCB_TASK"}; +static const char* asc_task_name{"ACB_TASK"}; + std::array<u8, 48_KB> Liverpool::ConstantEngine::constants_heap; Liverpool::Liverpool() { @@ -69,12 +74,16 @@ void Liverpool::Process(std::stop_token stoken) { } void Liverpool::WaitGpuIdle() { + RENDERER_TRACE; + while (const auto old = num_submits.load()) { num_submits.wait(old); } } Liverpool::Task Liverpool::ProcessCeUpdate(std::span<const u32> ccb) { + TracyFiberEnter(ccb_task_name); + while (!ccb.empty()) { const auto* header = reinterpret_cast<const PM4Header*>(ccb.data()); const u32 type = header->type; @@ -109,7 +118,9 @@ Liverpool::Task Liverpool::ProcessCeUpdate(std::span<const u32> ccb) { case PM4ItOpcode::WaitOnDeCounterDiff: { const auto diff = it_body[0]; while ((cblock.de_count - cblock.ce_count) >= diff) { + TracyFiberLeave; co_yield {}; + TracyFiberEnter(ccb_task_name); } break; } @@ -120,9 +131,13 @@ Liverpool::Task Liverpool::ProcessCeUpdate(std::span<const u32> ccb) { } ccb = ccb.subspan(header->type3.NumWords() + 1); } + + TracyFiberLeave; } Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<const u32> ccb) { + TracyFiberEnter(dcb_task_name); + cblock.Reset(); // TODO: potentially, ASCs also can depend on CE and in this case the @@ -132,7 +147,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c if (!ccb.empty()) { // In case of CCB provided kick off CE asap to have the constant heap ready to use ce_task = ProcessCeUpdate(ccb); + TracyFiberLeave; ce_task.handle.resume(); + TracyFiberEnter(dcb_task_name); } while (!dcb.empty()) { @@ -330,7 +347,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c const auto* wait_reg_mem = reinterpret_cast<const PM4CmdWaitRegMem*>(header); ASSERT(wait_reg_mem->engine.Value() == PM4CmdWaitRegMem::Engine::Me); while (!wait_reg_mem->Test()) { + TracyFiberLeave; co_yield {}; + TracyFiberEnter(dcb_task_name); } break; } @@ -340,7 +359,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c } case PM4ItOpcode::WaitOnCeCounter: { while (cblock.ce_count <= cblock.de_count) { + TracyFiberLeave; ce_task.handle.resume(); + TracyFiberEnter(dcb_task_name); } break; } @@ -356,6 +377,8 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c ASSERT_MSG(ce_task.handle.done(), "Partially processed CCB"); ce_task.handle.destroy(); } + + TracyFiberLeave; } Liverpool::Task Liverpool::ProcessCompute(std::span<const u32> acb) { diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 291d38fd2..abb6d3282 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/config.h" +#include "common/debug.h" #include "core/memory.h" #include "video_core/amdgpu/liverpool.h" #include "video_core/renderer_vulkan/vk_instance.h" @@ -33,6 +34,8 @@ Rasterizer::Rasterizer(const Instance& instance_, Scheduler& scheduler_, Rasterizer::~Rasterizer() = default; void Rasterizer::Draw(bool is_indexed, u32 index_offset) { + RENDERER_TRACE; + const auto cmdbuf = scheduler.CommandBuffer(); const auto& regs = liverpool->regs; const u32 num_indices = SetupIndexBuffer(is_indexed, index_offset); @@ -104,6 +107,8 @@ void Rasterizer::Draw(bool is_indexed, u32 index_offset) { } void Rasterizer::DispatchDirect() { + RENDERER_TRACE; + const auto cmdbuf = scheduler.CommandBuffer(); const auto& cs_program = liverpool->regs.cs_program; const ComputePipeline* pipeline = pipeline_cache.GetComputePipeline(); From e8fa9e0e899c3ed2bda049e350533324f2b1977e Mon Sep 17 00:00:00 2001 From: georgemoralis <giorgosmrls@gmail.com> Date: Tue, 11 Jun 2024 16:42:15 +0300 Subject: [PATCH 08/17] added libScePosix nanosleep --- src/core/libraries/kernel/time_management.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/libraries/kernel/time_management.cpp b/src/core/libraries/kernel/time_management.cpp index 3f5f92eef..dcfcb51f2 100644 --- a/src/core/libraries/kernel/time_management.cpp +++ b/src/core/libraries/kernel/time_management.cpp @@ -168,6 +168,7 @@ void timeSymbolsRegister(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("-ZR+hG7aDHw", "libkernel", 1, "libkernel", 1, 1, sceKernelSleep); LIB_FUNCTION("0wu33hunNdE", "libScePosix", 1, "libkernel", 1, 1, sceKernelSleep); LIB_FUNCTION("yS8U2TGCe1A", "libkernel", 1, "libkernel", 1, 1, posix_nanosleep); + LIB_FUNCTION("yS8U2TGCe1A", "libScePosix", 1, "libkernel", 1, 1, posix_nanosleep); LIB_FUNCTION("QBi7HCK03hw", "libkernel", 1, "libkernel", 1, 1, sceKernelClockGettime); LIB_FUNCTION("lLMT9vJAck0", "libkernel", 1, "libkernel", 1, 1, clock_gettime); LIB_FUNCTION("lLMT9vJAck0", "libScePosix", 1, "libkernel", 1, 1, clock_gettime); From 7c7b617852af8190e4ab5c05d26ebcb360d98412 Mon Sep 17 00:00:00 2001 From: Xphalnos <164882787+Xphalnos@users.noreply.github.com> Date: Tue, 11 Jun 2024 18:15:36 +0200 Subject: [PATCH 09/17] Minor fixes --- .reuse/dep5 | 14 +++++++------- externals/stb_image.h | 9 ++++++--- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.reuse/dep5 b/.reuse/dep5 index 283c680b4..5f60238ee 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -6,7 +6,7 @@ Files: CMakeSettings.json scripts/ps4_names.txt documents/changelog.txt documents/readme.txt - documents/Screenshots/screenshot.png + documents/Screenshots/screenshot.png .github/shadps4.desktop .github/shadps4.png .gitmodules @@ -25,12 +25,12 @@ Files: CMakeSettings.json src/images/settings_icon.png src/images/stop_icon.png src/images/themes_icon.png - src/images/flag_jp.png - src/images/flag_eu.png - src/images/flag_us.png - src/images/flag_china.png - src/images/flag_world.png - src/images/flag_unk.png + src/images/flag_jp.png + src/images/flag_eu.png + src/images/flag_us.png + src/images/flag_china.png + src/images/flag_world.png + src/images/flag_unk.png src/shadps4.rc src/shadps4.qrc externals/stb_image.h diff --git a/externals/stb_image.h b/externals/stb_image.h index a632d5435..9eedabedc 100644 --- a/externals/stb_image.h +++ b/externals/stb_image.h @@ -1,4 +1,4 @@ -/* stb_image - v2.29 - public domain image loader - http://nothings.org/stb +/* stb_image - v2.30 - public domain image loader - http://nothings.org/stb no warranty implied; use at your own risk Do this: @@ -48,6 +48,7 @@ LICENSE RECENT REVISION HISTORY: + 2.30 (2024-05-31) avoid erroneous gcc warning 2.29 (2023-05-xx) optimizations 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes @@ -5159,9 +5160,11 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now. if (scan == STBI__SCAN_header) { ++s->img_n; return 1; } if (z->depth == 16) { - for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + for (k = 0; k < s->img_n && k < 3; ++k) // extra loop test to suppress false GCC warning + tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is } else { - for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + for (k = 0; k < s->img_n && k < 3; ++k) + tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger } } break; From 991d44bde6b563903c8c52769bd31aeb0801ef6b Mon Sep 17 00:00:00 2001 From: georgemoralis <giorgosmrls@gmail.com> Date: Tue, 11 Jun 2024 19:36:17 +0300 Subject: [PATCH 10/17] fix typo --- src/core/libraries/kernel/time_management.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/libraries/kernel/time_management.cpp b/src/core/libraries/kernel/time_management.cpp index dcfcb51f2..db85ebdcf 100644 --- a/src/core/libraries/kernel/time_management.cpp +++ b/src/core/libraries/kernel/time_management.cpp @@ -48,7 +48,7 @@ u64 PS4_SYSV_ABI sceKernelReadTsc() { int PS4_SYSV_ABI sceKernelUsleep(u32 microseconds) { if (microseconds < 1000u) { -#if _WIN64 +#ifdef _WIN64 LARGE_INTEGER interval{ .QuadPart = -1 * (microseconds * 10u), }; From ac0d073050ece633e68ee3fcd4be1a12f1c71a0e Mon Sep 17 00:00:00 2001 From: Xphalnos <164882787+Xphalnos@users.noreply.github.com> Date: Tue, 11 Jun 2024 21:14:18 +0200 Subject: [PATCH 11/17] Improve Building and Readme --- README.md | 34 ++++++++++--------- .../{linux_building.md => building-linux.md} | 0 documents/building-windows.md | 8 ++--- 3 files changed, 22 insertions(+), 20 deletions(-) rename documents/{linux_building.md => building-linux.md} (100%) diff --git a/README.md b/README.md index 30652035a..8c88f1541 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,13 @@ SPDX-License-Identifier: GPL-2.0-or-later shadPS4 is an early PS4 emulator for Windows and Linux written in C++ -To discuss shadPS4 development or suggest ideas, join the [Discord server](https://discord.gg/MyZRaBngxA) +To discuss shadPS4 development or suggest ideas, join the [**Discord server**](https://discord.gg/MyZRaBngxA). -Check us on [X (twitter)](https://x.com/shadps4) or on our [website](https://shadps4.net/). +Check us on [**X (twitter)**](https://x.com/shadps4) or on our [**website**](https://shadps4.net/). # Status -In development, 2D games are working like [Sonic Mania](https://www.youtube.com/watch?v=AAHoNzhHyCU), Undertale, Momodora and others... +In development, 2D games are working like [**Sonic Mania**](https://www.youtube.com/watch?v=AAHoNzhHyCU), Undertale, Momodora and others... # Why? @@ -52,11 +52,11 @@ The project started as a fun project. Due to limited free time, it will probably ## Windows -Check the build instructions for [Windows](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-windows.md). +Check the build instructions for [**Windows**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-windows.md). ## Linux -Check the build instructions for [Linux](https://github.com/shadps4-emu/shadPS4/blob/main/documents/linux_building.md). +Check the build instructions for [**Linux**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/linux_building.md). ## Build status @@ -79,18 +79,20 @@ C:\Users\******\shadPS4.exe C:\******\game_eboot.bin # Main team -- [georgemoralis](https://github.com/georgemoralis) -- [raphaelthegreat](https://github.com/raphaelthegreat) -- [psucien](https://github.com/psucien) -- [skmp](https://github.com/skmp) -- [wheremyfoodat](https://github.com/wheremyfoodat) -- [raziel1000](https://github.com/raziel1000) +- [**georgemoralis**](https://github.com/georgemoralis) +- [**raphaelthegreat**](https://github.com/raphaelthegreat) +- [**psucien**](https://github.com/psucien) +- [**skmp**](https://github.com/skmp) +- [**wheremyfoodat**](https://github.com/wheremyfoodat) +- [**raziel1000**](https://github.com/raziel1000) -Logo is done by [Xphalnos](https://github.com/Xphalnos) +Logo is done by [**Xphalnos**](https://github.com/Xphalnos) -# Contribution +# Contributing -We currently accept any contribution, just open a PR and we will check it :) +If you want to contribute, please look the [**CONTRIBUTING.md**](https://github.com/shadps4-emu/shadPS4/blob/main/CONTRIBUTING.md) file. + +Open a PR and we'll check it :) # Contributors @@ -100,5 +102,5 @@ We currently accept any contribution, just open a PR and we will check it :) # Sister Projects -- [Panda3DS](https://github.com/wheremyfoodat/Panda3DS): A multiplatform 3DS emulator from our co-author wheremyfoodat. -- [hydra](https://github.com/hydra-emu/hydra): A multisystem, multiplatform emulator (chip-8, GB, NES, N64) from Paris. +- [**Panda3DS**](https://github.com/wheremyfoodat/Panda3DS): A multiplatform 3DS emulator from our co-author wheremyfoodat. +- [**hydra**](https://github.com/hydra-emu/hydra): A multisystem, multiplatform emulator (chip-8, GB, NES, N64) from Paris. diff --git a/documents/linux_building.md b/documents/building-linux.md similarity index 100% rename from documents/linux_building.md rename to documents/building-linux.md diff --git a/documents/building-windows.md b/documents/building-windows.md index 577407425..e00ed90d9 100644 --- a/documents/building-windows.md +++ b/documents/building-windows.md @@ -3,11 +3,11 @@ SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project SPDX-License-Identifier: GPL-2.0-or-later --> -# Build shadps4 for Windows +# Build shadPS4 for Windows -## Download Visual Studio Community 2022 17.9.6 +## Download Visual Studio Community 2022 -Download link: [Visual Studio 2022](https://visualstudio.microsoft.com/vs/) +Download link: [**Visual Studio 2022**](https://visualstudio.microsoft.com/vs/) ## Requirements @@ -22,4 +22,4 @@ Download link: [Visual Studio 2022](https://visualstudio.microsoft.com/vs/) - ## Compiling -- Open Visual Studio and select the Clang-debug or Clang-release. It should compile just fine. +- Open Visual Studio Community and select the **x64-Clang-Release**, **x64-Clang-Debug** or **x64-Clang-RelWithDebInfo**. It should compile just fine. From 8612907831db7c49c1510eec3ea5e6b3ecd694e7 Mon Sep 17 00:00:00 2001 From: georgemoralis <giorgosmrls@gmail.com> Date: Tue, 11 Jun 2024 22:41:35 +0300 Subject: [PATCH 12/17] really fixed typo --- src/core/libraries/kernel/time_management.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/libraries/kernel/time_management.cpp b/src/core/libraries/kernel/time_management.cpp index db85ebdcf..19f2847e5 100644 --- a/src/core/libraries/kernel/time_management.cpp +++ b/src/core/libraries/kernel/time_management.cpp @@ -47,8 +47,8 @@ u64 PS4_SYSV_ABI sceKernelReadTsc() { } int PS4_SYSV_ABI sceKernelUsleep(u32 microseconds) { - if (microseconds < 1000u) { #ifdef _WIN64 + if (microseconds < 1000u) { LARGE_INTEGER interval{ .QuadPart = -1 * (microseconds * 10u), }; @@ -57,7 +57,7 @@ int PS4_SYSV_ABI sceKernelUsleep(u32 microseconds) { std::this_thread::sleep_for(std::chrono::microseconds(microseconds)); } #else - usleep(microseconds); + usleep(microseconds); #endif return 0; } From d7565dec5724be81638950217b58d9ae609db542 Mon Sep 17 00:00:00 2001 From: psucien <bad_cast@protonmail.com> Date: Tue, 11 Jun 2024 21:52:48 +0200 Subject: [PATCH 13/17] tracy: added Vulkan GPU profiling --- src/common/debug.h | 5 +- .../renderer_vulkan/renderer_vulkan.cpp | 109 +++++++++--------- .../renderer_vulkan/vk_instance.cpp | 23 ++++ src/video_core/renderer_vulkan/vk_instance.h | 8 ++ .../renderer_vulkan/vk_scheduler.cpp | 15 ++- src/video_core/renderer_vulkan/vk_scheduler.h | 2 + 6 files changed, 107 insertions(+), 55 deletions(-) diff --git a/src/common/debug.h b/src/common/debug.h index 98f6d3eb2..ea1dff7d6 100644 --- a/src/common/debug.h +++ b/src/common/debug.h @@ -33,7 +33,7 @@ enum MarkersPallete : int { EmulatorMarkerColor = 0x264653, RendererMarkerColor = 0x2a9d8f, HleMarkerColor = 0xe9c46a, - Reserved0 = 0xf4a261, + GpuMarkerColor = 0xf4a261, Reserved1 = 0xe76f51, }; @@ -48,4 +48,7 @@ enum MarkersPallete : int { #define TRACE_CRIT(msg) \ [](const auto& msg) { TracyMessageC(msg.c_str(), msg.size(), tracy::Color::HotPink); }(msg) +#define GPU_SCOPE_LOCATION(name, color) \ + tracy::SourceLocationData{name, TracyFunction, TracyFile, (uint32_t)TracyLine, color}; + #define FRAME_END FrameMark diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 572316af6..ecce9bb54 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/config.h" +#include "common/debug.h" #include "common/singleton.h" #include "core/file_format/splash.h" #include "core/libraries/system/systemservice.h" @@ -270,14 +271,50 @@ void RendererVulkan::Present(Frame* frame) { }; const vk::CommandBuffer cmdbuf = frame->cmdbuf; cmdbuf.begin(begin_info); + { + TracyVkZoneC(instance.GetProfilerContext(), cmdbuf, "Host frame", + MarkersPallete::GpuMarkerColor); - const vk::Extent2D extent = swapchain.GetExtent(); - const std::array pre_barriers{ - vk::ImageMemoryBarrier{ - .srcAccessMask = vk::AccessFlagBits::eNone, - .dstAccessMask = vk::AccessFlagBits::eTransferWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eTransferDstOptimal, + const vk::Extent2D extent = swapchain.GetExtent(); + const std::array pre_barriers{ + vk::ImageMemoryBarrier{ + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eTransferWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eTransferDstOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapchain_image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }, + vk::ImageMemoryBarrier{ + .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, + .dstAccessMask = vk::AccessFlagBits::eTransferRead, + .oldLayout = vk::ImageLayout::eGeneral, + .newLayout = vk::ImageLayout::eTransferSrcOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = frame->image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }, + }; + const vk::ImageMemoryBarrier post_barrier{ + .srcAccessMask = vk::AccessFlagBits::eTransferWrite, + .dstAccessMask = vk::AccessFlagBits::eMemoryRead, + .oldLayout = vk::ImageLayout::eTransferDstOptimal, + .newLayout = vk::ImageLayout::ePresentSrcKHR, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = swapchain_image, @@ -288,54 +325,22 @@ void RendererVulkan::Present(Frame* frame) { .baseArrayLayer = 0, .layerCount = VK_REMAINING_ARRAY_LAYERS, }, - }, - vk::ImageMemoryBarrier{ - .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, - .dstAccessMask = vk::AccessFlagBits::eTransferRead, - .oldLayout = vk::ImageLayout::eGeneral, - .newLayout = vk::ImageLayout::eTransferSrcOptimal, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = frame->image, - .subresourceRange{ - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }, - }; - const vk::ImageMemoryBarrier post_barrier{ - .srcAccessMask = vk::AccessFlagBits::eTransferWrite, - .dstAccessMask = vk::AccessFlagBits::eMemoryRead, - .oldLayout = vk::ImageLayout::eTransferDstOptimal, - .newLayout = vk::ImageLayout::ePresentSrcKHR, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapchain_image, - .subresourceRange{ - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }; + }; - cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, - {}, {}, pre_barriers); + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::PipelineStageFlagBits::eTransfer, + vk::DependencyFlagBits::eByRegion, {}, {}, pre_barriers); - cmdbuf.blitImage(frame->image, vk::ImageLayout::eTransferSrcOptimal, swapchain_image, - vk::ImageLayout::eTransferDstOptimal, - MakeImageBlit(frame->width, frame->height, extent.width, extent.height), - vk::Filter::eLinear); - - cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, - vk::PipelineStageFlagBits::eAllCommands, - vk::DependencyFlagBits::eByRegion, {}, {}, post_barrier); + cmdbuf.blitImage(frame->image, vk::ImageLayout::eTransferSrcOptimal, swapchain_image, + vk::ImageLayout::eTransferDstOptimal, + MakeImageBlit(frame->width, frame->height, extent.width, extent.height), + vk::Filter::eLinear); + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, + vk::PipelineStageFlagBits::eAllCommands, + vk::DependencyFlagBits::eByRegion, {}, {}, post_barrier); + } + TracyVkCollect(instance.GetProfilerContext(), cmdbuf); cmdbuf.end(); static constexpr std::array<vk::PipelineStageFlags, 2> wait_stage_masks = { diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 6d19452da..06a47675c 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -160,6 +160,7 @@ bool Instance::CreateDevice() { // The next two extensions are required to be available together in order to support write masks color_write_en = add_extension(VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME); color_write_en &= add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME); + const auto calibrated_timestamps = add_extension(VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME); const auto family_properties = physical_device.getQueueFamilyProperties(); if (family_properties.empty()) { @@ -212,6 +213,7 @@ bool Instance::CreateDevice() { }, vk::PhysicalDeviceVulkan12Features{ .scalarBlockLayout = true, + .hostQueryReset = true, .timelineSemaphore = true, }, vk::PhysicalDeviceVulkan13Features{ @@ -251,6 +253,27 @@ bool Instance::CreateDevice() { graphics_queue = device->getQueue(queue_family_index, 0); present_queue = device->getQueue(queue_family_index, 0); + if (calibrated_timestamps) { + const auto& time_domains = physical_device.getCalibrateableTimeDomainsEXT(); +#if _WIN64 + const bool has_host_time_domain = + std::find(time_domains.cbegin(), time_domains.cend(), + vk::TimeDomainEXT::eQueryPerformanceCounter) != time_domains.cend(); +#else + const bool has_host_time_domain = + std::find(time_domains.cbegin(), time_domains.cend(), + vk::TimeDomainEXT::eClockMonotonicRaw) != time_domains.cend(); +#endif + if (has_host_time_domain) { + static constexpr std::string_view context_name{"vk_rasterizer"}; + profiler_context = + TracyVkContextHostCalibrated(*instance, physical_device, *device, + VULKAN_HPP_DEFAULT_DISPATCHER.vkGetInstanceProcAddr, + VULKAN_HPP_DEFAULT_DISPATCHER.vkGetDeviceProcAddr); + TracyVkContextName(profiler_context, context_name.data(), context_name.size()); + } + } + CreateAllocator(); return true; } diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 797eb8869..f8e3c2e9d 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -7,6 +7,9 @@ #include "video_core/renderer_vulkan/vk_platform.h" +#define TRACY_VK_USE_SYMBOL_TABLE +#include <tracy/TracyVulkan.hpp> + namespace Frontend { class WindowSDL; } @@ -67,6 +70,10 @@ public: return present_queue; } + TracyVkCtx GetProfilerContext() const { + return profiler_context; + } + /// Returns true when a known debugging tool is attached. bool HasDebuggingToolAttached() const { return has_renderdoc || has_nsight_graphics; @@ -208,6 +215,7 @@ private: vk::Queue graphics_queue; std::vector<vk::PhysicalDevice> physical_devices; std::vector<std::string> available_extensions; + TracyVkCtx profiler_context{}; u32 queue_family_index{0}; bool image_view_reinterpretation{true}; bool timeline_semaphores{}; diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 8e265f728..54cd69742 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -2,17 +2,21 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include <mutex> +#include "common/debug.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" namespace Vulkan { Scheduler::Scheduler(const Instance& instance) - : master_semaphore{instance}, command_pool{instance, &master_semaphore} { + : instance{instance}, master_semaphore{instance}, command_pool{instance, &master_semaphore} { + profiler_scope = reinterpret_cast<tracy::VkCtxScope*>(std::malloc(sizeof(tracy::VkCtxScope))); AllocateWorkerCommandBuffers(); } -Scheduler::~Scheduler() = default; +Scheduler::~Scheduler() { + std::free(profiler_scope); +} void Scheduler::Flush(vk::Semaphore signal, vk::Semaphore wait) { // When flushing, we only send data to the worker thread; no waiting is necessary. @@ -41,11 +45,18 @@ void Scheduler::AllocateWorkerCommandBuffers() { current_cmdbuf = command_pool.Commit(); current_cmdbuf.begin(begin_info); + + static const auto scope_loc = GPU_SCOPE_LOCATION("Guest Frame", MarkersPallete::GpuMarkerColor); + new (profiler_scope) + tracy::VkCtxScope{instance.GetProfilerContext(), &scope_loc, current_cmdbuf, true}; } void Scheduler::SubmitExecution(vk::Semaphore signal_semaphore, vk::Semaphore wait_semaphore) { const u64 signal_value = master_semaphore.NextTick(); + profiler_scope->~VkCtxScope(); + TracyVkCollect(instance.GetProfilerContext(), current_cmdbuf); + std::scoped_lock lk{submit_mutex}; master_semaphore.SubmitWork(current_cmdbuf, wait_semaphore, signal_semaphore, signal_value); master_semaphore.Refresh(); diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index fde48824d..284c288a7 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -54,10 +54,12 @@ private: void SubmitExecution(vk::Semaphore signal_semaphore, vk::Semaphore wait_semaphore); private: + const Instance& instance; MasterSemaphore master_semaphore; CommandPool command_pool; vk::CommandBuffer current_cmdbuf; std::condition_variable_any event_cv; + tracy::VkCtxScope* profiler_scope{}; }; } // namespace Vulkan From 955752a24b371fdcdf52e75fee8e3bfe16187489 Mon Sep 17 00:00:00 2001 From: psucien <bad_cast@protonmail.com> Date: Tue, 11 Jun 2024 21:58:35 +0200 Subject: [PATCH 14/17] tracy: submodule update --- externals/tracy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/externals/tracy b/externals/tracy index ef96964f7..c6d779d78 160000 --- a/externals/tracy +++ b/externals/tracy @@ -1 +1 @@ -Subproject commit ef96964f71885e9ff177253ce0465569787e4a4c +Subproject commit c6d779d78508514102fbe1b8eb28bda10d95bb2a From 8362e2c49771891922899e2de4741dd0203ce376 Mon Sep 17 00:00:00 2001 From: georgemoralis <giorgosmrls@gmail.com> Date: Tue, 11 Jun 2024 23:26:13 +0300 Subject: [PATCH 15/17] fixed windows qt build --- src/core/libraries/kernel/time_management.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/libraries/kernel/time_management.cpp b/src/core/libraries/kernel/time_management.cpp index 19f2847e5..a4362e28c 100644 --- a/src/core/libraries/kernel/time_management.cpp +++ b/src/core/libraries/kernel/time_management.cpp @@ -9,12 +9,12 @@ #include "core/libraries/libs.h" #ifdef _WIN64 -#include <Windows.h> #include <pthread_time.h> +#include <windows.h> // http://stackoverflow.com/a/31411628/4725495 static u32(__stdcall* NtDelayExecution)(BOOL Alertable, PLARGE_INTEGER DelayInterval) = - (u32(__stdcall*)(BOOL, PLARGE_INTEGER))GetProcAddress(GetModuleHandle("ntdll.dll"), + (u32(__stdcall*)(BOOL, PLARGE_INTEGER))GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtDelayExecution"); #else #include <time.h> From 64569ff7375652a38771c01051e85d4dd71d1079 Mon Sep 17 00:00:00 2001 From: psucien <bad_cast@protonmail.com> Date: Tue, 11 Jun 2024 22:57:37 +0200 Subject: [PATCH 16/17] tracy: guards for missing vk profiler context --- .../renderer_vulkan/renderer_vulkan.cpp | 10 +++++++--- src/video_core/renderer_vulkan/vk_scheduler.cpp | 16 +++++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index ecce9bb54..87fb447bb 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -272,8 +272,9 @@ void RendererVulkan::Present(Frame* frame) { const vk::CommandBuffer cmdbuf = frame->cmdbuf; cmdbuf.begin(begin_info); { - TracyVkZoneC(instance.GetProfilerContext(), cmdbuf, "Host frame", - MarkersPallete::GpuMarkerColor); + auto* profiler_ctx = instance.GetProfilerContext(); + TracyVkNamedZoneC(profiler_ctx, renderer_gpu_zone, cmdbuf, "Host frame", + MarkersPallete::GpuMarkerColor, profiler_ctx != nullptr); const vk::Extent2D extent = swapchain.GetExtent(); const std::array pre_barriers{ @@ -339,8 +340,11 @@ void RendererVulkan::Present(Frame* frame) { cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eAllCommands, vk::DependencyFlagBits::eByRegion, {}, {}, post_barrier); + + if (profiler_ctx) { + TracyVkCollect(profiler_ctx, cmdbuf); + } } - TracyVkCollect(instance.GetProfilerContext(), cmdbuf); cmdbuf.end(); static constexpr std::array<vk::PipelineStageFlags, 2> wait_stage_masks = { diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 54cd69742..7ed311f7b 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -46,16 +46,22 @@ void Scheduler::AllocateWorkerCommandBuffers() { current_cmdbuf = command_pool.Commit(); current_cmdbuf.begin(begin_info); - static const auto scope_loc = GPU_SCOPE_LOCATION("Guest Frame", MarkersPallete::GpuMarkerColor); - new (profiler_scope) - tracy::VkCtxScope{instance.GetProfilerContext(), &scope_loc, current_cmdbuf, true}; + auto* profiler_ctx = instance.GetProfilerContext(); + if (profiler_ctx) { + static const auto scope_loc = + GPU_SCOPE_LOCATION("Guest Frame", MarkersPallete::GpuMarkerColor); + new (profiler_scope) tracy::VkCtxScope{profiler_ctx, &scope_loc, current_cmdbuf, true}; + } } void Scheduler::SubmitExecution(vk::Semaphore signal_semaphore, vk::Semaphore wait_semaphore) { const u64 signal_value = master_semaphore.NextTick(); - profiler_scope->~VkCtxScope(); - TracyVkCollect(instance.GetProfilerContext(), current_cmdbuf); + auto* profiler_ctx = instance.GetProfilerContext(); + if (profiler_ctx) { + profiler_scope->~VkCtxScope(); + TracyVkCollect(profiler_ctx, current_cmdbuf); + } std::scoped_lock lk{submit_mutex}; master_semaphore.SubmitWork(current_cmdbuf, wait_semaphore, signal_semaphore, signal_value); From e62690759d5f8ebe99e442fc26246fe507ef93ef Mon Sep 17 00:00:00 2001 From: Xphalnos <164882787+Xphalnos@users.noreply.github.com> Date: Wed, 12 Jun 2024 18:09:54 +0200 Subject: [PATCH 17/17] Fix missing SDL3.dll error message for Windows-Qt (#193) --- .github/workflows/windows-qt.yml | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/windows-qt.yml b/.github/workflows/windows-qt.yml index 4db3c69cb..7cad4630c 100644 --- a/.github/workflows/windows-qt.yml +++ b/.github/workflows/windows-qt.yml @@ -45,6 +45,7 @@ jobs: mkdir upload move build/Release/shadPS4.exe upload move build/Release/zlib-ng2.dll upload + move build/Release/SDL3.dll upload windeployqt --dir upload upload/shadPS4.exe - name: Upload executable diff --git a/README.md b/README.md index 8c88f1541..c93a3b44d 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Check the build instructions for [**Windows**](https://github.com/shadps4-emu/sh ## Linux -Check the build instructions for [**Linux**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/linux_building.md). +Check the build instructions for [**Linux**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-linux.md). ## Build status