From 0f27e0edf22e2a111ade9b6d6b43f851b9a18451 Mon Sep 17 00:00:00 2001 From: raziel1000 Date: Mon, 10 Jun 2024 20:42:21 -0600 Subject: [PATCH 1/2] - 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 69e066f5..283c680b 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 2e9b3a33..7bb549d2 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 63dd4c13..a577b143 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 7af028dc..c41c8c29 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 59cfcf7b..6beeb794 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 d45b5651..630faa34 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 11edef84..2f926054 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 5b8a8862..1c7ddfba 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 2251402d..5150e128 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 bf2ce891..f77a7804 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 fb808697..4a7f6215 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 31978630..17e515de 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 00000000..cb55af2e --- /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 00000000..6d1f13bd --- /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 b12821c1..f80bfbb8 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 2f4d0651..608970dc 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 dd8de3a7..793d996a 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 00000000..1674e1ab --- /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 00000000..15aeb55f --- /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 0b57a162..ca28e9ce 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 751184ce..19ac531b 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 f48ab327..39cdeb75 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 82477287..c137a5a6 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 3ae78da9..ab4fc273 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 b75aaaf6..abd447d9 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 47e03789..f0ba113e 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 24ef8081..e9f75afd 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 2561f2d7..2e25f122 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 06d4334a..c7349500 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 e2434f75..00000000 --- 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 d2fd3b3b..00000000 --- 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 d56fa54a..00000000 --- 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 5edefc01..3fc1452c 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 6f8dc148..ee7ab650 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 dfe448b9..27d14b93 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 6d7b5260..7b5bf181 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 9bccb482..cf0b2167 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 3be2c526..0e0a8706 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 b428bcda..00000000 --- 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 1e6d1a65..00000000 --- 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 00000000..8c28019e --- /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 00000000..ab79ac50 --- /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 ac5b2dd6..cdbae786 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 2/2] 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 c41c8c29..0a3b4905 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);