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 m_pkg_viewer; +std::vector m_elf_viewer; +std::vector 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 pkgList) { + m_pkg_viewer.resize(pkgList.size()); + m_pkg_viewer = pkgList; +} +void setElfViewer(std::vector elfList) { + m_elf_viewer.resize(elfList.size()); + m_elf_viewer = elfList; +} +void setRecentFiles(std::vector 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 getPkgViewer() { + return m_pkg_viewer; +} +std::vector getElfViewer() { + return m_elf_viewer; +} +std::vector 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(lle, "libc", true); } } + if (data.contains("GUI")) { + auto guiResult = toml::expect(data.at("GUI")); + if (guiResult.is_ok()) { + auto gui = guiResult.unwrap(); + + m_icon_size = toml::find_or(gui, "iconSize", 0); + m_icon_size_grid = toml::find_or(gui, "iconSizeGrid", 0); + m_slider_pos = toml::find_or(gui, "sliderPos", 0); + m_slider_pos_grid = toml::find_or(gui, "sliderPosGrid", 0); + mw_themes = toml::find_or(gui, "theme", 0); + m_window_size_W = toml::find_or(gui, "mw_width", 0); + m_window_size_H = toml::find_or(gui, "mw_height", 0); + settings_install_dir = toml::find_or(gui, "installDir", ""); + main_window_geometry_x = toml::find_or(gui, "geometry_x", 0); + main_window_geometry_y = toml::find_or(gui, "geometry_y", 0); + main_window_geometry_w = toml::find_or(gui, "geometry_w", 0); + main_window_geometry_h = toml::find_or(gui, "geometry_h", 0); + m_pkg_viewer = toml::find_or>(gui, "pkgDirs", {}); + m_elf_viewer = toml::find_or>(gui, "elfDirs", {}); + m_recent_files = toml::find_or>(gui, "recentFiles", {}); + m_table_mode = toml::find_or(gui, "gameTableMode", 0); + } + } } void save(const std::filesystem::path& path) { toml::basic_value 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 #include "types.h" +#include 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 pkgList); +void setElfViewer(std::vector elfList); +void setRecentFiles(std::vector 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 getPkgViewer(); +std::vector getElfViewer(); +std::vector 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 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 #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 dec_key, std::span 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 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 ivkey, } } +void Crypto::aesCbcCfb128DecryptEntry(std::span ivkey, + std::span ciphertext, + std::span decrypted) { + std::array key; + std::array 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 NPcommID, + std::span efsmIv, std::span ciphertext, + std::span decrypted) { + + std::vector TrophyKey = {0x21, 0xF4, 0x1A, 0x6B, 0xAD, 0x8A, 0x1D, 0x3E, + 0xCA, 0x7A, 0xD5, 0x86, 0xC1, 0x01, 0xB7, 0xA9}; + std::vector TrophyIV = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + // step 1: Encrypt NPcommID + CryptoPP::CBC_Mode::Encryption encrypt; + encrypt.SetKeyWithIV(TrophyKey.data(), TrophyKey.size(), TrophyIV.data()); + + std::vector trpKey(16); + + encrypt.ProcessData(trpKey.data(), NPcommID.data(), 16); + // step 2: decrypt efsm. + CryptoPP::CBC_Mode::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 ekpfs, std::span seed, std::span dataKey, @@ -151,8 +194,8 @@ void Crypto::decryptPFS(std::span 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::Encryption encrypt(tweakKey.data(), tweakKey.size()); - CryptoPP::ECB_Mode::Decryption decrypt(dataKey.data(), dataKey.size()); + CryptoPP::ECB_Mode::Encryption encrypt(tweakKey.data(), tweakKey.size()); + CryptoPP::ECB_Mode::Decryption decrypt(dataKey.data(), dataKey.size()); std::array tweak{}; std::array 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 dk3, std::span ciphertext, @@ -35,6 +33,11 @@ public: void aesCbcCfb128Decrypt(std::span ivkey, std::span ciphertext, std::span decrypted); + void aesCbcCfb128DecryptEntry(std::span ivkey, + std::span ciphertext, + std::span decrypted); + void decryptEFSM(std::span, std::span efsmIv, + std::span ciphertext, std::span decrypted); void PfsGenCryptoKey(std::span ekpfs, std::span seed, std::span 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 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(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(&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 concatenated_ivkey_dk3; std::array seed_digest; std::array, 7> digest1; std::array, 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 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(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 cipherNp(pkg.data() + entry.offset, entry.size); + std::array 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 seed; + std::array 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 pfs_encrypted(length); file.Seek(pkgheader.pfs_image_offset); file.Read(pfs_encrypted); - + file.Close(); // Decrypt the pfs_image. std::vector 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 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(flag); + } + + static constexpr std::array, 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 pkg; u64 pkgSize = 0; char pkgTitleID[9]; PKGHeader pkgheader; + std::string pkgFlags; std::unordered_map extractPaths; std::vector fsTable; @@ -133,12 +162,13 @@ private: std::vector sectorMap; u64 pfsc_offset; - std::array dk3_; - std::array ivKey; - std::array imgKey; - std::array ekpfsKey; - std::array dataKey; - std::array tweakKey; + std::array dk3_; + std::array ivKey; + std::array imgKey; + std::array ekpfsKey; + std::array dataKey; + std::array tweakKey; + std::vector 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 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 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(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& 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 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 ESFM(entry.entry_len - iv_len); + std::vector 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 +#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 NPcommID = std::vector(12); + std::array np_comm_id{}; + std::array 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::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 00000000..13bf221e Binary files /dev/null and b/src/images/flag_china.png differ diff --git a/src/images/flag_eu.png b/src/images/flag_eu.png new file mode 100644 index 00000000..0922e11e Binary files /dev/null and b/src/images/flag_eu.png differ diff --git a/src/images/flag_jp.png b/src/images/flag_jp.png new file mode 100644 index 00000000..6433eecf Binary files /dev/null and b/src/images/flag_jp.png differ diff --git a/src/images/flag_unk.png b/src/images/flag_unk.png new file mode 100644 index 00000000..5c381028 Binary files /dev/null and b/src/images/flag_unk.png differ diff --git a/src/images/flag_us.png b/src/images/flag_us.png new file mode 100644 index 00000000..f4bf3a30 Binary files /dev/null and b/src/images/flag_us.png differ diff --git a/src/images/flag_world.png b/src/images/flag_world.png new file mode 100644 index 00000000..0dcccf80 Binary files /dev/null and b/src/images/flag_world.png differ 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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 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 game_info_get, - std::shared_ptr 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 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 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 m_games_search, bool fromSearch) { @@ -43,9 +46,7 @@ void GameGridFrame::PopulateGameGrid(QVector m_games_search, bool from else m_games_ = m_game_info->m_games; m_games_shared = std::make_shared>(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 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 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 #include #include +#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 m_game_info; - std::shared_ptr m_gui_settings_; std::shared_ptr> m_games_shared; public: - explicit GameGridFrame(std::shared_ptr game_info_get, - std::shared_ptr m_gui_settings, QWidget* parent = nullptr); + explicit GameGridFrame(std::shared_ptr game_info_get, QWidget* parent = nullptr); void PopulateGameGrid(QVector 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 #include +#include +#include #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 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> 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 futureWatcher; + GameListUtils game_util; + bool finished = false; + futureWatcher.setFuture(QtConcurrent::map(m_games, game_util.GetFolderSize)); + connect(&futureWatcher, &QFutureWatcher::finished, [&]() { + dialog.reset(); + std::sort(m_games.begin(), m_games.end(), CompareStrings); + }); + connect(&dialog, &QProgressDialog::canceled, &futureWatcher, &QFutureWatcher::cancel); + dialog.setRange(0, m_games.size()); + connect(&futureWatcher, &QFutureWatcher::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 #include +#include #include #include +#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 m_gui_settings = std::make_shared(); + GameInfoClass(); + ~GameInfoClass(); + void GetGameInfo(QWidget* parent = nullptr); QVector 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 #include #include -#include "gui_settings.h" -GameInstallDialog::GameInstallDialog(std::shared_ptr 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 -#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 gui_settings); + GameInstallDialog(); ~GameInstallDialog(); private slots: @@ -23,5 +24,4 @@ private: private: QLineEdit* m_gamesDirectory; - std::shared_ptr 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 game_info_get, - std::shared_ptr 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 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 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 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 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 game_info_get, - std::shared_ptr m_gui_settings, QWidget* parent = nullptr); + explicit GameListFrame(std::shared_ptr 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 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 #include +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 #include #include "game_info.h" +#include "trophy_viewer.h" -class GuiContextMenus { +class GuiContextMenus : public QObject { + Q_OBJECT public: void RequestGameMenu(const QPoint& pos, QVector 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 InstallDragDropPkg) { + void RequestGameMenuPKGViewer( + const QPoint& pos, QStringList m_pkg_app_list, QTreeWidget* treeWidget, + std::function 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 -#include - -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(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 - -#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 +#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(); - 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 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(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(Theme::Light)); + Config::setMainWindowTheme(static_cast(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(Theme::Dark)); + Config::setMainWindowTheme(static_cast(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(Theme::Green)); + Config::setMainWindowTheme(static_cast(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(Theme::Blue)); + Config::setMainWindowTheme(static_cast(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(Theme::Violet)); + Config::setMainWindowTheme(static_cast(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 indices; + QVector 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 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::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::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(m_gui_settings->GetValue(gui::mw_themes).toInt()); + Theme lastTheme = static_cast(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 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 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 #include #include #include #include +#include +#include +#include +#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; - - 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 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 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 m_game_list_frame; + QScopedPointer m_game_grid_frame; + QScopedPointer m_elf_viewer; // Status Bar. - QStatusBar* statusBar = nullptr; + QScopedPointer statusBar; + + PSF psf; std::shared_ptr m_game_info = std::make_shared(); - std::shared_ptr 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 #include "pkg_viewer.h" -PKGViewer::PKGViewer(std::shared_ptr game_info_get, - std::shared_ptr m_gui_settings, - std::function InstallDragDropPkg) - : QMainWindow() { +PKGViewer::PKGViewer(std::shared_ptr game_info_get, QWidget* parent, + std::function 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 game_info_get, QStringList headers; headers << "Name" << "Serial" - << "Size" << "Installed" + << "Size" << "Category" << "Type" << "App Ver" @@ -50,6 +52,8 @@ PKGViewer::PKGViewer(std::shared_ptr 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(&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 game_info_get, - std::shared_ptr m_gui_settings, - std::function InstallDragDropPkg = nullptr); + explicit PKGViewer( + std::shared_ptr game_info_get, QWidget* parent, + std::function 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 pkg; - std::vector psf; u64 pkgSize = 0; - std::shared_ptr m_gui_settings_; std::unordered_map map_strings; std::unordered_map map_integers; @@ -58,18 +54,6 @@ private: // Status bar QStatusBar* statusBar; - std::vector> 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> appTypes = { {0, "FULL APP"}, {1, "UPGRADABLE"}, @@ -77,47 +61,11 @@ private: {3, "FREEMIUM"}, }; - bool isFlagSet(u32_be variable, PKGContentFlag flag) { - return (variable) & static_cast(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 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 - -#include -#include -#include -#include - -#include "gui_save.h" - -typedef QPair q_string_pair; -typedef QPair q_size_pair; -typedef QList q_pair_list; -typedef QList 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 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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 @@ images/controller_icon.png images/refresh_icon.png images/list_mode_icon.png + images/flag_jp.png + images/flag_eu.png + images/flag_unk.png + images/flag_us.png + images/flag_world.png + images/flag_china.png