- Added trophy decryption when extracting a fpkg. trp icons and xmls are dumped to game_data/<title> (can be restored if deleted by accident by opening the trophy viewer)

- Added a trophy viewer (right click on game ==> trophy viewer)
- Enabled Run button.
- Switched gui settings to toml.
- Added recent files (6 max)
- Applied @raphaelthegreat suggestions and corrections (Thanks a lot).
- Fixed several bugs and crashes.
- Full screen should disabled by default.
- Added region in list mode.
- Added a simple temp elf list widget.
- Added messages when extracting pkg (ex: installing a patch before the game...etc)
This commit is contained in:
raziel1000 2024-06-10 20:42:21 -06:00
parent 71dda8c776
commit 0f27e0edf2
49 changed files with 1616 additions and 891 deletions

View File

@ -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

View File

@ -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()

View File

@ -24,6 +24,23 @@ static bool shouldDumpShaders = false;
static bool shouldDumpPM4 = false;
static bool vkValidation = false;
static bool vkValidationSync = false;
// Gui
std::string settings_install_dir = "";
u32 main_window_geometry_x = 400;
u32 main_window_geometry_y = 400;
u32 main_window_geometry_w = 1280;
u32 main_window_geometry_h = 720;
u32 mw_themes = 0;
u32 m_icon_size = 36;
u32 m_icon_size_grid = 69;
u32 m_slider_pos = 0;
u32 m_slider_pos_grid = 0;
u32 m_table_mode = 0;
u32 m_window_size_W = 1280;
u32 m_window_size_H = 720;
std::vector<std::string> m_pkg_viewer;
std::vector<std::string> m_elf_viewer;
std::vector<std::string> m_recent_files;
bool isLleLibc() {
return isLibc;
@ -85,6 +102,101 @@ bool vkValidationSyncEnabled() {
return vkValidationSync;
}
void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) {
main_window_geometry_x = x;
main_window_geometry_y = y;
main_window_geometry_w = w;
main_window_geometry_h = h;
}
void setGameInstallDir(const std::string& dir) {
settings_install_dir = dir;
}
void setMainWindowTheme(u32 theme) {
mw_themes = theme;
}
void setIconSize(u32 size) {
m_icon_size = size;
}
void setIconSizeGrid(u32 size) {
m_icon_size_grid = size;
}
void setSliderPositon(u32 pos) {
m_slider_pos = pos;
}
void setSliderPositonGrid(u32 pos) {
m_slider_pos_grid = pos;
}
void setTableMode(u32 mode) {
m_table_mode = mode;
}
void setMainWindowWidth(u32 width) {
m_window_size_W = width;
}
void setMainWindowHeight(u32 height) {
m_window_size_H = height;
}
void setPkgViewer(std::vector<std::string> pkgList) {
m_pkg_viewer.resize(pkgList.size());
m_pkg_viewer = pkgList;
}
void setElfViewer(std::vector<std::string> elfList) {
m_elf_viewer.resize(elfList.size());
m_elf_viewer = elfList;
}
void setRecentFiles(std::vector<std::string> recentFiles) {
m_recent_files.resize(recentFiles.size());
m_recent_files = recentFiles;
}
u32 getMainWindowGeometryX() {
return main_window_geometry_x;
}
u32 getMainWindowGeometryY() {
return main_window_geometry_y;
}
u32 getMainWindowGeometryW() {
return main_window_geometry_w;
}
u32 getMainWindowGeometryH() {
return main_window_geometry_h;
}
std::string getGameInstallDir() {
return settings_install_dir;
}
u32 getMainWindowTheme() {
return mw_themes;
}
u32 getIconSize() {
return m_icon_size;
}
u32 getIconSizeGrid() {
return m_icon_size_grid;
}
u32 getSliderPositon() {
return m_slider_pos;
}
u32 getSliderPositonGrid() {
return m_slider_pos_grid;
}
u32 getTableMode() {
return m_table_mode;
}
u32 getMainWindowWidth() {
return m_window_size_W;
}
u32 getMainWindowHeight() {
return m_window_size_H;
}
std::vector<std::string> getPkgViewer() {
return m_pkg_viewer;
}
std::vector<std::string> getElfViewer() {
return m_elf_viewer;
}
std::vector<std::string> getRecentFiles() {
return m_recent_files;
}
void load(const std::filesystem::path& path) {
// If the configuration file does not exist, create it and return
std::error_code error;
@ -152,6 +264,29 @@ void load(const std::filesystem::path& path) {
isLibc = toml::find_or<toml::boolean>(lle, "libc", true);
}
}
if (data.contains("GUI")) {
auto guiResult = toml::expect<toml::value>(data.at("GUI"));
if (guiResult.is_ok()) {
auto gui = guiResult.unwrap();
m_icon_size = toml::find_or<toml::integer>(gui, "iconSize", 0);
m_icon_size_grid = toml::find_or<toml::integer>(gui, "iconSizeGrid", 0);
m_slider_pos = toml::find_or<toml::integer>(gui, "sliderPos", 0);
m_slider_pos_grid = toml::find_or<toml::integer>(gui, "sliderPosGrid", 0);
mw_themes = toml::find_or<toml::integer>(gui, "theme", 0);
m_window_size_W = toml::find_or<toml::integer>(gui, "mw_width", 0);
m_window_size_H = toml::find_or<toml::integer>(gui, "mw_height", 0);
settings_install_dir = toml::find_or<toml::string>(gui, "installDir", "");
main_window_geometry_x = toml::find_or<toml::integer>(gui, "geometry_x", 0);
main_window_geometry_y = toml::find_or<toml::integer>(gui, "geometry_y", 0);
main_window_geometry_w = toml::find_or<toml::integer>(gui, "geometry_w", 0);
main_window_geometry_h = toml::find_or<toml::integer>(gui, "geometry_h", 0);
m_pkg_viewer = toml::find_or<std::vector<std::string>>(gui, "pkgDirs", {});
m_elf_viewer = toml::find_or<std::vector<std::string>>(gui, "elfDirs", {});
m_recent_files = toml::find_or<std::vector<std::string>>(gui, "recentFiles", {});
m_table_mode = toml::find_or<toml::integer>(gui, "gameTableMode", 0);
}
}
}
void save(const std::filesystem::path& path) {
toml::basic_value<toml::preserve_comments> data;
@ -187,6 +322,22 @@ void save(const std::filesystem::path& path) {
data["Vulkan"]["validation_sync"] = vkValidationSync;
data["Debug"]["DebugDump"] = isDebugDump;
data["LLE"]["libc"] = isLibc;
data["GUI"]["theme"] = mw_themes;
data["GUI"]["iconSize"] = m_icon_size;
data["GUI"]["sliderPos"] = m_slider_pos;
data["GUI"]["iconSizeGrid"] = m_icon_size_grid;
data["GUI"]["sliderPosGrid"] = m_slider_pos_grid;
data["GUI"]["gameTableMode"] = m_table_mode;
data["GUI"]["mw_width"] = m_window_size_W;
data["GUI"]["mw_height"] = m_window_size_H;
data["GUI"]["installDir"] = settings_install_dir;
data["GUI"]["geometry_x"] = main_window_geometry_x;
data["GUI"]["geometry_y"] = main_window_geometry_y;
data["GUI"]["geometry_w"] = main_window_geometry_w;
data["GUI"]["geometry_h"] = main_window_geometry_h;
data["GUI"]["pkgDirs"] = m_pkg_viewer;
data["GUI"]["elfDirs"] = m_elf_viewer;
data["GUI"]["recentFiles"] = m_recent_files;
std::ofstream file(path, std::ios::out);
file << data;

View File

@ -5,6 +5,7 @@
#include <filesystem>
#include "types.h"
#include <vector>
namespace Config {
void load(const std::filesystem::path& path);
@ -29,4 +30,36 @@ bool dumpPM4();
bool vkValidationEnabled();
bool vkValidationSyncEnabled();
// Gui
void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h);
void setGameInstallDir(const std::string& dir);
void setMainWindowTheme(u32 theme);
void setIconSize(u32 size);
void setIconSizeGrid(u32 size);
void setSliderPositon(u32 pos);
void setSliderPositonGrid(u32 pos);
void setTableMode(u32 mode);
void setMainWindowWidth(u32 width);
void setMainWindowHeight(u32 height);
void setPkgViewer(std::vector<std::string> pkgList);
void setElfViewer(std::vector<std::string> elfList);
void setRecentFiles(std::vector<std::string> recentFiles);
u32 getMainWindowGeometryX();
u32 getMainWindowGeometryY();
u32 getMainWindowGeometryW();
u32 getMainWindowGeometryH();
std::string getGameInstallDir();
u32 getMainWindowTheme();
u32 getIconSize();
u32 getIconSizeGrid();
u32 getSliderPositon();
u32 getSliderPositonGrid();
u32 getTableMode();
u32 getMainWindowWidth();
u32 getMainWindowHeight();
std::vector<std::string> getPkgViewer();
std::vector<std::string> getElfViewer();
std::vector<std::string> getRecentFiles();
}; // namespace Config

View File

@ -201,6 +201,11 @@ public:
return WriteSpan(string);
}
static void WriteBytes(const std::filesystem::path path, std::span<u8> vec) {
IOFile out(path, FileAccessMode::Write);
out.Write(vec);
}
private:
std::filesystem::path file_path;
FileAccessMode file_access_mode{};

View File

@ -4,67 +4,69 @@
#include <array>
#include "crypto.h"
RSA::PrivateKey Crypto::key_pkg_derived_key3_keyset_init() {
InvertibleRSAFunction params;
params.SetPrime1(Integer(pkg_derived_key3_keyset.Prime1, 0x80));
params.SetPrime2(Integer(pkg_derived_key3_keyset.Prime2, 0x80));
CryptoPP::RSA::PrivateKey Crypto::key_pkg_derived_key3_keyset_init() {
CryptoPP::InvertibleRSAFunction params;
params.SetPrime1(CryptoPP::Integer(pkg_derived_key3_keyset.Prime1, 0x80));
params.SetPrime2(CryptoPP::Integer(pkg_derived_key3_keyset.Prime2, 0x80));
params.SetPublicExponent(Integer(pkg_derived_key3_keyset.PublicExponent, 4));
params.SetPrivateExponent(Integer(pkg_derived_key3_keyset.PrivateExponent, 0x100));
params.SetPublicExponent(CryptoPP::Integer(pkg_derived_key3_keyset.PublicExponent, 4));
params.SetPrivateExponent(CryptoPP::Integer(pkg_derived_key3_keyset.PrivateExponent, 0x100));
params.SetModPrime1PrivateExponent(Integer(pkg_derived_key3_keyset.Exponent1, 0x80));
params.SetModPrime2PrivateExponent(Integer(pkg_derived_key3_keyset.Exponent2, 0x80));
params.SetModPrime1PrivateExponent(CryptoPP::Integer(pkg_derived_key3_keyset.Exponent1, 0x80));
params.SetModPrime2PrivateExponent(CryptoPP::Integer(pkg_derived_key3_keyset.Exponent2, 0x80));
params.SetModulus(Integer(pkg_derived_key3_keyset.Modulus, 0x100));
params.SetModulus(CryptoPP::Integer(pkg_derived_key3_keyset.Modulus, 0x100));
params.SetMultiplicativeInverseOfPrime2ModPrime1(
Integer(pkg_derived_key3_keyset.Coefficient, 0x80));
CryptoPP::Integer(pkg_derived_key3_keyset.Coefficient, 0x80));
RSA::PrivateKey privateKey(params);
CryptoPP::RSA::PrivateKey privateKey(params);
return privateKey;
}
RSA::PrivateKey Crypto::FakeKeyset_keyset_init() {
InvertibleRSAFunction params;
params.SetPrime1(Integer(FakeKeyset_keyset.Prime1, 0x80));
params.SetPrime2(Integer(FakeKeyset_keyset.Prime2, 0x80));
CryptoPP::RSA::PrivateKey Crypto::FakeKeyset_keyset_init() {
CryptoPP::InvertibleRSAFunction params;
params.SetPrime1(CryptoPP::Integer(FakeKeyset_keyset.Prime1, 0x80));
params.SetPrime2(CryptoPP::Integer(FakeKeyset_keyset.Prime2, 0x80));
params.SetPublicExponent(Integer(FakeKeyset_keyset.PublicExponent, 4));
params.SetPrivateExponent(Integer(FakeKeyset_keyset.PrivateExponent, 0x100));
params.SetPublicExponent(CryptoPP::Integer(FakeKeyset_keyset.PublicExponent, 4));
params.SetPrivateExponent(CryptoPP::Integer(FakeKeyset_keyset.PrivateExponent, 0x100));
params.SetModPrime1PrivateExponent(Integer(FakeKeyset_keyset.Exponent1, 0x80));
params.SetModPrime2PrivateExponent(Integer(FakeKeyset_keyset.Exponent2, 0x80));
params.SetModPrime1PrivateExponent(CryptoPP::Integer(FakeKeyset_keyset.Exponent1, 0x80));
params.SetModPrime2PrivateExponent(CryptoPP::Integer(FakeKeyset_keyset.Exponent2, 0x80));
params.SetModulus(Integer(FakeKeyset_keyset.Modulus, 0x100));
params.SetMultiplicativeInverseOfPrime2ModPrime1(Integer(FakeKeyset_keyset.Coefficient, 0x80));
params.SetModulus(CryptoPP::Integer(FakeKeyset_keyset.Modulus, 0x100));
params.SetMultiplicativeInverseOfPrime2ModPrime1(
CryptoPP::Integer(FakeKeyset_keyset.Coefficient, 0x80));
RSA::PrivateKey privateKey(params);
CryptoPP::RSA::PrivateKey privateKey(params);
return privateKey;
}
RSA::PrivateKey Crypto::DebugRifKeyset_init() {
AutoSeededRandomPool rng;
InvertibleRSAFunction params;
params.SetPrime1(Integer(DebugRifKeyset_keyset.Prime1, sizeof(DebugRifKeyset_keyset.Prime1)));
params.SetPrime2(Integer(DebugRifKeyset_keyset.Prime2, sizeof(DebugRifKeyset_keyset.Prime2)));
CryptoPP::RSA::PrivateKey Crypto::DebugRifKeyset_init() {
CryptoPP::InvertibleRSAFunction params;
params.SetPrime1(
CryptoPP::Integer(DebugRifKeyset_keyset.Prime1, sizeof(DebugRifKeyset_keyset.Prime1)));
params.SetPrime2(
CryptoPP::Integer(DebugRifKeyset_keyset.Prime2, sizeof(DebugRifKeyset_keyset.Prime2)));
params.SetPublicExponent(Integer(DebugRifKeyset_keyset.PrivateExponent,
sizeof(DebugRifKeyset_keyset.PrivateExponent)));
params.SetPrivateExponent(Integer(DebugRifKeyset_keyset.PrivateExponent,
sizeof(DebugRifKeyset_keyset.PrivateExponent)));
params.SetPublicExponent(CryptoPP::Integer(DebugRifKeyset_keyset.PrivateExponent,
sizeof(DebugRifKeyset_keyset.PrivateExponent)));
params.SetPrivateExponent(CryptoPP::Integer(DebugRifKeyset_keyset.PrivateExponent,
sizeof(DebugRifKeyset_keyset.PrivateExponent)));
params.SetModPrime1PrivateExponent(
Integer(DebugRifKeyset_keyset.Exponent1, sizeof(DebugRifKeyset_keyset.Exponent1)));
params.SetModPrime2PrivateExponent(
Integer(DebugRifKeyset_keyset.Exponent2, sizeof(DebugRifKeyset_keyset.Exponent2)));
params.SetModPrime1PrivateExponent(CryptoPP::Integer(DebugRifKeyset_keyset.Exponent1,
sizeof(DebugRifKeyset_keyset.Exponent1)));
params.SetModPrime2PrivateExponent(CryptoPP::Integer(DebugRifKeyset_keyset.Exponent2,
sizeof(DebugRifKeyset_keyset.Exponent2)));
params.SetModulus(
Integer(DebugRifKeyset_keyset.Modulus, sizeof(DebugRifKeyset_keyset.Modulus)));
params.SetMultiplicativeInverseOfPrime2ModPrime1(
Integer(DebugRifKeyset_keyset.Coefficient, sizeof(DebugRifKeyset_keyset.Coefficient)));
CryptoPP::Integer(DebugRifKeyset_keyset.Modulus, sizeof(DebugRifKeyset_keyset.Modulus)));
params.SetMultiplicativeInverseOfPrime2ModPrime1(CryptoPP::Integer(
DebugRifKeyset_keyset.Coefficient, sizeof(DebugRifKeyset_keyset.Coefficient)));
RSA::PrivateKey privateKey(params);
CryptoPP::RSA::PrivateKey privateKey(params);
return privateKey;
}
@ -73,21 +75,21 @@ void Crypto::RSA2048Decrypt(std::span<CryptoPP::byte, 32> dec_key,
std::span<const CryptoPP::byte, 256> ciphertext,
bool is_dk3) { // RSAES_PKCS1v15_
// Create an RSA decryptor
RSA::PrivateKey privateKey;
CryptoPP::RSA::PrivateKey privateKey;
if (is_dk3) {
privateKey = key_pkg_derived_key3_keyset_init();
} else {
privateKey = FakeKeyset_keyset_init();
}
RSAES_PKCS1v15_Decryptor rsaDecryptor(privateKey);
CryptoPP::RSAES_PKCS1v15_Decryptor rsaDecryptor(privateKey);
// Allocate memory for the decrypted data
std::array<CryptoPP::byte, 256> decrypted;
// Perform the decryption
AutoSeededRandomPool rng;
DecodingResult result =
CryptoPP::AutoSeededRandomPool rng;
CryptoPP::DecodingResult result =
rsaDecryptor.Decrypt(rng, ciphertext.data(), decrypted.size(), decrypted.data());
std::copy(decrypted.begin(), decrypted.begin() + dec_key.size(), dec_key.begin());
}
@ -120,6 +122,47 @@ void Crypto::aesCbcCfb128Decrypt(std::span<const CryptoPP::byte, 32> ivkey,
}
}
void Crypto::aesCbcCfb128DecryptEntry(std::span<const CryptoPP::byte, 32> ivkey,
std::span<CryptoPP::byte> ciphertext,
std::span<CryptoPP::byte> decrypted) {
std::array<CryptoPP::byte, CryptoPP::AES::DEFAULT_KEYLENGTH> key;
std::array<CryptoPP::byte, CryptoPP::AES::DEFAULT_KEYLENGTH> iv;
std::copy(ivkey.begin() + 16, ivkey.begin() + 16 + key.size(), key.begin());
std::copy(ivkey.begin(), ivkey.begin() + iv.size(), iv.begin());
CryptoPP::AES::Decryption aesDecryption(key.data(), CryptoPP::AES::DEFAULT_KEYLENGTH);
CryptoPP::CBC_Mode_ExternalCipher::Decryption cbcDecryption(aesDecryption, iv.data());
for (size_t i = 0; i < decrypted.size(); i += CryptoPP::AES::BLOCKSIZE) {
cbcDecryption.ProcessData(decrypted.data() + i, ciphertext.data() + i,
CryptoPP::AES::BLOCKSIZE);
}
}
void Crypto::decryptEFSM(std::span<CryptoPP::byte, 16> NPcommID,
std::span<CryptoPP::byte, 16> efsmIv, std::span<CryptoPP::byte> ciphertext,
std::span<CryptoPP::byte> decrypted) {
std::vector<CryptoPP::byte> TrophyKey = {0x21, 0xF4, 0x1A, 0x6B, 0xAD, 0x8A, 0x1D, 0x3E,
0xCA, 0x7A, 0xD5, 0x86, 0xC1, 0x01, 0xB7, 0xA9};
std::vector<CryptoPP::byte> TrophyIV = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
// step 1: Encrypt NPcommID
CryptoPP::CBC_Mode<CryptoPP::AES>::Encryption encrypt;
encrypt.SetKeyWithIV(TrophyKey.data(), TrophyKey.size(), TrophyIV.data());
std::vector<CryptoPP::byte> trpKey(16);
encrypt.ProcessData(trpKey.data(), NPcommID.data(), 16);
// step 2: decrypt efsm.
CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption decrypt;
decrypt.SetKeyWithIV(trpKey.data(), trpKey.size(), efsmIv.data());
for (size_t i = 0; i < decrypted.size(); i += CryptoPP::AES::BLOCKSIZE) {
decrypt.ProcessData(decrypted.data() + i, ciphertext.data() + i, CryptoPP::AES::BLOCKSIZE);
}
}
void Crypto::PfsGenCryptoKey(std::span<const CryptoPP::byte, 32> ekpfs,
std::span<const CryptoPP::byte, 16> seed,
std::span<CryptoPP::byte, 16> dataKey,
@ -151,8 +194,8 @@ void Crypto::decryptPFS(std::span<const CryptoPP::byte, 16> dataKey,
// Start at 0x10000 to keep the header when decrypting the whole pfs_image.
for (int i = 0; i < src_image.size(); i += 0x1000) {
const u64 current_sector = sector + (i / 0x1000);
CryptoPP::ECB_Mode<AES>::Encryption encrypt(tweakKey.data(), tweakKey.size());
CryptoPP::ECB_Mode<AES>::Decryption decrypt(dataKey.data(), dataKey.size());
CryptoPP::ECB_Mode<CryptoPP::AES>::Encryption encrypt(tweakKey.data(), tweakKey.size());
CryptoPP::ECB_Mode<CryptoPP::AES>::Decryption decrypt(dataKey.data(), dataKey.size());
std::array<CryptoPP::byte, 16> tweak{};
std::array<CryptoPP::byte, 16> encryptedTweak;

View File

@ -15,17 +15,15 @@
#include "common/types.h"
#include "keys.h"
using namespace CryptoPP;
class Crypto {
public:
PkgDerivedKey3Keyset pkg_derived_key3_keyset;
FakeKeyset FakeKeyset_keyset;
DebugRifKeyset DebugRifKeyset_keyset;
RSA::PrivateKey key_pkg_derived_key3_keyset_init();
RSA::PrivateKey FakeKeyset_keyset_init();
RSA::PrivateKey DebugRifKeyset_init();
CryptoPP::RSA::PrivateKey key_pkg_derived_key3_keyset_init();
CryptoPP::RSA::PrivateKey FakeKeyset_keyset_init();
CryptoPP::RSA::PrivateKey DebugRifKeyset_init();
void RSA2048Decrypt(std::span<CryptoPP::byte, 32> dk3,
std::span<const CryptoPP::byte, 256> ciphertext,
@ -35,6 +33,11 @@ public:
void aesCbcCfb128Decrypt(std::span<const CryptoPP::byte, 32> ivkey,
std::span<const CryptoPP::byte, 256> ciphertext,
std::span<CryptoPP::byte, 256> decrypted);
void aesCbcCfb128DecryptEntry(std::span<const CryptoPP::byte, 32> ivkey,
std::span<CryptoPP::byte> ciphertext,
std::span<CryptoPP::byte> decrypted);
void decryptEFSM(std::span<CryptoPP::byte, 16>, std::span<CryptoPP::byte, 16> efsmIv,
std::span<CryptoPP::byte> ciphertext, std::span<CryptoPP::byte> decrypted);
void PfsGenCryptoKey(std::span<const CryptoPP::byte, 32> ekpfs,
std::span<const CryptoPP::byte, 16> seed,
std::span<CryptoPP::byte, 16> dataKey,

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <rsa.h>
class FakeKeyset {
public:

View File

@ -45,26 +45,54 @@ PKG::PKG() = default;
PKG::~PKG() = default;
bool PKG::Open(const std::string& filepath) {
bool PKG::Open(const std::filesystem::path& filepath) {
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
return false;
}
pkgSize = file.GetSize();
PKGHeader pkgheader;
file.Read(pkgheader);
if (pkgheader.magic != 0x7F434E54)
return false;
for (const auto& flag : flagNames) {
if (isFlagSet(pkgheader.pkg_content_flags, flag.first)) {
if (!pkgFlags.empty())
pkgFlags += (", ");
pkgFlags += (flag.second);
}
}
// Find title id it is part of pkg_content_id starting at offset 0x40
file.Seek(0x47); // skip first 7 characters of content_id
file.Read(pkgTitleID);
file.Seek(0);
pkg.resize(pkgheader.pkg_promote_size);
file.Read(pkg);
u32 offset = pkgheader.pkg_table_entry_offset;
u32 n_files = pkgheader.pkg_table_entry_count;
for (int i = 0; i < n_files; i++) {
PKGEntry entry;
std::memcpy(&entry, &pkg[offset + i * 0x20], sizeof(entry));
// Try to figure out the name
const auto name = GetEntryNameByType(entry.id);
if (name == "param.sfo") {
sfo.clear();
file.Seek(entry.offset);
sfo.resize(entry.size);
file.ReadRaw<u8>(sfo.data(), entry.size);
}
}
file.Close();
return true;
}
bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extract,
bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::path& extract,
std::string& failreason) {
extract_path = extract;
pkgpath = filepath;
@ -75,6 +103,9 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr
pkgSize = file.GetSize();
file.ReadRaw<u8>(&pkgheader, sizeof(PKGHeader));
if (pkgheader.magic != 0x7F434E54)
return false;
if (pkgheader.pkg_size > pkgSize) {
failreason = "PKG file size is different";
return false;
@ -90,6 +121,7 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr
u32 offset = pkgheader.pkg_table_entry_offset;
u32 n_files = pkgheader.pkg_table_entry_count;
std::array<u8, 64> concatenated_ivkey_dk3;
std::array<u8, 32> seed_digest;
std::array<std::array<u8, 32>, 7> digest1;
std::array<std::array<u8, 256>, 7> key1;
@ -101,6 +133,9 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr
// Try to figure out the name
const auto name = GetEntryNameByType(entry.id);
const auto filepath = extract_path / "sce_sys" / name;
std::filesystem::create_directories(filepath.parent_path());
if (name.empty()) {
// Just print with id
Common::FS::IOFile out(extract_path / "sce_sys" / std::to_string(entry.id),
@ -110,9 +145,6 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr
continue;
}
const auto filepath = extract_path / "sce_sys" / name;
std::filesystem::create_directories(filepath.parent_path());
if (entry.id == 0x1) { // DIGESTS, seek;
// file.Seek(entry.offset, fsSeekSet);
} else if (entry.id == 0x10) { // ENTRY_KEYS, seek;
@ -133,7 +165,6 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr
file.Read(imgkeydata);
// The Concatenated iv + dk3 imagekey for HASH256
std::array<CryptoPP::byte, 64> concatenated_ivkey_dk3;
std::memcpy(concatenated_ivkey_dk3.data(), &entry, sizeof(entry));
std::memcpy(concatenated_ivkey_dk3.data() + sizeof(entry), dk3_.data(), sizeof(dk3_));
@ -150,10 +181,33 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr
Common::FS::IOFile out(extract_path / "sce_sys" / name, Common::FS::FileAccessMode::Write);
out.WriteRaw<u8>(pkg.data() + entry.offset, entry.size);
out.Close();
// Decrypt Np stuff and overwrite.
if (entry.id == 0x400 || entry.id == 0x401 || entry.id == 0x402 ||
entry.id == 0x403) { // somehow 0x401 is not decrypting
decNp.resize(entry.size);
std::span<u8> cipherNp(pkg.data() + entry.offset, entry.size);
std::array<u8, 64> concatenated_ivkey_dk3_;
std::memcpy(concatenated_ivkey_dk3_.data(), &entry, sizeof(entry));
std::memcpy(concatenated_ivkey_dk3_.data() + sizeof(entry), dk3_.data(), sizeof(dk3_));
PKG::crypto.ivKeyHASH256(concatenated_ivkey_dk3_, ivKey);
PKG::crypto.aesCbcCfb128DecryptEntry(ivKey, cipherNp, decNp);
Common::FS::IOFile out(extract_path / "sce_sys" / name,
Common::FS::FileAccessMode::Write);
out.Write(decNp);
out.Close();
}
}
// Extract trophy files
if (!trp.Extract(extract_path)) {
// Do nothing some pkg come with no trp file.
// return false;
}
// Read the seed
std::array<CryptoPP::byte, 16> seed;
std::array<u8, 16> seed;
file.Seek(pkgheader.pfs_image_offset + 0x370);
file.Read(seed);
@ -165,7 +219,7 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr
std::vector<u8> pfs_encrypted(length);
file.Seek(pkgheader.pfs_image_offset);
file.Read(pfs_encrypted);
file.Close();
// Decrypt the pfs_image.
std::vector<u8> pfs_decrypted(length);
PKG::crypto.decryptPFS(dataKey, tweakKey, pfs_encrypted, pfs_decrypted, 0);

View File

@ -12,6 +12,7 @@
#include "common/endian.h"
#include "core/crypto/crypto.h"
#include "pfs.h"
#include "trp.h"
struct PKGHeader {
u32_be magic; // Magic
@ -103,11 +104,13 @@ public:
PKG();
~PKG();
bool Open(const std::string& filepath);
bool Open(const std::filesystem::path& filepath);
void ExtractFiles(const int& index);
bool Extract(const std::string& filepath, const std::filesystem::path& extract,
bool Extract(const std::filesystem::path& filepath, const std::filesystem::path& extract,
std::string& failreason);
std::vector<u8> sfo;
u32 GetNumberOfFiles() {
return fsTable.size();
}
@ -116,16 +119,42 @@ public:
return pkgSize;
}
std::string GetPkgFlags() {
return pkgFlags;
}
std::string_view GetTitleID() {
return std::string_view(pkgTitleID, 9);
}
PKGHeader GetPkgHeader() {
return pkgheader;
}
static bool isFlagSet(u32_be variable, PKGContentFlag flag) {
return (variable) & static_cast<u32>(flag);
}
static constexpr std::array<std::pair<PKGContentFlag, std::string_view>, 10> flagNames = {
{{PKGContentFlag::FIRST_PATCH, "FIRST_PATCH"},
{PKGContentFlag::PATCHGO, "PATCHGO"},
{PKGContentFlag::REMASTER, "REMASTER"},
{PKGContentFlag::PS_CLOUD, "PS_CLOUD"},
{PKGContentFlag::GD_AC, "GD_AC"},
{PKGContentFlag::NON_GAME, "NON_GAME"},
{PKGContentFlag::UNKNOWN_0x8000000, "UNKNOWN_0x8000000"},
{PKGContentFlag::SUBSEQUENT_PATCH, "SUBSEQUENT_PATCH"},
{PKGContentFlag::DELTA_PATCH, "DELTA_PATCH"},
{PKGContentFlag::CUMULATIVE_PATCH, "CUMULATIVE_PATCH"}}};
private:
Crypto crypto;
TRP trp;
std::vector<u8> pkg;
u64 pkgSize = 0;
char pkgTitleID[9];
PKGHeader pkgheader;
std::string pkgFlags;
std::unordered_map<int, std::filesystem::path> extractPaths;
std::vector<pfs_fs_table> fsTable;
@ -133,12 +162,13 @@ private:
std::vector<u64> sectorMap;
u64 pfsc_offset;
std::array<CryptoPP::byte, 32> dk3_;
std::array<CryptoPP::byte, 32> ivKey;
std::array<CryptoPP::byte, 256> imgKey;
std::array<CryptoPP::byte, 32> ekpfsKey;
std::array<CryptoPP::byte, 16> dataKey;
std::array<CryptoPP::byte, 16> tweakKey;
std::array<u8, 32> dk3_;
std::array<u8, 32> ivKey;
std::array<u8, 256> imgKey;
std::array<u8, 32> ekpfsKey;
std::array<u8, 16> dataKey;
std::array<u8, 16> tweakKey;
std::vector<u8> decNp;
std::filesystem::path pkgpath;
std::filesystem::path current_dir;

View File

@ -12,16 +12,22 @@ PSF::PSF() = default;
PSF::~PSF() = default;
bool PSF::open(const std::string& filepath) {
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
return false;
}
bool PSF::open(const std::string& filepath, std::vector<u8> psfBuffer) {
if (!psfBuffer.empty()) {
psf.resize(psfBuffer.size());
psf = psfBuffer;
} else {
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
return false;
}
const u64 psfSize = file.GetSize();
psf.resize(psfSize);
file.Seek(0);
file.Read(psf);
const u64 psfSize = file.GetSize();
psf.resize(psfSize);
file.Seek(0);
file.Read(psf);
file.Close();
}
// Parse file contents
PSFHeader header;

View File

@ -35,7 +35,7 @@ public:
PSF();
~PSF();
bool open(const std::string& filepath);
bool open(const std::string& filepath, std::vector<u8> psfBuffer);
std::string GetString(const std::string& key);
u32 GetInteger(const std::string& key);

View File

@ -0,0 +1,91 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "trp.h"
TRP::TRP() = default;
TRP::~TRP() = default;
void TRP::GetNPcommID(std::filesystem::path trophyPath, int index) {
std::filesystem::path trpPath = trophyPath / "sce_sys/npbind.dat";
Common::FS::IOFile npbindFile(trpPath, Common::FS::FileAccessMode::Read);
if (!npbindFile.IsOpen()) {
return;
}
npbindFile.Seek(0x84 + (index * 0x180));
npbindFile.ReadRaw<u8>(np_comm_id.data(), 12);
std::fill(np_comm_id.begin() + 12, np_comm_id.end(), 0); // fill with 0, we need 16 bytes.
}
static void removePadding(std::vector<u8>& vec) {
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
if (*it == '>') {
size_t pos = std::distance(vec.begin(), it.base());
vec.resize(pos);
break;
}
}
}
bool TRP::Extract(std::filesystem::path trophyPath) {
std::string title = trophyPath.filename().string();
std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/";
if (!std::filesystem::exists(gameSysDir)) {
return false;
}
for (int index = 0; const auto& it : std::filesystem::directory_iterator(gameSysDir)) {
if (it.is_regular_file()) {
GetNPcommID(trophyPath, index);
Common::FS::IOFile file(it.path(), Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
return false;
}
TrpHeader header;
file.Read(header);
if (header.magic != 0xDCA24D00)
return false;
s64 seekPos = sizeof(TrpHeader);
std::filesystem::path trpFilesPath(std::filesystem::current_path() / "game_data" /
title / "TrophyFiles" / it.path().stem());
std::filesystem::create_directories(trpFilesPath / "Icons");
std::filesystem::create_directory(trpFilesPath / "Xml");
for (int i = 0; i < header.entry_num; i++) {
file.Seek(seekPos);
seekPos += (s64)header.entry_size;
TrpEntry entry;
file.Read(entry);
std::string_view name(entry.entry_name);
if (entry.flag == 0 && name.find("TROP") != std::string::npos) { // PNG
file.Seek(entry.entry_pos);
std::vector<u8> icon(entry.entry_len);
file.Read(icon);
Common::FS::IOFile::WriteBytes(trpFilesPath / "Icons" / name, icon);
}
if (entry.flag == 3 && np_comm_id[0] == 'N' &&
np_comm_id[1] == 'P') { // ESFM, encrypted.
file.Seek(entry.entry_pos);
file.Read(esfmIv); // get iv key.
// Skip the first 16 bytes which are the iv key on every entry as we want a
// clean xml file.
std::vector<u8> ESFM(entry.entry_len - iv_len);
std::vector<u8> XML(entry.entry_len - iv_len);
file.Seek(entry.entry_pos + iv_len);
file.Read(ESFM);
crypto.decryptEFSM(np_comm_id, esfmIv, ESFM, XML); // decrypt
removePadding(XML);
std::string xml_name = entry.entry_name;
size_t pos = xml_name.find("ESFM");
if (pos != std::string::npos)
xml_name.replace(pos, xml_name.length(), "XML");
Common::FS::IOFile::WriteBytes(trpFilesPath / "Xml" / xml_name, XML);
}
}
}
index++;
}
return true;
}

View File

@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <vector>
#include "common/endian.h"
#include "common/io_file.h"
#include "common/types.h"
#include "core/crypto/crypto.h"
struct TrpHeader {
u32_be magic; // (0xDCA24D00)
u32_be version;
u64_be file_size; // size of full trp file
u32_be entry_num; // num entries
u32_be entry_size; // size of entry
u32_be dev_flag; // 1: dev
unsigned char digest[20]; // sha1 hash
u32_be key_index; // 3031300?
unsigned char padding[44];
};
struct TrpEntry {
char entry_name[32];
u64_be entry_pos;
u64_be entry_len;
u32_be flag; // 3 = CONFIG/ESFM , 0 = PNG
unsigned char padding[12];
};
class TRP {
public:
TRP();
~TRP();
bool Extract(std::filesystem::path trophyPath);
void GetNPcommID(std::filesystem::path trophyPath, int index);
private:
Crypto crypto;
std::vector<u8> NPcommID = std::vector<u8>(12);
std::array<u8, 16> np_comm_id{};
std::array<u8, 16> esfmIv{};
std::filesystem::path trpFilesPath;
static constexpr int iv_len = 16;
};

View File

@ -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;

View File

@ -14,5 +14,5 @@ enum class FileTypes {
Pkg,
};
FileTypes DetectFileType(const std::string& filepath);
FileTypes DetectFileType(const std::filesystem::path& filepath);
} // namespace Loader

View File

@ -65,7 +65,7 @@ void Emulator::Run(const std::filesystem::path& file) {
for (const auto& entry : std::filesystem::directory_iterator(sce_sys_folder)) {
if (entry.path().filename() == "param.sfo") {
auto* param_sfo = Common::Singleton<PSF>::Instance();
param_sfo->open(sce_sys_folder.string() + "/param.sfo");
param_sfo->open(sce_sys_folder.string() + "/param.sfo", {});
std::string id(param_sfo->GetString("CONTENT_ID"), 7, 9);
std::string title(param_sfo->GetString("TITLE"));
LOG_INFO(Loader, "Game id: {} Title: {}", id, title);

BIN
src/images/flag_china.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 B

BIN
src/images/flag_eu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 942 B

BIN
src/images/flag_jp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

BIN
src/images/flag_unk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

BIN
src/images/flag_us.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

BIN
src/images/flag_world.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

96
src/qt_gui/elf_viewer.cpp Normal file
View File

@ -0,0 +1,96 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QHeaderView>
#include "elf_viewer.h"
ElfViewer::ElfViewer(QWidget* parent) : QTableWidget(parent) {
dir_list_std = Config::getElfViewer();
for (const auto& str : dir_list_std) {
dir_list.append(QString::fromStdString(str));
}
CheckElfFolders();
this->setShowGrid(false);
this->setEditTriggers(QAbstractItemView::NoEditTriggers);
this->setSelectionBehavior(QAbstractItemView::SelectRows);
this->setSelectionMode(QAbstractItemView::SingleSelection);
this->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
this->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
this->verticalScrollBar()->installEventFilter(this);
this->verticalScrollBar()->setSingleStep(20);
this->horizontalScrollBar()->setSingleStep(20);
this->verticalHeader()->setVisible(false);
this->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
this->horizontalHeader()->setHighlightSections(false);
this->horizontalHeader()->setSortIndicatorShown(true);
this->horizontalHeader()->setStretchLastSection(true);
this->setContextMenuPolicy(Qt::CustomContextMenu);
this->setColumnCount(2);
this->setColumnWidth(0, 250);
this->setColumnWidth(1, 400);
this->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
this->setStyleSheet("QTableWidget { background-color: #D3D3D3; }");
OpenElfFiles();
QStringList headers;
headers << "Name"
<< "Path";
this->setHorizontalHeaderLabels(headers);
this->horizontalHeader()->setSortIndicatorShown(true);
this->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
}
void ElfViewer::OpenElfFolder() {
QString folderPath =
QFileDialog::getExistingDirectory(this, tr("Open Folder"), QDir::homePath());
if (!dir_list.contains(folderPath)) {
dir_list.append(folderPath);
QDir directory(folderPath);
QFileInfoList fileInfoList = directory.entryInfoList(QDir::Files);
for (const QFileInfo& fileInfo : fileInfoList) {
QString file_ext = fileInfo.suffix();
if (fileInfo.isFile() && (file_ext == "bin" || file_ext == "elf")) {
m_elf_list.append(fileInfo.absoluteFilePath());
}
}
std::ranges::sort(m_elf_list);
OpenElfFiles();
dir_list_std.clear();
for (auto dir : dir_list) {
dir_list_std.push_back(dir.toStdString());
}
Config::setElfViewer(dir_list_std);
} else {
// qDebug() << "Folder selection canceled.";
}
}
void ElfViewer::CheckElfFolders() {
m_elf_list.clear();
for (const QString& dir : dir_list) {
QDir directory(dir);
QFileInfoList fileInfoList = directory.entryInfoList(QDir::Files);
for (const QFileInfo& fileInfo : fileInfoList) {
QString file_ext = fileInfo.suffix();
if (fileInfo.isFile() && (file_ext == "bin" || file_ext == "elf")) {
m_elf_list.append(fileInfo.absoluteFilePath());
}
}
}
std::sort(m_elf_list.begin(), m_elf_list.end());
}
void ElfViewer::OpenElfFiles() {
this->clearContents();
this->setRowCount(m_elf_list.size());
for (int i = 0; auto elf : m_elf_list) {
QTableWidgetItem* item = new QTableWidgetItem();
QFileInfo fileInfo(m_elf_list[i]);
QString fileName = fileInfo.baseName();
SetTableItem(this, i, 0, fileName);
item = new QTableWidgetItem();
SetTableItem(this, i, 1, m_elf_list[i]);
i++;
}
this->resizeColumnsToContents();
}

61
src/qt_gui/elf_viewer.h Normal file
View File

@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <ranges>
#include <QApplication>
#include <QFileDialog>
#include <QMainWindow>
#include <QMenuBar>
#include <QScrollBar>
#include <QTableWidget>
#include <QTextEdit>
#include <QTreeView>
#include <QVBoxLayout>
#include <QWidget>
#include "game_list_frame.h"
#include "src/core/loader/elf.h"
class ElfViewer : public QTableWidget {
Q_OBJECT
public:
explicit ElfViewer(QWidget* parent = nullptr);
QStringList m_elf_list;
private:
void CheckElfFolders();
void OpenElfFiles();
Core::Loader::Elf m_elf_file;
QStringList dir_list;
QStringList elf_headers_list;
std::vector<std::string> dir_list_std;
void SetTableItem(QTableWidget* game_list, int row, int column, QString itemStr) {
QTableWidgetItem* item = new QTableWidgetItem();
QWidget* widget = new QWidget(this);
QVBoxLayout* layout = new QVBoxLayout(widget);
QLabel* label = new QLabel(itemStr, widget);
label->setStyleSheet("color: white; font-size: 15px; font-weight: bold;");
// Create shadow effect
QGraphicsDropShadowEffect* shadowEffect = new QGraphicsDropShadowEffect();
shadowEffect->setBlurRadius(5); // Set the blur radius of the shadow
shadowEffect->setColor(QColor(0, 0, 0, 160)); // Set the color and opacity of the shadow
shadowEffect->setOffset(2, 2); // Set the offset of the shadow
label->setGraphicsEffect(shadowEffect); // Apply shadow effect to the QLabel
layout->addWidget(label);
if (column != 8 && column != 1)
layout->setAlignment(Qt::AlignCenter);
widget->setLayout(layout);
game_list->setItem(row, column, item);
game_list->setCellWidget(row, column, widget);
}
public slots:
void OpenElfFolder();
};

View File

@ -3,12 +3,9 @@
#include "game_grid_frame.h"
GameGridFrame::GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get,
std::shared_ptr<GuiSettings> m_gui_settings, QWidget* parent)
: QTableWidget(parent) {
m_game_info = game_info_get;
m_gui_settings_ = m_gui_settings;
icon_size = m_gui_settings->GetValue(gui::m_icon_size_grid).toInt();
GameGridFrame::GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent)
: QTableWidget(parent), m_game_info(game_info_get) {
icon_size = Config::getIconSizeGrid();
windowWidth = parent->width();
this->setShowGrid(false);
this->setEditTriggers(QAbstractItemView::NoEditTriggers);
@ -33,6 +30,12 @@ GameGridFrame::GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get,
connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) {
m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, this, false);
});
connect(this, &QTableWidget::cellClicked, this, [&]() {
cellClicked = true;
crtRow = this->currentRow();
crtColumn = this->currentColumn();
columnCnt = this->columnCount();
});
}
void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool fromSearch) {
@ -43,9 +46,7 @@ void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool from
else
m_games_ = m_game_info->m_games;
m_games_shared = std::make_shared<QVector<GameInfo>>(m_games_);
icon_size = m_gui_settings_->GetValue(gui::m_icon_size_grid)
.toInt(); // update icon size for resize event.
icon_size = Config::getIconSizeGrid(); // update icon size for resize event.
int gamesPerRow = windowWidth / (icon_size + 20); // 2 x cell widget border size.
int row = 0;
@ -62,10 +63,10 @@ void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool from
QWidget* widget = new QWidget();
QVBoxLayout* layout = new QVBoxLayout();
QLabel* image_label = new QLabel();
QPixmap icon = m_games_[gameCounter].icon.scaled(
QImage icon = m_games_[gameCounter].icon.scaled(
QSize(icon_size, icon_size), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
image_label->setFixedSize(icon.width(), icon.height());
image_label->setPixmap(icon);
image_label->setPixmap(QPixmap::fromImage(icon));
QLabel* name_label = new QLabel(QString::fromStdString(m_games_[gameCounter].serial));
name_label->setAlignment(Qt::AlignHCenter);
layout->addWidget(image_label);
@ -86,8 +87,7 @@ void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool from
"color: #000000;"
"border: 1px solid #000000;"
"padding: 2px;"
"font-size: 12px; }")
.arg(tooltipText);
"font-size: 12px; }");
widget->setStyleSheet(tooltipStyle);
this->setCellWidget(row, column, widget);
@ -134,10 +134,12 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) {
}
void GameGridFrame::RefreshGridBackgroundImage() {
QPixmap blurredPixmap = QPixmap::fromImage(backgroundImage);
QPalette palette;
palette.setBrush(QPalette::Base, QBrush(blurredPixmap.scaled(size(), Qt::IgnoreAspectRatio)));
QColor transparentColor = QColor(135, 206, 235, 40);
palette.setColor(QPalette::Highlight, transparentColor);
this->setPalette(palette);
if (!backgroundImage.isNull()) {
QPalette palette;
palette.setBrush(QPalette::Base,
QBrush(backgroundImage.scaled(size(), Qt::IgnoreAspectRatio)));
QColor transparentColor = QColor(135, 206, 235, 40);
palette.setColor(QPalette::Highlight, transparentColor);
this->setPalette(palette);
}
}

View File

@ -14,6 +14,7 @@
#include <QVBoxLayout>
#include <QWidget>
#include <QtConcurrent/QtConcurrent>
#include "common/config.h"
#include "game_info.h"
#include "game_list_utils.h"
#include "gui_context_menus.h"
@ -33,14 +34,16 @@ private:
GameListUtils m_game_list_utils;
GuiContextMenus m_gui_context_menus;
std::shared_ptr<GameInfoClass> m_game_info;
std::shared_ptr<GuiSettings> m_gui_settings_;
std::shared_ptr<QVector<GameInfo>> m_games_shared;
public:
explicit GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get,
std::shared_ptr<GuiSettings> m_gui_settings, QWidget* parent = nullptr);
explicit GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent = nullptr);
void PopulateGameGrid(QVector<GameInfo> m_games, bool fromSearch);
bool cellClicked = false;
int icon_size;
int windowWidth;
int crtRow;
int crtColumn;
int columnCnt;
};

View File

@ -3,25 +3,43 @@
#include <future>
#include <thread>
#include <QProgressDialog>
#include <QtConcurrent/QtConcurrent>
#include "game_info.h"
void GameInfoClass::GetGameInfo() {
QString installDir = m_gui_settings->GetValue(gui::settings_install_dir).toString();
std::filesystem::path parent_folder(installDir.toStdString());
std::vector<std::string> filePaths;
for (const auto& dir : std::filesystem::directory_iterator(parent_folder)) {
if (dir.is_directory()) {
filePaths.push_back(dir.path().string());
GameInfoClass::GameInfoClass() = default;
GameInfoClass::~GameInfoClass() = default;
void GameInfoClass::GetGameInfo(QWidget* parent) {
QString installDir = QString::fromStdString(Config::getGameInstallDir());
QStringList filePaths;
QDir parentFolder(installDir);
QFileInfoList fileList = parentFolder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
for (const auto& fileInfo : fileList) {
if (fileInfo.isDir()) {
filePaths.append(fileInfo.absoluteFilePath());
}
}
std::vector<std::future<GameInfo>> futures;
m_games = QtConcurrent::mapped(filePaths, [&](const QString& path) {
return readGameInfo(path.toStdString());
}).results();
for (const auto& filePath : filePaths) {
futures.emplace_back(std::async(std::launch::async, readGameInfo, filePath));
}
// Progress bar, please be patient :)
QProgressDialog dialog("Loading game list, please wait :3", "Cancel", 0, 0, parent);
dialog.setWindowTitle("Loading...");
for (auto& future : futures) {
m_games.push_back(future.get());
}
std::sort(m_games.begin(), m_games.end(), CompareStrings);
QFutureWatcher<void> futureWatcher;
GameListUtils game_util;
bool finished = false;
futureWatcher.setFuture(QtConcurrent::map(m_games, game_util.GetFolderSize));
connect(&futureWatcher, &QFutureWatcher<void>::finished, [&]() {
dialog.reset();
std::sort(m_games.begin(), m_games.end(), CompareStrings);
});
connect(&dialog, &QProgressDialog::canceled, &futureWatcher, &QFutureWatcher<void>::cancel);
dialog.setRange(0, m_games.size());
connect(&futureWatcher, &QFutureWatcher<void>::progressValueChanged, &dialog,
&QProgressDialog::setValue);
dialog.exec();
}

View File

@ -3,34 +3,20 @@
#pragma once
#include <string>
#include <QFuture>
#include <QObject>
#include <QPixmap>
#include <QtConcurrent/QtConcurrent>
#include "common/config.h"
#include "core/file_format/psf.h"
#include "game_list_utils.h"
#include "gui_settings.h"
struct GameInfo {
std::string path; // root path of game directory (normaly directory that contains eboot.bin)
std::string icon_path; // path of icon0.png
std::string pic_path; // path of pic1.png
QPixmap icon;
std::string size;
// variables extracted from param.sfo
std::string name = "Unknown";
std::string serial = "Unknown";
std::string version = "Unknown";
std::string category = "Unknown";
std::string fw = "Unknown";
};
class GameInfoClass {
class GameInfoClass : public QObject {
Q_OBJECT
public:
void GetGameInfo();
std::shared_ptr<GuiSettings> m_gui_settings = std::make_shared<GuiSettings>();
GameInfoClass();
~GameInfoClass();
void GetGameInfo(QWidget* parent = nullptr);
QVector<GameInfo> m_games;
static bool CompareStrings(GameInfo& a, GameInfo& b) {
@ -39,19 +25,17 @@ public:
static GameInfo readGameInfo(const std::string& filePath) {
GameInfo game;
GameListUtils game_util;
game.size = game_util.GetFolderSize(QDir(QString::fromStdString(filePath))).toStdString();
game.path = filePath;
PSF psf;
if (psf.open(game.path + "/sce_sys/param.sfo")) {
QString iconpath(QString::fromStdString(game.path) + "/sce_sys/icon0.png");
QString picpath(QString::fromStdString(game.path) + "/sce_sys/pic1.png");
game.icon_path = iconpath.toStdString();
game.icon = QPixmap(iconpath);
game.pic_path = picpath.toStdString();
if (psf.open(game.path + "/sce_sys/param.sfo", {})) {
game.icon_path = game.path + "/sce_sys/icon0.png";
QString iconpath = QString::fromStdString(game.icon_path);
game.icon = QImage(iconpath);
game.pic_path = game.path + "/sce_sys/pic1.png";
game.name = psf.GetString("TITLE");
game.serial = psf.GetString("TITLE_ID");
game.region = GameListUtils::GetRegion(psf.GetString("CONTENT_ID").at(0)).toStdString();
u32 fw_int = psf.GetInteger("SYSTEM_VER");
QString fw = QString::number(fw_int, 16);
QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.')

View File

@ -13,10 +13,8 @@
#include <QMessageBox>
#include <QPushButton>
#include <QVBoxLayout>
#include "gui_settings.h"
GameInstallDialog::GameInstallDialog(std::shared_ptr<GuiSettings> gui_settings)
: m_gamesDirectory(nullptr), m_gui_settings(std::move(gui_settings)) {
GameInstallDialog::GameInstallDialog() : m_gamesDirectory(nullptr) {
auto layout = new QVBoxLayout(this);
layout->addWidget(SetupGamesDirectory());
@ -43,7 +41,7 @@ QWidget* GameInstallDialog::SetupGamesDirectory() {
// Input.
m_gamesDirectory = new QLineEdit();
m_gamesDirectory->setText(m_gui_settings->GetValue(gui::settings_install_dir).toString());
m_gamesDirectory->setText(QString::fromStdString(Config::getGameInstallDir()));
m_gamesDirectory->setMinimumWidth(400);
layout->addWidget(m_gamesDirectory);
@ -78,7 +76,8 @@ void GameInstallDialog::Save() {
return;
}
m_gui_settings->SetValue(gui::settings_install_dir, QDir::toNativeSeparators(gamesDirectory));
Config::setGameInstallDir(gamesDirectory.toStdString());
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::save(config_dir / "config.toml");
accept();
}

View File

@ -4,13 +4,14 @@
#pragma once
#include <QDialog>
#include "gui_settings.h"
#include "common/config.h"
#include "common/path_util.h"
class QLineEdit;
class GameInstallDialog final : public QDialog {
public:
GameInstallDialog(std::shared_ptr<GuiSettings> gui_settings);
GameInstallDialog();
~GameInstallDialog();
private slots:
@ -23,5 +24,4 @@ private:
private:
QLineEdit* m_gamesDirectory;
std::shared_ptr<GuiSettings> m_gui_settings;
};

View File

@ -1,14 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "game_list_frame.h"
GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get,
std::shared_ptr<GuiSettings> m_gui_settings, QWidget* parent)
: QTableWidget(parent) {
m_game_info = game_info_get;
icon_size = m_gui_settings->GetValue(gui::m_icon_size).toInt();
GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent)
: QTableWidget(parent), m_game_info(game_info_get) {
icon_size = Config::getIconSize();
this->setShowGrid(false);
this->setEditTriggers(QAbstractItemView::NoEditTriggers);
this->setSelectionBehavior(QAbstractItemView::SelectRows);
@ -25,17 +23,19 @@ GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get,
this->horizontalHeader()->setSortIndicatorShown(true);
this->horizontalHeader()->setStretchLastSection(true);
this->setContextMenuPolicy(Qt::CustomContextMenu);
this->setColumnCount(8);
this->setColumnCount(9);
this->setColumnWidth(1, 250);
this->setColumnWidth(2, 110);
this->setColumnWidth(3, 80);
this->setColumnWidth(4, 90);
this->setColumnWidth(5, 80);
this->setColumnWidth(6, 80);
this->setColumnWidth(7, 80);
QStringList headers;
headers << "Icon"
<< "Name"
<< "Serial"
<< "Region"
<< "Firmware"
<< "Size"
<< "Version"
@ -81,13 +81,14 @@ void GameListFrame::PopulateGameList() {
ResizeIcons(icon_size);
for (int i = 0; i < m_game_info->m_games.size(); i++) {
SetTableItem(this, i, 1, QString::fromStdString(m_game_info->m_games[i].name));
SetTableItem(this, i, 2, QString::fromStdString(m_game_info->m_games[i].serial));
SetTableItem(this, i, 3, QString::fromStdString(m_game_info->m_games[i].fw));
SetTableItem(this, i, 4, QString::fromStdString(m_game_info->m_games[i].size));
SetTableItem(this, i, 5, QString::fromStdString(m_game_info->m_games[i].version));
SetTableItem(this, i, 6, QString::fromStdString(m_game_info->m_games[i].category));
SetTableItem(this, i, 7, QString::fromStdString(m_game_info->m_games[i].path));
SetTableItem(i, 1, QString::fromStdString(m_game_info->m_games[i].name));
SetTableItem(i, 2, QString::fromStdString(m_game_info->m_games[i].serial));
SetRegionFlag(i, 3, QString::fromStdString(m_game_info->m_games[i].region));
SetTableItem(i, 4, QString::fromStdString(m_game_info->m_games[i].fw));
SetTableItem(i, 5, QString::fromStdString(m_game_info->m_games[i].size));
SetTableItem(i, 6, QString::fromStdString(m_game_info->m_games[i].version));
SetTableItem(i, 7, QString::fromStdString(m_game_info->m_games[i].category));
SetTableItem(i, 8, QString::fromStdString(m_game_info->m_games[i].path));
}
}
@ -119,12 +120,14 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) {
}
void GameListFrame::RefreshListBackgroundImage() {
QPixmap blurredPixmap = QPixmap::fromImage(backgroundImage);
QPalette palette;
palette.setBrush(QPalette::Base, QBrush(blurredPixmap.scaled(size(), Qt::IgnoreAspectRatio)));
QColor transparentColor = QColor(135, 206, 235, 40);
palette.setColor(QPalette::Highlight, transparentColor);
this->setPalette(palette);
if (!backgroundImage.isNull()) {
QPalette palette;
palette.setBrush(QPalette::Base,
QBrush(backgroundImage.scaled(size(), Qt::IgnoreAspectRatio)));
QColor transparentColor = QColor(135, 206, 235, 40);
palette.setColor(QPalette::Highlight, transparentColor);
this->setPalette(palette);
}
}
void GameListFrame::SortNameAscending(int columnIndex) {
@ -142,22 +145,66 @@ void GameListFrame::SortNameDescending(int columnIndex) {
}
void GameListFrame::ResizeIcons(int iconSize) {
QList<int> indices;
for (int i = 0; i < m_game_info->m_games.size(); i++) {
indices.append(i);
for (int index = 0; auto& game : m_game_info->m_games) {
QImage scaledPixmap = game.icon.scaled(QSize(iconSize, iconSize), Qt::KeepAspectRatio,
Qt::SmoothTransformation);
QTableWidgetItem* iconItem = new QTableWidgetItem();
this->verticalHeader()->resizeSection(index, scaledPixmap.height());
this->horizontalHeader()->resizeSection(0, scaledPixmap.width());
iconItem->setData(Qt::DecorationRole, scaledPixmap);
this->setItem(index, 0, iconItem);
index++;
}
std::future<void> future = std::async(std::launch::async, [=, this]() {
for (int index : indices) {
QPixmap scaledPixmap = m_game_info->m_games[index].icon.scaled(
QSize(iconSize, iconSize), Qt::KeepAspectRatio, Qt::SmoothTransformation);
QTableWidgetItem* iconItem = new QTableWidgetItem();
this->verticalHeader()->resizeSection(index, scaledPixmap.height());
this->horizontalHeader()->resizeSection(0, scaledPixmap.width());
iconItem->setData(Qt::DecorationRole, scaledPixmap);
this->setItem(index, 0, iconItem);
}
});
future.wait();
this->horizontalHeader()->setSectionResizeMode(7, QHeaderView::ResizeToContents);
}
void GameListFrame::SetTableItem(int row, int column, QString itemStr) {
QTableWidgetItem* item = new QTableWidgetItem();
QWidget* widget = new QWidget(this);
QVBoxLayout* layout = new QVBoxLayout(widget);
QLabel* label = new QLabel(itemStr, widget);
label->setStyleSheet("color: white; font-size: 15px; font-weight: bold;");
// Create shadow effect
QGraphicsDropShadowEffect* shadowEffect = new QGraphicsDropShadowEffect();
shadowEffect->setBlurRadius(5); // Set the blur radius of the shadow
shadowEffect->setColor(QColor(0, 0, 0, 160)); // Set the color and opacity of the shadow
shadowEffect->setOffset(2, 2); // Set the offset of the shadow
label->setGraphicsEffect(shadowEffect); // Apply shadow effect to the QLabel
layout->addWidget(label);
if (column != 8 && column != 1)
layout->setAlignment(Qt::AlignCenter);
widget->setLayout(layout);
this->setItem(row, column, item);
this->setCellWidget(row, column, widget);
}
void GameListFrame::SetRegionFlag(int row, int column, QString itemStr) {
QTableWidgetItem* item = new QTableWidgetItem();
QImage scaledPixmap;
if (itemStr == "Japan") {
scaledPixmap = QImage(":/images/flag_jp.png");
} else if (itemStr == "Europe") {
scaledPixmap = QImage(":/images/flag_eu.png");
} else if (itemStr == "USA") {
scaledPixmap = QImage(":/images/flag_us.png");
} else if (itemStr == "Asia") {
scaledPixmap = QImage(":/images/flag_china.png");
} else if (itemStr == "World") {
scaledPixmap = QImage(":/images/flag_world.png");
} else {
scaledPixmap = QImage(":/images/flag_unk.png");
}
QWidget* widget = new QWidget(this);
QVBoxLayout* layout = new QVBoxLayout(widget);
QLabel* label = new QLabel(widget);
label->setPixmap(QPixmap::fromImage(scaledPixmap));
layout->setAlignment(Qt::AlignCenter);
layout->addWidget(label);
widget->setLayout(layout);
this->setItem(row, column, item);
this->setCellWidget(row, column, widget);
}

View File

@ -23,8 +23,7 @@
class GameListFrame : public QTableWidget {
Q_OBJECT
public:
explicit GameListFrame(std::shared_ptr<GameInfoClass> game_info_get,
std::shared_ptr<GuiSettings> m_gui_settings, QWidget* parent = nullptr);
explicit GameListFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent = nullptr);
Q_SIGNALS:
void GameListFrameClosed();
@ -35,6 +34,8 @@ public Q_SLOTS:
void SortNameDescending(int columnIndex);
private:
void SetTableItem(int row, int column, QString itemStr);
void SetRegionFlag(int row, int column, QString itemStr);
QList<QAction*> m_columnActs;
GameInfoClass* game_inf_get = nullptr;
bool ListSortedAsc = true;
@ -50,30 +51,6 @@ public:
int icon_size;
void SetTableItem(QTableWidget* game_list, int row, int column, QString itemStr) {
QWidget* widget = new QWidget();
QVBoxLayout* layout = new QVBoxLayout();
QLabel* label = new QLabel(itemStr);
QTableWidgetItem* item = new QTableWidgetItem();
label->setStyleSheet("color: white; font-size: 15px; font-weight: bold;");
// Create shadow effect
QGraphicsDropShadowEffect* shadowEffect = new QGraphicsDropShadowEffect();
shadowEffect->setBlurRadius(5); // Set the blur radius of the shadow
shadowEffect->setColor(QColor(0, 0, 0, 160)); // Set the color and opacity of the shadow
shadowEffect->setOffset(2, 2); // Set the offset of the shadow
label->setGraphicsEffect(shadowEffect); // Apply shadow effect to the QLabel
layout->addWidget(label);
if (column != 7 && column != 1)
layout->setAlignment(Qt::AlignCenter);
widget->setLayout(layout);
game_list->setItem(row, column, item);
game_list->setCellWidget(row, column, widget);
}
static bool CompareStringsAscending(GameInfo a, GameInfo b, int columnIndex) {
if (columnIndex == 1) {
return a.name < b.name;

View File

@ -8,6 +8,21 @@
#include <QImage>
#include <QString>
struct GameInfo {
std::string path; // root path of game directory (normaly directory that contains eboot.bin)
std::string icon_path; // path of icon0.png
std::string pic_path; // path of pic1.png
QImage icon;
std::string size;
// variables extracted from param.sfo
std::string name = "Unknown";
std::string serial = "Unknown";
std::string version = "Unknown";
std::string region = "Unknown";
std::string category = "Unknown";
std::string fw = "Unknown";
};
class GameListUtils {
public:
static QString FormatSize(qint64 size) {
@ -33,25 +48,49 @@ public:
return sizeString + " " + suffixes[suffixIndex];
}
static QString GetFolderSize(const QDir& dir) {
static void GetFolderSize(GameInfo& game) {
QDir dir(QString::fromStdString(game.path));
QDirIterator it(dir.absolutePath(), QDirIterator::Subdirectories);
qint64 total = 0;
while (it.hasNext()) {
// check if entry is file
if (it.fileInfo().isFile()) {
total += it.fileInfo().size();
}
it.next(); // this is heavy.
}
// if there is a file left "at the end" get it's size
if (it.fileInfo().isFile()) {
it.next();
total += it.fileInfo().size();
}
game.size = FormatSize(total).toStdString();
}
return FormatSize(total);
static QString GetRegion(char region) {
switch (region) {
case 'U':
return "USA";
case 'E':
return "Europe";
case 'J':
return "Japan";
case 'H':
return "Asia";
case 'I':
return "World";
default:
return "Unknown";
}
}
static QString GetAppType(int type) {
switch (type) {
case 0:
return "Not Specified";
case 1:
return "FULL APP";
case 2:
return "UPGRADABLE";
case 3:
return "DEMO";
case 4:
return "FREEMIUM";
default:
return "Unknown";
}
}
QImage BlurImage(const QImage& image, const QRect& rect, int radius) {

View File

@ -10,8 +10,10 @@
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include "game_info.h"
#include "trophy_viewer.h"
class GuiContextMenus {
class GuiContextMenus : public QObject {
Q_OBJECT
public:
void RequestGameMenu(const QPoint& pos, QVector<GameInfo> m_games, QTableWidget* widget,
bool isList) {
@ -27,9 +29,11 @@ public:
QMenu menu(widget);
QAction openFolder("Open Game Folder", widget);
QAction openSfoViewer("SFO Viewer", widget);
QAction openTrophyViewer("Trophy Viewer", widget);
menu.addAction(&openFolder);
menu.addAction(&openSfoViewer);
menu.addAction(&openTrophyViewer);
// Show menu.
auto selected = menu.exec(global_pos);
if (!selected) {
@ -43,9 +47,13 @@ public:
if (selected == &openSfoViewer) {
PSF psf;
if (psf.open(m_games[itemID].path + "/sce_sys/param.sfo")) {
if (psf.open(m_games[itemID].path + "/sce_sys/param.sfo", {})) {
int rows = psf.map_strings.size() + psf.map_integers.size();
QTableWidget* tableWidget = new QTableWidget(rows, 2);
tableWidget->setAttribute(Qt::WA_DeleteOnClose);
connect(widget->parent(), &QWidget::destroyed, tableWidget,
[widget, tableWidget]() { tableWidget->deleteLater(); });
tableWidget->verticalHeader()->setVisible(false); // Hide vertical header
int row = 0;
@ -88,6 +96,15 @@ public:
tableWidget->show();
}
}
if (selected == &openTrophyViewer) {
QString trophyPath = QString::fromStdString(m_games[itemID].serial);
QString gameTrpPath = QString::fromStdString(m_games[itemID].path);
TrophyViewer* trophyViewer = new TrophyViewer(trophyPath, gameTrpPath);
trophyViewer->show();
connect(widget->parent(), &QWidget::destroyed, trophyViewer,
[widget, trophyViewer]() { trophyViewer->deleteLater(); });
}
}
int GetRowIndex(QTreeWidget* treeWidget, QTreeWidgetItem* item) {
@ -112,9 +129,9 @@ public:
return -1;
}
void RequestGameMenuPKGViewer(const QPoint& pos, QStringList m_pkg_app_list,
QTreeWidget* treeWidget,
std::function<void(std::string, int, int)> InstallDragDropPkg) {
void RequestGameMenuPKGViewer(
const QPoint& pos, QStringList m_pkg_app_list, QTreeWidget* treeWidget,
std::function<void(std::filesystem::path, int, int)> InstallDragDropPkg) {
QPoint global_pos = treeWidget->viewport()->mapToGlobal(pos); // context menu position
QTreeWidgetItem* currentItem = treeWidget->currentItem(); // current clicked item
int itemIndex = GetRowIndex(treeWidget, currentItem); // row
@ -131,15 +148,11 @@ public:
if (selected == &installPackage) {
QStringList pkg_app_ = m_pkg_app_list[itemIndex].split(";;");
std::string pkg_to_install = pkg_app_[9].toStdString();
InstallDragDropPkg(pkg_to_install, 1, 1);
QFile file("log.txt");
if (!file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text))
return;
QTextStream stream(&file);
stream << QString::fromStdString(pkg_to_install) << Qt::endl;
std::filesystem::path path(pkg_app_[9].toStdString());
#ifdef _WIN32
path = std::filesystem::path(pkg_app_[9].toStdWString());
#endif
InstallDragDropPkg(path, 1, 1);
}
}
};

View File

@ -1,29 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QString>
#include <QVariant>
struct GuiSave {
QString key;
QString name;
QVariant def;
GuiSave() {
key = "";
name = "";
def = QVariant();
}
GuiSave(const QString& k, const QString& n, const QVariant& d) {
key = k;
name = n;
def = d;
}
bool operator==(const GuiSave& rhs) const noexcept {
return key == rhs.key && name == rhs.name && def == rhs.def;
}
};

View File

@ -1,24 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "gui_settings.h"
GuiSettings::GuiSettings(QObject* parent) {
m_settings.reset(new QSettings("shadps4qt.ini", QSettings::Format::IniFormat,
parent)); // TODO make the path configurable
}
void GuiSettings::SetGamelistColVisibility(int col, bool val) const {
SetValue(GetGuiSaveForColumn(col), val);
}
bool GuiSettings::GetGamelistColVisibility(int col) const {
return GetValue(GetGuiSaveForColumn(col)).toBool();
}
GuiSave GuiSettings::GetGuiSaveForColumn(int col) {
return GuiSave{gui::game_list,
"visibility_" +
gui::get_game_list_column_name(static_cast<gui::game_list_columns>(col)),
true};
}

View File

@ -1,88 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QColor>
#include "settings.h"
namespace gui {
enum custom_roles {
game_role = Qt::UserRole + 1337,
};
enum game_list_columns {
column_icon,
column_name,
column_serial,
column_firmware,
column_size,
column_version,
column_category,
column_path,
column_count
};
inline QString get_game_list_column_name(game_list_columns col) {
switch (col) {
case column_icon:
return "column_icon";
case column_name:
return "column_name";
case column_serial:
return "column_serial";
case column_firmware:
return "column_firmware";
case column_size:
return "column_size";
case column_version:
return "column_version";
case column_category:
return "column_category";
case column_path:
return "column_path";
case column_count:
return "";
}
throw std::runtime_error("get_game_list_column_name: Invalid column");
}
const QString main_window = "main_window";
const QString game_list = "GameList";
const QString settings = "Settings";
const QString themes = "Themes";
const GuiSave main_window_gamelist_visible = GuiSave(main_window, "gamelistVisible", true);
const GuiSave main_window_geometry = GuiSave(main_window, "geometry", QByteArray());
const GuiSave main_window_windowState = GuiSave(main_window, "windowState", QByteArray());
const GuiSave main_window_mwState = GuiSave(main_window, "mwState", QByteArray());
const GuiSave game_list_state = GuiSave(game_list, "state", QByteArray());
const GuiSave game_list_listMode = GuiSave(game_list, "listMode", true);
const GuiSave settings_install_dir = GuiSave(settings, "installDirectory", "");
const GuiSave mw_themes = GuiSave(themes, "Themes", 0);
const GuiSave m_icon_size = GuiSave(game_list, "iconSize", 36);
const GuiSave m_icon_size_grid = GuiSave(game_list, "iconSizeGrid", 69);
const GuiSave m_slide_pos = GuiSave(game_list, "sliderPos", 0);
const GuiSave m_slide_pos_grid = GuiSave(game_list, "sliderPosGrid", 0);
const GuiSave m_table_mode = GuiSave(main_window, "tableMode", 0);
const GuiSave m_window_size = GuiSave(main_window, "windowSize", QSize(1280, 720));
const GuiSave m_pkg_viewer = GuiSave("pkg_viewer", "pkgDir", QStringList());
const GuiSave m_pkg_viewer_pkg_list = GuiSave("pkg_viewer", "pkgList", QStringList());
} // namespace gui
class GuiSettings : public Settings {
Q_OBJECT
public:
explicit GuiSettings(QObject* parent = nullptr);
bool GetGamelistColVisibility(int col) const;
public Q_SLOTS:
void SetGamelistColVisibility(int col, bool val) const;
static GuiSave GetGuiSaveForColumn(int col);
};

View File

@ -3,21 +3,33 @@
#include <QtWidgets/QApplication>
#include "common/config.h"
#include "core/file_sys/fs.h"
#include "qt_gui/game_install_dialog.h"
#include "qt_gui/gui_settings.h"
#include "qt_gui/main_window.h"
#include "src/sdl_window.h"
Frontend::WindowSDL* g_window;
void customMessageHandler(QtMsgType, const QMessageLogContext&, const QString&) {}
int main(int argc, char* argv[]) {
QApplication a(argc, argv);
auto m_gui_settings = std::make_shared<GuiSettings>();
if (m_gui_settings->GetValue(gui::settings_install_dir) == "") {
GameInstallDialog dlg(m_gui_settings);
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::load(config_dir / "config.toml");
QString gameDataPath = qApp->applicationDirPath() + "/game_data/";
std::string stdStr = gameDataPath.toStdString();
std::filesystem::path path(stdStr);
#ifdef _WIN64
std::wstring wstdStr = gameDataPath.toStdWString();
path = std::filesystem::path(wstdStr);
#endif
std::filesystem::create_directory(path);
if (Config::getGameInstallDir() == "") {
GameInstallDialog dlg;
dlg.exec();
}
MainWindow* m_main_window = new MainWindow(m_gui_settings, nullptr);
qInstallMessageHandler(customMessageHandler); // ignore qt logs.
MainWindow* m_main_window = new MainWindow(nullptr);
m_main_window->Init();
return a.exec();

View File

@ -13,38 +13,41 @@
#include "core/file_format/pkg.h"
#include "core/loader.h"
#include "game_install_dialog.h"
#include "gui_settings.h"
#include "main_window.h"
MainWindow::MainWindow(std::shared_ptr<GuiSettings> gui_settings, QWidget* parent)
: QMainWindow(parent), ui(new Ui::MainWindow), m_gui_settings(std::move(gui_settings)) {
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
}
MainWindow::~MainWindow() {
SaveWindowState();
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::save(config_dir / "config.toml");
}
bool MainWindow::Init() {
auto start = std::chrono::steady_clock::now();
// setup ui
AddUiWidgets();
CreateActions();
CreateRecentGameActions();
ConfigureGuiFromSettings();
CreateDockWindows();
CreateConnects();
SetLastUsedTheme();
SetLastIconSizeBullet();
ConfigureGuiFromSettings();
LoadGameLists();
// show ui
setMinimumSize(350, minimumSizeHint().height());
setWindowTitle(QString::fromStdString("shadPS4 v" + std::string(Common::VERSION)));
show();
this->show();
// load game list
LoadGameLists();
auto end = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
statusBar = new QStatusBar(this);
m_main_window->setStatusBar(statusBar);
statusBar.reset(new QStatusBar);
this->setStatusBar(statusBar.data());
// Update status bar
int numGames = m_game_info->m_games.size();
QString statusMessage = "Games: " + QString::number(numGames) + " (" +
@ -93,45 +96,59 @@ void MainWindow::AddUiWidgets() {
}
void MainWindow::CreateDockWindows() {
m_main_window = new QMainWindow();
m_main_window->setContextMenuPolicy(Qt::PreventContextMenu);
// place holder widget is needed for good health they say :)
QWidget* phCentralWidget = new QWidget(this);
setCentralWidget(phCentralWidget);
// resize window to last W and H
QSize window_size = m_gui_settings->GetValue(gui::m_window_size).toSize();
m_main_window->resize(window_size.width(), window_size.height());
// Add the game table.
m_dock_widget = new QDockWidget("Game List", m_main_window);
m_game_list_frame = new GameListFrame(m_game_info, m_gui_settings, m_main_window);
m_dock_widget.reset(new QDockWidget("Game List", this));
m_game_list_frame.reset(new GameListFrame(m_game_info, this));
m_game_list_frame->setObjectName("gamelist");
m_game_grid_frame = new GameGridFrame(m_game_info, m_gui_settings, m_main_window);
m_game_grid_frame.reset(new GameGridFrame(m_game_info, this));
m_game_grid_frame->setObjectName("gamegridlist");
m_elf_viewer.reset(new ElfViewer(this));
m_elf_viewer->setObjectName("elflist");
int table_mode = m_gui_settings->GetValue(gui::m_table_mode).toInt();
int table_mode = Config::getTableMode();
int slider_pos = 0;
if (table_mode == 0) { // List
m_game_grid_frame->hide();
m_dock_widget->setWidget(m_game_list_frame);
slider_pos = m_gui_settings->GetValue(gui::m_slide_pos).toInt();
m_elf_viewer->hide();
m_game_list_frame->show();
m_dock_widget->setWidget(m_game_list_frame.data());
slider_pos = Config::getSliderPositon();
ui->sizeSlider->setSliderPosition(slider_pos); // set slider pos at start;
isTableList = true;
} else { // Grid
} else if (table_mode == 1) { // Grid
m_game_list_frame->hide();
m_dock_widget->setWidget(m_game_grid_frame);
slider_pos = m_gui_settings->GetValue(gui::m_slide_pos_grid).toInt();
m_elf_viewer->hide();
m_game_grid_frame->show();
m_dock_widget->setWidget(m_game_grid_frame.data());
slider_pos = Config::getSliderPositonGrid();
ui->sizeSlider->setSliderPosition(slider_pos); // set slider pos at start;
isTableList = false;
} else {
m_game_list_frame->hide();
m_game_grid_frame->hide();
m_elf_viewer->show();
m_dock_widget->setWidget(m_elf_viewer.data());
isTableList = false;
}
m_main_window->addDockWidget(Qt::LeftDockWidgetArea, m_dock_widget);
m_main_window->setDockNestingEnabled(true);
m_dock_widget->setAllowedAreas(Qt::AllDockWidgetAreas);
m_dock_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_dock_widget->resize(this->width(), this->height());
addDockWidget(Qt::LeftDockWidgetArea, m_dock_widget.data());
this->setDockNestingEnabled(true);
setCentralWidget(m_main_window);
// handle resize like this for now, we deal with it when we add more docks
connect(this, &MainWindow::WindowResized, this, [&]() {
this->resizeDocks({m_dock_widget.data()}, {this->width()}, Qt::Orientation::Horizontal);
});
}
void MainWindow::LoadGameLists() {
// Get game info from game folders.
m_game_info->GetGameInfo();
m_game_info->GetGameInfo(this);
if (isTableList) {
m_game_list_frame->PopulateGameList();
} else {
@ -141,111 +158,150 @@ void MainWindow::LoadGameLists() {
void MainWindow::CreateConnects() {
connect(this, &MainWindow::WindowResized, this, &MainWindow::HandleResize);
connect(ui->mw_searchbar, &QLineEdit::textChanged, this, &MainWindow::SearchGameTable);
connect(ui->exitAct, &QAction::triggered, this, &QWidget::close);
connect(ui->refreshGameListAct, &QAction::triggered, this, &MainWindow::RefreshGameTable);
connect(this, &MainWindow::ExtractionFinished, this, &MainWindow::RefreshGameTable);
connect(ui->sizeSlider, &QSlider::valueChanged, this, [this](int value) {
if (isTableList) {
m_game_list_frame->icon_size =
36 + value; // 36 is the minimum icon size to use due to text disappearing.
m_game_list_frame->ResizeIcons(36 + value);
m_gui_settings->SetValue(gui::m_icon_size, 36 + value);
m_gui_settings->SetValue(gui::m_slide_pos, value);
Config::setIconSize(36 + value);
Config::setSliderPositon(value);
} else {
m_game_grid_frame->icon_size = 69 + value;
m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false);
m_gui_settings->SetValue(gui::m_icon_size_grid, 69 + value);
m_gui_settings->SetValue(gui::m_slide_pos_grid, value);
Config::setIconSizeGrid(69 + value);
Config::setSliderPositonGrid(value);
}
});
connect(ui->setIconSizeTinyAct, &QAction::triggered, this, [this](int value) {
connect(ui->playButton, &QPushButton::clicked, this, [this]() {
QString gamePath = "";
int table_mode = Config::getTableMode();
if (table_mode == 0) {
if (m_game_list_frame->currentItem()) {
int itemID = m_game_list_frame->currentItem()->row();
gamePath = QString::fromStdString(m_game_info->m_games[itemID].path + "/eboot.bin");
}
} else if (table_mode == 1) {
if (m_game_grid_frame->cellClicked) {
int itemID = (m_game_grid_frame->crtRow * m_game_grid_frame->columnCnt) +
m_game_grid_frame->crtColumn;
gamePath = QString::fromStdString(m_game_info->m_games[itemID].path + "/eboot.bin");
}
} else {
if (m_elf_viewer->currentItem()) {
int itemID = m_elf_viewer->currentItem()->row();
gamePath = QString::fromStdString(m_elf_viewer->m_elf_list[itemID].toStdString());
}
}
if (gamePath != "") {
AddRecentFiles(gamePath);
Core::Emulator emulator;
emulator.Run(gamePath.toUtf8().constData());
}
});
connect(ui->setIconSizeTinyAct, &QAction::triggered, this, [this]() {
if (isTableList) {
m_game_list_frame->icon_size =
36; // 36 is the minimum icon size to use due to text disappearing.
m_gui_settings->SetValue(gui::m_icon_size, 36);
ui->sizeSlider->setValue(0); // icone_size - 36
m_gui_settings->SetValue(gui::m_slide_pos, 0);
Config::setIconSize(36);
Config::setSliderPositon(0);
} else {
m_gui_settings->SetValue(gui::m_icon_size_grid, 69); // nice :3
ui->sizeSlider->setValue(0); // icone_size - 36
m_gui_settings->SetValue(gui::m_slide_pos_grid, 0);
ui->sizeSlider->setValue(0); // icone_size - 36
Config::setIconSizeGrid(69);
Config::setSliderPositonGrid(0);
}
});
connect(ui->setIconSizeSmallAct, &QAction::triggered, this, [this](int value) {
connect(ui->setIconSizeSmallAct, &QAction::triggered, this, [this]() {
if (isTableList) {
m_game_list_frame->icon_size = 64;
m_gui_settings->SetValue(gui::m_icon_size, 64);
ui->sizeSlider->setValue(28);
m_gui_settings->SetValue(gui::m_slide_pos, 28);
Config::setIconSize(64);
Config::setSliderPositon(28);
} else {
m_gui_settings->SetValue(gui::m_icon_size_grid, 97);
ui->sizeSlider->setValue(28);
m_gui_settings->SetValue(gui::m_slide_pos_grid, 28);
Config::setIconSizeGrid(97);
Config::setSliderPositonGrid(28);
}
});
connect(ui->setIconSizeMediumAct, &QAction::triggered, this, [this](int value) {
connect(ui->setIconSizeMediumAct, &QAction::triggered, this, [this]() {
if (isTableList) {
m_game_list_frame->icon_size = 128;
m_gui_settings->SetValue(gui::m_icon_size, 128);
ui->sizeSlider->setValue(92);
m_gui_settings->SetValue(gui::m_slide_pos, 92);
Config::setIconSize(128);
Config::setSliderPositon(92);
} else {
m_gui_settings->SetValue(gui::m_icon_size_grid, 160);
ui->sizeSlider->setValue(92);
m_gui_settings->SetValue(gui::m_slide_pos_grid, 92);
Config::setIconSizeGrid(160);
Config::setSliderPositonGrid(91);
}
});
connect(ui->setIconSizeLargeAct, &QAction::triggered, this, [this](int value) {
connect(ui->setIconSizeLargeAct, &QAction::triggered, this, [this]() {
if (isTableList) {
m_game_list_frame->icon_size = 256;
m_gui_settings->SetValue(gui::m_icon_size, 256);
ui->sizeSlider->setValue(220);
m_gui_settings->SetValue(gui::m_slide_pos, 220);
Config::setIconSize(256);
Config::setSliderPositon(220);
} else {
m_gui_settings->SetValue(gui::m_icon_size_grid, 256);
ui->sizeSlider->setValue(220);
m_gui_settings->SetValue(gui::m_slide_pos_grid, 220);
Config::setIconSizeGrid(256);
Config::setSliderPositonGrid(220);
}
});
connect(ui->setlistModeListAct, &QAction::triggered, m_dock_widget, [this]() {
m_dock_widget->setWidget(m_game_list_frame);
m_game_list_frame->show();
// List
connect(ui->setlistModeListAct, &QAction::triggered, m_dock_widget.data(), [this]() {
m_dock_widget->setWidget(m_game_list_frame.data());
m_game_grid_frame->hide();
m_elf_viewer->hide();
m_game_list_frame->show();
if (m_game_list_frame->item(0, 0) == nullptr) {
m_game_list_frame->clearContents();
m_game_list_frame->PopulateGameList();
}
isTableList = true;
m_gui_settings->SetValue(gui::m_table_mode, 0); // save table mode
int slider_pos = m_gui_settings->GetValue(gui::m_slide_pos).toInt();
Config::setTableMode(0);
int slider_pos = Config::getSliderPositon();
ui->sizeSlider->setEnabled(true);
ui->sizeSlider->setSliderPosition(slider_pos);
});
connect(ui->setlistModeGridAct, &QAction::triggered, m_dock_widget, [this]() {
m_dock_widget->setWidget(m_game_grid_frame);
// Grid
connect(ui->setlistModeGridAct, &QAction::triggered, m_dock_widget.data(), [this]() {
m_dock_widget->setWidget(m_game_grid_frame.data());
m_game_grid_frame->show();
m_game_list_frame->hide();
m_elf_viewer->hide();
if (m_game_grid_frame->item(0, 0) == nullptr) {
m_game_grid_frame->clearContents();
m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false);
}
isTableList = false;
m_gui_settings->SetValue(gui::m_table_mode, 1); // save table mode
int slider_pos_grid = m_gui_settings->GetValue(gui::m_slide_pos_grid).toInt();
Config::setTableMode(1);
int slider_pos_grid = Config::getSliderPositonGrid();
ui->sizeSlider->setEnabled(true);
ui->sizeSlider->setSliderPosition(slider_pos_grid);
});
// Elf
connect(ui->setlistElfAct, &QAction::triggered, m_dock_widget.data(), [this]() {
m_dock_widget->setWidget(m_elf_viewer.data());
m_game_grid_frame->hide();
m_game_list_frame->hide();
m_elf_viewer->show();
isTableList = false;
ui->sizeSlider->setDisabled(true);
Config::setTableMode(2);
});
// Dump game list.
connect(ui->dumpGameListAct, &QAction::triggered, this, [this] {
connect(ui->dumpGameListAct, &QAction::triggered, this, [&] {
QString filePath = qApp->applicationDirPath().append("/GameList.txt");
QFile file(filePath);
QTextStream out(&file);
@ -270,21 +326,26 @@ void MainWindow::CreateConnects() {
});
// Package install.
connect(ui->bootInstallPkgAct, &QAction::triggered, this, [this] { InstallPkg(); });
connect(ui->gameInstallPathAct, &QAction::triggered, this, [this] { InstallDirectory(); });
connect(ui->bootInstallPkgAct, &QAction::triggered, this, &MainWindow::InstallPkg);
connect(ui->gameInstallPathAct, &QAction::triggered, this, &MainWindow::InstallDirectory);
// elf viewer
connect(ui->addElfFolderAct, &QAction::triggered, m_elf_viewer.data(),
&ElfViewer::OpenElfFolder);
// Package Viewer.
connect(ui->pkgViewerAct, &QAction::triggered, this, [this]() {
PKGViewer* pkgViewer = new PKGViewer(m_game_info, m_gui_settings,
[this](std::string file, int pkgNum, int nPkg) {
this->InstallDragDropPkg(file, pkgNum, nPkg);
});
PKGViewer* pkgViewer = new PKGViewer(
m_game_info, this, [this](std::filesystem::path file, int pkgNum, int nPkg) {
this->InstallDragDropPkg(file, pkgNum, nPkg);
});
pkgViewer->show();
});
// Themes
connect(ui->setThemeLight, &QAction::triggered, &m_window_themes, [this]() {
m_window_themes.SetWindowTheme(Theme::Light, ui->mw_searchbar);
m_gui_settings->SetValue(gui::mw_themes, static_cast<int>(Theme::Light));
Config::setMainWindowTheme(static_cast<int>(Theme::Light));
if (!isIconBlack) {
SetUiIcons(true);
isIconBlack = true;
@ -292,7 +353,7 @@ void MainWindow::CreateConnects() {
});
connect(ui->setThemeDark, &QAction::triggered, &m_window_themes, [this]() {
m_window_themes.SetWindowTheme(Theme::Dark, ui->mw_searchbar);
m_gui_settings->SetValue(gui::mw_themes, static_cast<int>(Theme::Dark));
Config::setMainWindowTheme(static_cast<int>(Theme::Dark));
if (isIconBlack) {
SetUiIcons(false);
isIconBlack = false;
@ -300,7 +361,7 @@ void MainWindow::CreateConnects() {
});
connect(ui->setThemeGreen, &QAction::triggered, &m_window_themes, [this]() {
m_window_themes.SetWindowTheme(Theme::Green, ui->mw_searchbar);
m_gui_settings->SetValue(gui::mw_themes, static_cast<int>(Theme::Green));
Config::setMainWindowTheme(static_cast<int>(Theme::Green));
if (isIconBlack) {
SetUiIcons(false);
isIconBlack = false;
@ -308,7 +369,7 @@ void MainWindow::CreateConnects() {
});
connect(ui->setThemeBlue, &QAction::triggered, &m_window_themes, [this]() {
m_window_themes.SetWindowTheme(Theme::Blue, ui->mw_searchbar);
m_gui_settings->SetValue(gui::mw_themes, static_cast<int>(Theme::Blue));
Config::setMainWindowTheme(static_cast<int>(Theme::Blue));
if (isIconBlack) {
SetUiIcons(false);
isIconBlack = false;
@ -316,7 +377,7 @@ void MainWindow::CreateConnects() {
});
connect(ui->setThemeViolet, &QAction::triggered, &m_window_themes, [this]() {
m_window_themes.SetWindowTheme(Theme::Violet, ui->mw_searchbar);
m_gui_settings->SetValue(gui::mw_themes, static_cast<int>(Theme::Violet));
Config::setMainWindowTheme(static_cast<int>(Theme::Violet));
if (isIconBlack) {
SetUiIcons(false);
isIconBlack = false;
@ -345,8 +406,8 @@ void MainWindow::SearchGameTable(const QString& text) {
}
void MainWindow::RefreshGameTable() {
m_game_info->m_games.clear();
m_game_info->GetGameInfo();
// m_game_info->m_games.clear();
m_game_info->GetGameInfo(this);
m_game_list_frame->clearContents();
m_game_list_frame->PopulateGameList();
m_game_grid_frame->clearContents();
@ -358,16 +419,10 @@ void MainWindow::RefreshGameTable() {
}
void MainWindow::ConfigureGuiFromSettings() {
// Restore GUI state if needed. We need to if they exist.
if (!restoreGeometry(m_gui_settings->GetValue(gui::main_window_geometry).toByteArray())) {
resize(QGuiApplication::primaryScreen()->availableSize() * 0.7);
}
m_main_window->restoreState(m_gui_settings->GetValue(gui::main_window_mwState).toByteArray());
ui->showGameListAct->setChecked(
m_gui_settings->GetValue(gui::main_window_gamelist_visible).toBool());
setGeometry(Config::getMainWindowGeometryX(), Config::getMainWindowGeometryY(),
Config::getMainWindowGeometryW(), Config::getMainWindowGeometryH());
ui->showGameListAct->setChecked(true);
if (isTableList) {
ui->setlistModeListAct->setChecked(true);
} else {
@ -376,87 +431,155 @@ void MainWindow::ConfigureGuiFromSettings() {
}
void MainWindow::SaveWindowState() const {
// Save gui settings
m_gui_settings->SetValue(gui::main_window_geometry, saveGeometry());
m_gui_settings->SetValue(gui::main_window_windowState, saveState());
m_gui_settings->SetValue(gui::m_window_size,
QSize(m_main_window->width(), m_main_window->height()));
m_gui_settings->SetValue(gui::main_window_mwState, m_main_window->saveState());
Config::setMainWindowWidth(this->width());
Config::setMainWindowHeight(this->height());
Config::setMainWindowGeometry(this->geometry().x(), this->geometry().y(),
this->geometry().width(), this->geometry().height());
}
void MainWindow::InstallPkg() {
QStringList fileNames = QFileDialog::getOpenFileNames(
this, tr("Install PKG Files"), QDir::currentPath(), tr("PKG File (*.PKG)"));
int nPkg = fileNames.size();
int pkgNum = 0;
for (const QString& file : fileNames) {
pkgNum++;
MainWindow::InstallDragDropPkg(file.toStdString(), pkgNum, nPkg);
QFileDialog dialog;
dialog.setFileMode(QFileDialog::ExistingFiles);
dialog.setNameFilter(tr("PKG File (*.PKG)"));
if (dialog.exec()) {
QStringList fileNames = dialog.selectedFiles();
int nPkg = fileNames.size();
int pkgNum = 0;
for (const QString& file : fileNames) {
++pkgNum;
std::filesystem::path path(file.toStdString());
#ifdef _WIN64
path = std::filesystem::path(file.toStdWString());
#endif
MainWindow::InstallDragDropPkg(path, pkgNum, nPkg);
}
}
}
void MainWindow::InstallDragDropPkg(std::string file, int pkgNum, int nPkg) {
void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int nPkg) {
if (Loader::DetectFileType(file) == Loader::FileTypes::Pkg) {
PKG pkg;
pkg = PKG();
pkg.Open(file);
std::string failreason;
const auto extract_path =
std::filesystem::path(
m_gui_settings->GetValue(gui::settings_install_dir).toString().toStdString()) /
pkg.GetTitleID();
std::filesystem::path(Config::getGameInstallDir()) / pkg.GetTitleID();
QString pkgType = QString::fromStdString(pkg.GetPkgFlags());
QDir game_dir(QString::fromStdString(extract_path.string()));
if (game_dir.exists()) {
QMessageBox msgBox;
msgBox.setWindowTitle("PKG Extraction");
if (pkgType.contains("PATCH")) {
psf.open("", pkg.sfo);
QString pkg_app_version = QString::fromStdString(psf.GetString("APP_VER"));
psf.open(extract_path.string() + "/sce_sys/param.sfo", {});
QString game_app_version = QString::fromStdString(psf.GetString("APP_VER"));
double appD = game_app_version.toDouble();
double pkgD = pkg_app_version.toDouble();
if (pkgD == appD) {
msgBox.setText(
QString("Patch detected!\nPKG and Game versions match!: %1\nWould you like "
"to overwrite?")
.arg(pkg_app_version));
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::No);
} else if (pkgD < appD) {
QMessageBox::information(this, "PKG Extraction",
QString("Patch detected!\nPKG Version %1 is older "
"than installed version!: %2\nWould you like "
"to overwrite?")
.arg(pkg_app_version, game_app_version));
return;
} else {
msgBox.setText(QString("Patch detected!\nGame is installed: %1\nWould you like "
"to install Patch: %2?")
.arg(game_app_version, pkg_app_version));
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::No);
}
int result = msgBox.exec();
if (result == QMessageBox::Yes) {
// Do nothing.
} else {
return;
}
} else {
msgBox.setText(QString("Game already installed\n%1\nWould you like to overwrite?")
.arg(QString::fromStdString(extract_path.string())));
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::No);
int result = msgBox.exec();
if (result == QMessageBox::Yes) {
// Do nothing.
} else {
return;
}
}
} else {
// Do nothing;
if (pkgType.contains("PATCH")) {
QMessageBox::information(this, "PKG Extraction",
"PKG is a patch, please install the game first!");
return;
}
// what else?
}
if (!pkg.Extract(file, extract_path, failreason)) {
QMessageBox::critical(this, "PKG ERROR", QString::fromStdString(failreason),
QMessageBox::Ok);
QMessageBox::critical(this, "PKG ERROR", QString::fromStdString(failreason));
} else {
int nfiles = pkg.GetNumberOfFiles();
QList<int> indices;
QVector<int> indices;
for (int i = 0; i < nfiles; i++) {
indices.append(i);
}
QProgressDialog dialog;
dialog.setWindowTitle("PKG Extraction");
dialog.setWindowModality(Qt::WindowModal);
QString extractmsg = QString("Extracting PKG %1/%2").arg(pkgNum).arg(nPkg);
dialog.setLabelText(extractmsg);
dialog.setAutoClose(true);
dialog.setRange(0, nfiles);
// Create a QFutureWatcher and connect signals and slots.
QFutureWatcher<void> futureWatcher;
QObject::connect(&futureWatcher, SIGNAL(finished()), &dialog, SLOT(reset()));
QObject::connect(&dialog, SIGNAL(canceled()), &futureWatcher, SLOT(cancel()));
QObject::connect(&futureWatcher, SIGNAL(progressRangeChanged(int, int)), &dialog,
SLOT(setRange(int, int)));
QObject::connect(&futureWatcher, SIGNAL(progressValueChanged(int)), &dialog,
SLOT(setValue(int)));
futureWatcher.setFuture(QtConcurrent::map(
indices, std::bind(&PKG::ExtractFiles, pkg, std::placeholders::_1)));
// Display the dialog and start the event loop.
connect(&futureWatcher, &QFutureWatcher<void>::finished, this, [=, this]() {
if (pkgNum == nPkg) {
QString path = QString::fromStdString(Config::getGameInstallDir());
QMessageBox extractMsgBox(this);
extractMsgBox.setWindowTitle("Extraction Finished");
extractMsgBox.setText(QString("Game successfully installed at %1").arg(path));
extractMsgBox.addButton(QMessageBox::Ok);
extractMsgBox.setDefaultButton(QMessageBox::Ok);
connect(&extractMsgBox, &QMessageBox::buttonClicked, this,
[&](QAbstractButton* button) {
if (extractMsgBox.button(QMessageBox::Ok) == button) {
extractMsgBox.close();
emit ExtractionFinished();
}
});
extractMsgBox.exec();
}
});
connect(&dialog, &QProgressDialog::canceled, [&]() { futureWatcher.cancel(); });
connect(&futureWatcher, &QFutureWatcher<void>::progressValueChanged, &dialog,
&QProgressDialog::setValue);
futureWatcher.setFuture(
QtConcurrent::map(indices, [&](int index) { pkg.ExtractFiles(index); }));
dialog.exec();
futureWatcher.waitForFinished();
auto path = m_gui_settings->GetValue(gui::settings_install_dir).toString();
if (pkgNum == nPkg) {
QMessageBox::information(this, "Extraction Finished",
"Game successfully installed at " + path, QMessageBox::Ok);
// Refresh game table after extraction.
RefreshGameTable();
}
}
} else {
QMessageBox::critical(this, "PKG ERROR", "File doesn't appear to be a valid PKG file",
QMessageBox::Ok);
QMessageBox::critical(this, "PKG ERROR", "File doesn't appear to be a valid PKG file");
}
}
void MainWindow::InstallDirectory() {
GameInstallDialog dlg(m_gui_settings);
GameInstallDialog dlg;
dlg.exec();
}
void MainWindow::SetLastUsedTheme() {
Theme lastTheme = static_cast<Theme>(m_gui_settings->GetValue(gui::mw_themes).toInt());
Theme lastTheme = static_cast<Theme>(Config::getMainWindowTheme());
m_window_themes.SetWindowTheme(lastTheme, ui->mw_searchbar);
switch (lastTheme) {
@ -489,7 +612,7 @@ void MainWindow::SetLastUsedTheme() {
void MainWindow::SetLastIconSizeBullet() {
// set QAction bullet point if applicable
int lastSize = m_gui_settings->GetValue(gui::m_icon_size).toInt();
int lastSize = Config::getIconSize();
switch (lastSize) {
case 36:
ui->setIconSizeTinyAct->setChecked(true);
@ -507,47 +630,30 @@ void MainWindow::SetLastIconSizeBullet() {
}
QIcon MainWindow::RecolorIcon(const QIcon& icon, bool isWhite) {
QPixmap pixmap(icon.pixmap(icon.actualSize(QSize(120, 120)), QIcon::Normal));
QPixmap pixmap(icon.pixmap(icon.actualSize(QSize(120, 120))));
QColor clr(isWhite ? Qt::white : Qt::black);
QBitmap mask = pixmap.createMaskFromColor(clr, Qt::MaskOutColor);
pixmap.fill(QColor(isWhite ? Qt::black : Qt::white));
pixmap.setMask(mask);
QIcon newIcon(pixmap);
return newIcon;
return QIcon(pixmap);
}
void MainWindow::SetUiIcons(bool isWhite) {
QIcon icon;
icon = RecolorIcon(ui->bootInstallPkgAct->icon(), isWhite);
ui->bootInstallPkgAct->setIcon(icon);
icon = RecolorIcon(ui->exitAct->icon(), isWhite);
ui->exitAct->setIcon(icon);
icon = RecolorIcon(ui->setlistModeListAct->icon(), isWhite);
ui->setlistModeListAct->setIcon(icon);
icon = RecolorIcon(ui->setlistModeGridAct->icon(), isWhite);
ui->setlistModeGridAct->setIcon(icon);
icon = RecolorIcon(ui->gameInstallPathAct->icon(), isWhite);
ui->gameInstallPathAct->setIcon(icon);
icon = RecolorIcon(ui->menuThemes->icon(), isWhite);
ui->menuThemes->setIcon(icon);
icon = RecolorIcon(ui->menuGame_List_Icons->icon(), isWhite);
ui->menuGame_List_Icons->setIcon(icon);
icon = RecolorIcon(ui->playButton->icon(), isWhite);
ui->playButton->setIcon(icon);
icon = RecolorIcon(ui->pauseButton->icon(), isWhite);
ui->pauseButton->setIcon(icon);
icon = RecolorIcon(ui->stopButton->icon(), isWhite);
ui->stopButton->setIcon(icon);
icon = RecolorIcon(ui->settingsButton->icon(), isWhite);
ui->settingsButton->setIcon(icon);
icon = RecolorIcon(ui->controllerButton->icon(), isWhite);
ui->controllerButton->setIcon(icon);
icon = RecolorIcon(ui->refreshGameListAct->icon(), isWhite);
ui->refreshGameListAct->setIcon(icon);
icon = RecolorIcon(ui->menuGame_List_Mode->icon(), isWhite);
ui->menuGame_List_Mode->setIcon(icon);
icon = RecolorIcon(ui->pkgViewerAct->icon(), isWhite);
ui->pkgViewerAct->setIcon(icon);
ui->bootInstallPkgAct->setIcon(RecolorIcon(ui->bootInstallPkgAct->icon(), isWhite));
ui->exitAct->setIcon(RecolorIcon(ui->exitAct->icon(), isWhite));
ui->setlistModeListAct->setIcon(RecolorIcon(ui->setlistModeListAct->icon(), isWhite));
ui->setlistModeGridAct->setIcon(RecolorIcon(ui->setlistModeGridAct->icon(), isWhite));
ui->gameInstallPathAct->setIcon(RecolorIcon(ui->gameInstallPathAct->icon(), isWhite));
ui->menuThemes->setIcon(RecolorIcon(ui->menuThemes->icon(), isWhite));
ui->menuGame_List_Icons->setIcon(RecolorIcon(ui->menuGame_List_Icons->icon(), isWhite));
ui->playButton->setIcon(RecolorIcon(ui->playButton->icon(), isWhite));
ui->pauseButton->setIcon(RecolorIcon(ui->pauseButton->icon(), isWhite));
ui->stopButton->setIcon(RecolorIcon(ui->stopButton->icon(), isWhite));
ui->settingsButton->setIcon(RecolorIcon(ui->settingsButton->icon(), isWhite));
ui->controllerButton->setIcon(RecolorIcon(ui->controllerButton->icon(), isWhite));
ui->refreshGameListAct->setIcon(RecolorIcon(ui->refreshGameListAct->icon(), isWhite));
ui->menuGame_List_Mode->setIcon(RecolorIcon(ui->menuGame_List_Mode->icon(), isWhite));
ui->pkgViewerAct->setIcon(RecolorIcon(ui->pkgViewerAct->icon(), isWhite));
}
void MainWindow::resizeEvent(QResizeEvent* event) {
@ -564,3 +670,43 @@ void MainWindow::HandleResize(QResizeEvent* event) {
m_game_grid_frame->RefreshGridBackgroundImage();
}
}
void MainWindow::AddRecentFiles(QString filePath) {
std::vector<std::string> vec = Config::getRecentFiles();
if (!vec.empty()) {
if (filePath.toStdString() == vec.at(0)) {
return;
}
auto it = std::find(vec.begin(), vec.end(), filePath.toStdString());
if (it != vec.end()) {
vec.erase(it);
}
}
vec.insert(vec.begin(), filePath.toStdString());
if (vec.size() > 6) {
vec.pop_back();
}
Config::setRecentFiles(vec);
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::save(config_dir / "config.toml");
CreateRecentGameActions(); // Refresh the QActions.
}
void MainWindow::CreateRecentGameActions() {
m_recent_files_group = new QActionGroup(this);
ui->menuRecent->clear();
std::vector<std::string> vec = Config::getRecentFiles();
for (int i = 0; i < vec.size(); i++) {
QAction* recentFileAct = new QAction(this);
recentFileAct->setText(QString::fromStdString(vec.at(i)));
ui->menuRecent->addAction(recentFileAct);
m_recent_files_group->addAction(recentFileAct);
}
connect(m_recent_files_group, &QActionGroup::triggered, this, [this](QAction* action) {
QString gamePath = action->text();
AddRecentFiles(gamePath); // Update the list.
Core::Emulator emulator;
emulator.Run(gamePath.toUtf8().constData());
});
}

View File

@ -3,10 +3,19 @@
#pragma once
#include <QAbstractButton>
#include <QActionGroup>
#include <QDragEnterEvent>
#include <QMainWindow>
#include <QMimeData>
#include <QScopedPointer>
#include <emulator.h>
#include <fmt/core.h>
#include "common/config.h"
#include "common/path_util.h"
#include "core/file_format/psf.h"
#include "core/file_sys/fs.h"
#include "elf_viewer.h"
#include "game_grid_frame.h"
#include "game_info.h"
#include "game_list_frame.h"
@ -15,26 +24,19 @@
#include "main_window_ui.h"
#include "pkg_viewer.h"
class GuiSettings;
class GameListFrame;
class MainWindow : public QMainWindow {
Q_OBJECT
std::unique_ptr<Ui_MainWindow> ui;
bool m_is_list_mode = true;
bool m_save_slider_pos = false;
int m_other_slider_pos = 0;
signals:
void WindowResized(QResizeEvent* event);
void ExtractionFinished();
public:
explicit MainWindow(std::shared_ptr<GuiSettings> gui_settings, QWidget* parent = nullptr);
explicit MainWindow(QWidget* parent = nullptr);
~MainWindow();
bool Init();
void InstallPkg();
void InstallDragDropPkg(std::string file, int pkgNum, int nPkg);
void InstallDragDropPkg(std::filesystem::path file, int pkgNum, int nPkg);
void InstallDirectory();
private Q_SLOTS:
@ -45,38 +47,40 @@ private Q_SLOTS:
void HandleResize(QResizeEvent* event);
private:
Ui_MainWindow* ui;
void AddUiWidgets();
void CreateActions();
void CreateRecentGameActions();
void CreateDockWindows();
void LoadGameLists();
void CreateConnects();
void SetLastUsedTheme();
void SetLastIconSizeBullet();
void SetUiIcons(bool isWhite);
void InstallPkg();
void AddRecentFiles(QString filePath);
QIcon RecolorIcon(const QIcon& icon, bool isWhite);
bool isIconBlack = false;
bool isTableList = true;
QActionGroup* m_icon_size_act_group = nullptr;
QActionGroup* m_list_mode_act_group = nullptr;
QActionGroup* m_theme_act_group = nullptr;
QActionGroup* m_recent_files_group = nullptr;
PKG pkg;
// Dockable widget frames
QMainWindow* m_main_window = nullptr;
WindowThemes m_window_themes;
GameListUtils m_game_list_utils;
QDockWidget* m_dock_widget = nullptr;
QScopedPointer<QDockWidget> m_dock_widget;
// Game Lists
GameListFrame* m_game_list_frame = nullptr;
GameGridFrame* m_game_grid_frame = nullptr;
// Packge Viewer
PKGViewer* m_pkg_viewer = nullptr;
QScopedPointer<GameListFrame> m_game_list_frame;
QScopedPointer<GameGridFrame> m_game_grid_frame;
QScopedPointer<ElfViewer> m_elf_viewer;
// Status Bar.
QStatusBar* statusBar = nullptr;
QScopedPointer<QStatusBar> statusBar;
PSF psf;
std::shared_ptr<GameInfoClass> m_game_info = std::make_shared<GameInfoClass>();
std::shared_ptr<GuiSettings> m_gui_settings;
protected:
void dragEnterEvent(QDragEnterEvent* event1) override {
@ -93,7 +97,11 @@ protected:
int nPkg = urlList.size();
for (const QUrl& url : urlList) {
pkgNum++;
InstallDragDropPkg(url.toLocalFile().toStdString(), pkgNum, nPkg);
std::filesystem::path path(url.toLocalFile().toStdString());
#ifdef _WIN64
path = std::filesystem::path(url.toLocalFile().toStdWString());
#endif
InstallDragDropPkg(path, pkgNum, nPkg);
}
}
}

View File

@ -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(

View File

@ -5,14 +5,16 @@
#include <QWidget>
#include "pkg_viewer.h"
PKGViewer::PKGViewer(std::shared_ptr<GameInfoClass> game_info_get,
std::shared_ptr<GuiSettings> m_gui_settings,
std::function<void(std::string, int, int)> InstallDragDropPkg)
: QMainWindow() {
PKGViewer::PKGViewer(std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent,
std::function<void(std::filesystem::path, int, int)> InstallDragDropPkg)
: QMainWindow(), m_game_info(game_info_get) {
this->resize(1280, 720);
m_gui_settings_ = m_gui_settings;
m_game_info = game_info_get;
dir_list = m_gui_settings->GetValue(gui::m_pkg_viewer).toStringList();
this->setAttribute(Qt::WA_DeleteOnClose);
dir_list_std = Config::getPkgViewer();
dir_list.clear();
for (const auto& str : dir_list_std) {
dir_list.append(QString::fromStdString(str));
}
statusBar = new QStatusBar(treeWidget);
this->setStatusBar(statusBar);
treeWidget = new QTreeWidget(this);
@ -20,8 +22,8 @@ PKGViewer::PKGViewer(std::shared_ptr<GameInfoClass> game_info_get,
QStringList headers;
headers << "Name"
<< "Serial"
<< "Size"
<< "Installed"
<< "Size"
<< "Category"
<< "Type"
<< "App Ver"
@ -50,6 +52,8 @@ PKGViewer::PKGViewer(std::shared_ptr<GameInfoClass> game_info_get,
m_gui_context_menus.RequestGameMenuPKGViewer(pos, m_full_pkg_list, treeWidget,
InstallDragDropPkg);
});
connect(parent, &QWidget::destroyed, this, [parent, this]() { this->deleteLater(); });
}
PKGViewer::~PKGViewer() {}
@ -59,18 +63,21 @@ void PKGViewer::OpenPKGFolder() {
QFileDialog::getExistingDirectory(this, tr("Open Folder"), QDir::homePath());
if (!dir_list.contains(folderPath)) {
dir_list.append(folderPath);
if (!folderPath.isEmpty()) {
for (const auto& dir : std::filesystem::directory_iterator(folderPath.toStdString())) {
QString file_ext =
QString::fromStdString(dir.path().extension().string()).toLower();
if (std::filesystem::is_regular_file(dir.path()) && file_ext == ".pkg") {
m_pkg_list.append(QString::fromStdString(dir.path().string()));
}
QDir directory(folderPath);
QFileInfoList fileInfoList = directory.entryInfoList(QDir::Files);
for (const QFileInfo& fileInfo : fileInfoList) {
QString file_ext = fileInfo.suffix();
if (fileInfo.isFile() && file_ext == "pkg") {
m_pkg_list.append(fileInfo.absoluteFilePath());
}
std::sort(m_pkg_list.begin(), m_pkg_list.end());
ProcessPKGInfo();
m_gui_settings_->SetValue(gui::m_pkg_viewer, dir_list);
}
std::sort(m_pkg_list.begin(), m_pkg_list.end());
ProcessPKGInfo();
dir_list_std.clear();
for (auto dir : dir_list) {
dir_list_std.push_back(dir.toStdString());
}
Config::setPkgViewer(dir_list_std);
} else {
// qDebug() << "Folder selection canceled.";
}
@ -78,11 +85,13 @@ void PKGViewer::OpenPKGFolder() {
void PKGViewer::CheckPKGFolders() { // Check for new PKG file additions.
m_pkg_list.clear();
for (const QString& paths : dir_list) {
for (const auto& dir : std::filesystem::directory_iterator(paths.toStdString())) {
QString file_ext = QString::fromStdString(dir.path().extension().string()).toLower();
if (std::filesystem::is_regular_file(dir.path()) && file_ext == ".pkg") {
m_pkg_list.append(QString::fromStdString(dir.path().string()));
for (const QString& dir : dir_list) {
QDir directory(dir);
QFileInfoList fileInfoList = directory.entryInfoList(QDir::Files);
for (const QFileInfo& fileInfo : fileInfoList) {
QString file_ext = fileInfo.suffix();
if (fileInfo.isFile() && file_ext == "pkg") {
m_pkg_list.append(fileInfo.absoluteFilePath());
}
}
}
@ -97,87 +106,46 @@ void PKGViewer::ProcessPKGInfo() {
m_pkg_patch_list.clear();
m_full_pkg_list.clear();
for (int i = 0; i < m_pkg_list.size(); i++) {
Common::FS::IOFile file(m_pkg_list[i].toStdString(), Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) {
// return false;
}
file.ReadRaw<u8>(&pkgheader, sizeof(PKGHeader));
file.Seek(0);
pkgSize = file.GetSize();
pkg.resize(pkgheader.pkg_promote_size);
file.Read(pkg);
u32 offset = pkgheader.pkg_table_entry_offset;
u32 n_files = pkgheader.pkg_table_entry_count;
for (int i = 0; i < n_files; i++) {
std::memcpy(&entry, &pkg[offset + i * 0x20], sizeof(entry));
const auto name = GetEntryNameByType(entry.id);
if (name == "param.sfo") {
psf.resize(entry.size);
int seek = entry.offset;
file.Seek(seek);
file.Read(psf);
std::memcpy(&header, psf.data(), sizeof(header));
auto future = std::async(std::launch::async, [&]() {
for (u32 i = 0; i < header.index_table_entries; i++) {
PSFEntry psfentry;
std::memcpy(&psfentry, &psf[sizeof(PSFHeader) + i * sizeof(PSFEntry)],
sizeof(psfentry));
const std::string key =
(char*)&psf[header.key_table_offset + psfentry.key_offset];
if (psfentry.param_fmt == PSFEntry::Fmt::TextRaw ||
psfentry.param_fmt == PSFEntry::Fmt::TextNormal) {
map_strings[key] =
(char*)&psf[header.data_table_offset + psfentry.data_offset];
}
if (psfentry.param_fmt == PSFEntry::Fmt::Integer) {
u32 value;
std::memcpy(&value,
&psf[header.data_table_offset + psfentry.data_offset],
sizeof(value));
map_integers[key] = value;
}
}
});
future.wait();
}
}
QString title_name = GetString("TITLE");
QString title_id = GetString("TITLE_ID");
QString app_type = GetAppType(GetInteger("APP_TYPE"));
QString app_version = GetString("APP_VER");
QString title_category = GetString("CATEGORY");
QString pkg_size = game_list_util.FormatSize(pkgheader.pkg_size);
pkg_content_flag = pkgheader.pkg_content_flags;
std::filesystem::path path(m_pkg_list[i].toStdString());
#ifdef _WIN32
path = std::filesystem::path(m_pkg_list[i].toStdWString());
#endif
package.Open(path);
psf.open("", package.sfo);
QString title_name = QString::fromStdString(psf.GetString("TITLE"));
QString title_id = QString::fromStdString(psf.GetString("TITLE_ID"));
QString app_type = game_list_util.GetAppType(psf.GetInteger("APP_TYPE"));
QString app_version = QString::fromStdString(psf.GetString("APP_VER"));
QString title_category = QString::fromStdString(psf.GetString("CATEGORY"));
QString pkg_size = game_list_util.FormatSize(package.GetPkgHeader().pkg_size);
pkg_content_flag = package.GetPkgHeader().pkg_content_flags;
QString flagss = "";
for (const auto& flag : flagNames) {
if (isFlagSet(pkg_content_flag, flag.first)) {
for (const auto& flag : package.flagNames) {
if (package.isFlagSet(pkg_content_flag, flag.first)) {
if (!flagss.isEmpty())
flagss.append(", ");
flagss.append(QString::fromStdString(flag.second));
flagss += (", ");
flagss += QString::fromStdString(flag.second.data());
}
}
u32 fw_int = GetInteger("SYSTEM_VER");
u32 fw_int = psf.GetInteger("SYSTEM_VER");
QString fw = QString::number(fw_int, 16);
QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.')
: fw.left(3).insert(1, '.');
fw_ = (fw_int == 0) ? "0.00" : fw_;
char region = pkgheader.pkg_content_id[0];
char region = package.GetPkgHeader().pkg_content_id[0];
QString pkg_info = "";
if (title_category == "gd") {
if (title_category == "gd" && !flagss.contains("PATCH")) {
title_category = "App";
pkg_info = title_name + ";;" + title_id + ";;" + pkg_size + ";;" + title_category +
";;" + app_type + ";;" + app_version + ";;" + fw_ + ";;" +
GetRegion(region) + ";;" + flagss + ";;" + m_pkg_list[i];
game_list_util.GetRegion(region) + ";;" + flagss + ";;" + m_pkg_list[i];
m_pkg_app_list.append(pkg_info);
} else {
title_category = "Patch";
pkg_info = title_name + ";;" + title_id + ";;" + pkg_size + ";;" + title_category +
";;" + app_type + ";;" + app_version + ";;" + fw_ + ";;" +
GetRegion(region) + ";;" + flagss + ";;" + m_pkg_list[i];
game_list_util.GetRegion(region) + ";;" + flagss + ";;" + m_pkg_list[i];
m_pkg_patch_list.append(pkg_info);
}
}
@ -204,7 +172,7 @@ void PKGViewer::ProcessPKGInfo() {
treeItem->setText(9, pkg_app_[8]);
treeItem->setText(10, pkg_app_[9]);
for (const GameInfo& info : m_game_info->m_games) { // Check if game is installed.
if (info.name == pkg_app_[0].toStdString()) {
if (info.serial == pkg_app_[1].toStdString()) {
treeItem->setText(2, QChar(0x2713));
treeItem->setTextAlignment(2, Qt::AlignCenter);
}
@ -233,7 +201,6 @@ void PKGViewer::ProcessPKGInfo() {
}
}
}
std::sort(m_full_pkg_list.begin(), m_full_pkg_list.end());
for (int column = 0; column < treeWidget->columnCount() - 2; ++column) {
// Resize the column to fit its contents
@ -245,17 +212,3 @@ void PKGViewer::ProcessPKGInfo() {
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;
}

View File

@ -22,33 +22,29 @@
#include "game_info.h"
#include "game_list_utils.h"
#include "gui_context_menus.h"
#include "gui_settings.h"
class PKGViewer : public QMainWindow {
Q_OBJECT
public:
explicit PKGViewer(std::shared_ptr<GameInfoClass> game_info_get,
std::shared_ptr<GuiSettings> m_gui_settings,
std::function<void(std::string, int, int)> InstallDragDropPkg = nullptr);
explicit PKGViewer(
std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent,
std::function<void(std::filesystem::path, int, int)> InstallDragDropPkg = nullptr);
~PKGViewer();
void OpenPKGFolder();
void CheckPKGFolders();
void ProcessPKGInfo();
QString GetString(const std::string& key);
u32 GetInteger(const std::string& key);
private:
GuiContextMenus m_gui_context_menus;
PSF psf_;
PKG package;
PSF psf;
PKGHeader pkgheader;
PKGEntry entry;
PSFHeader header;
PSFEntry psfentry;
char pkgTitleID[9];
std::vector<u8> pkg;
std::vector<u8> psf;
u64 pkgSize = 0;
std::shared_ptr<GuiSettings> m_gui_settings_;
std::unordered_map<std::string, std::string> map_strings;
std::unordered_map<std::string, u32> map_integers;
@ -58,18 +54,6 @@ private:
// Status bar
QStatusBar* statusBar;
std::vector<std::pair<PKGContentFlag, std::string>> flagNames = {
{PKGContentFlag::FIRST_PATCH, "FIRST_PATCH"},
{PKGContentFlag::PATCHGO, "PATCHGO"},
{PKGContentFlag::REMASTER, "REMASTER"},
{PKGContentFlag::PS_CLOUD, "PS_CLOUD"},
{PKGContentFlag::GD_AC, "GD_AC"},
{PKGContentFlag::NON_GAME, "NON_GAME"},
{PKGContentFlag::UNKNOWN_0x8000000, "UNKNOWN_0x8000000"},
{PKGContentFlag::SUBSEQUENT_PATCH, "SUBSEQUENT_PATCH"},
{PKGContentFlag::DELTA_PATCH, "DELTA_PATCH"},
{PKGContentFlag::CUMULATIVE_PATCH, "CUMULATIVE_PATCH"}};
std::vector<std::pair<int, QString>> appTypes = {
{0, "FULL APP"},
{1, "UPGRADABLE"},
@ -77,47 +61,11 @@ private:
{3, "FREEMIUM"},
};
bool isFlagSet(u32_be variable, PKGContentFlag flag) {
return (variable) & static_cast<u32>(flag);
}
QString GetRegion(char region) {
switch (region) {
case 'U':
return "USA";
case 'E':
return "Europe";
case 'J':
return "Japan";
case 'H':
return "Asia";
case 'I':
return "World";
default:
return "Unknown";
}
}
QString GetAppType(int region) {
switch (region) {
case 0:
return "Not Specified";
case 1:
return "FULL APP";
case 2:
return "UPGRADABLE";
case 3:
return "DEMO";
case 4:
return "FREEMIUM";
default:
return "Unknown";
}
}
QStringList m_full_pkg_list;
QStringList m_pkg_app_list;
QStringList m_pkg_patch_list;
QStringList m_pkg_list;
QStringList dir_list;
std::vector<std::string> dir_list_std;
QTreeWidget* treeWidget = nullptr;
};

View File

@ -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();
}
}

View File

@ -1,50 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <QDir>
#include <QSettings>
#include <QSize>
#include <QVariant>
#include "gui_save.h"
typedef QPair<QString, QString> q_string_pair;
typedef QPair<QString, QSize> q_size_pair;
typedef QList<q_string_pair> q_pair_list;
typedef QList<q_size_pair> q_size_list;
// Parent Class for GUI settings
class Settings : public QObject {
Q_OBJECT
public:
explicit Settings(QObject* parent = nullptr);
~Settings();
QString GetSettingsDir() const;
QVariant GetValue(const QString& key, const QString& name, const QVariant& def) const;
QVariant GetValue(const GuiSave& entry) const;
static QVariant List2Var(const q_pair_list& list);
static q_pair_list Var2List(const QVariant& var);
public Q_SLOTS:
/** Remove entry */
void RemoveValue(const QString& key, const QString& name) const;
void RemoveValue(const GuiSave& entry) const;
/** Write value to entry */
void SetValue(const GuiSave& entry, const QVariant& value) const;
void SetValue(const QString& key, const QVariant& value) const;
void SetValue(const QString& key, const QString& name, const QVariant& value) const;
protected:
static QString ComputeSettingsDir();
std::unique_ptr<QSettings> m_settings;
QDir m_settings_dir;
};

View File

@ -0,0 +1,145 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "trophy_viewer.h"
TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath) : QMainWindow() {
this->setWindowTitle("Trophy Viewer");
this->setAttribute(Qt::WA_DeleteOnClose);
tabWidget = new QTabWidget(this);
gameTrpPath_ = gameTrpPath;
headers << "Trophy"
<< "Name"
<< "Description"
<< "ID"
<< "Hidden"
<< "Type"
<< "PID";
PopulateTrophyWidget(trophyPath);
}
void TrophyViewer::PopulateTrophyWidget(QString title) {
QString trophyDir = qApp->applicationDirPath() + "/game_data/" + title + "/TrophyFiles";
QDir dir(trophyDir);
if (!dir.exists()) {
std::filesystem::path path(gameTrpPath_.toStdString());
#ifdef _WIN64
path = std::filesystem::path(gameTrpPath_.toStdWString());
#endif
if (!trp.Extract(path))
return;
}
QFileInfoList dirList = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
if (dirList.isEmpty())
return;
for (const QFileInfo& dirInfo : dirList) {
QString tabName = dirInfo.fileName();
QString trpDir = trophyDir + "/" + tabName;
QString iconsPath = trpDir + "/Icons";
QDir iconsDir(iconsPath);
QFileInfoList iconDirList = iconsDir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
std::vector<QImage> icons;
for (const QFileInfo& iconInfo : iconDirList) {
QImage icon =
QImage(iconInfo.absoluteFilePath())
.scaled(QSize(128, 128), Qt::KeepAspectRatio, Qt::SmoothTransformation);
icons.push_back(icon);
}
QStringList trpId;
QStringList trpHidden;
QStringList trpType;
QStringList trpPid;
QStringList trophyNames;
QStringList trophyDetails;
QString xmlPath = trpDir + "/Xml/TROP.XML";
QFile file(xmlPath);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
return;
}
QXmlStreamReader reader(&file);
while (!reader.atEnd() && !reader.hasError()) {
reader.readNext();
if (reader.isStartElement() && reader.name().toString() == "trophy") {
trpId.append(reader.attributes().value("id").toString());
trpHidden.append(reader.attributes().value("hidden").toString());
trpType.append(reader.attributes().value("ttype").toString());
trpPid.append(reader.attributes().value("pid").toString());
}
if (reader.name().toString() == "name" && !trpId.isEmpty()) {
trophyNames.append(reader.readElementText());
}
if (reader.name().toString() == "detail" && !trpId.isEmpty()) {
trophyDetails.append(reader.readElementText());
}
}
QTableWidget* tableWidget = new QTableWidget(this);
tableWidget->setShowGrid(false);
tableWidget->setColumnCount(7);
tableWidget->setHorizontalHeaderLabels(headers);
tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
tableWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
tableWidget->horizontalHeader()->setStretchLastSection(true);
tableWidget->verticalHeader()->setVisible(false);
tableWidget->setRowCount(icons.size());
for (int row = 0; auto& icon : icons) {
QTableWidgetItem* item = new QTableWidgetItem();
item->setData(Qt::DecorationRole, icon);
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
tableWidget->setItem(row, 0, item);
if (!trophyNames.isEmpty() && !trophyDetails.isEmpty()) {
SetTableItem(tableWidget, row, 1, trophyNames[row]);
SetTableItem(tableWidget, row, 2, trophyDetails[row]);
SetTableItem(tableWidget, row, 3, trpId[row]);
SetTableItem(tableWidget, row, 4, trpHidden[row]);
SetTableItem(tableWidget, row, 5, GetTrpType(trpType[row].at(0)));
SetTableItem(tableWidget, row, 6, trpPid[row]);
}
tableWidget->verticalHeader()->resizeSection(row, icon.height());
row++;
}
tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
int width = 16;
for (int i = 0; i < 7; i++) {
width += tableWidget->horizontalHeader()->sectionSize(i);
}
tableWidget->resize(width, 720);
tabWidget->addTab(tableWidget,
tabName.insert(6, " ").replace(0, 1, tabName.at(0).toUpper()));
this->resize(width + 20, 720);
}
this->setCentralWidget(tabWidget);
}
void TrophyViewer::SetTableItem(QTableWidget* parent, int row, int column, QString str) {
QWidget* widget = new QWidget();
QVBoxLayout* layout = new QVBoxLayout();
QLabel* label = new QLabel(str);
QTableWidgetItem* item = new QTableWidgetItem();
label->setWordWrap(true);
label->setStyleSheet("color: white; font-size: 15px; font-weight: bold;");
// Create shadow effect
QGraphicsDropShadowEffect* shadowEffect = new QGraphicsDropShadowEffect();
shadowEffect->setBlurRadius(5); // Set the blur radius of the shadow
shadowEffect->setColor(QColor(0, 0, 0, 160)); // Set the color and opacity of the shadow
shadowEffect->setOffset(2, 2); // Set the offset of the shadow
label->setGraphicsEffect(shadowEffect); // Apply shadow effect to the QLabel
layout->addWidget(label);
if (column != 1 && column != 2)
layout->setAlignment(Qt::AlignCenter);
widget->setLayout(layout);
parent->setItem(row, column, item);
parent->setCellWidget(row, column, widget);
}

View File

@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QApplication>
#include <QDir>
#include <QFileInfoList>
#include <QGraphicsBlurEffect>
#include <QHeaderView>
#include <QLabel>
#include <QMainWindow>
#include <QStyleOptionViewItem>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QVBoxLayout>
#include <QWidget>
#include <QXmlStreamReader>
#include "common/types.h"
#include "core/file_format/trp.h"
class TrophyViewer : public QMainWindow {
Q_OBJECT
public:
explicit TrophyViewer(QString trophyPath, QString gameTrpPath);
private:
void PopulateTrophyWidget(QString title);
void SetTableItem(QTableWidget* parent, int row, int column, QString str);
QTabWidget* tabWidget = nullptr;
QStringList headers;
QString gameTrpPath_;
TRP trp;
QString GetTrpType(const QChar trp_) {
switch (trp_.toLatin1()) {
case 'B':
return "Bronze";
case 'S':
return "Silver";
case 'G':
return "Gold";
case 'P':
return "Platinum";
}
return "Unknown";
}
};

View File

@ -15,5 +15,11 @@
<file>images/controller_icon.png</file>
<file>images/refresh_icon.png</file>
<file>images/list_mode_icon.png</file>
<file>images/flag_jp.png</file>
<file>images/flag_eu.png</file>
<file>images/flag_unk.png</file>
<file>images/flag_us.png</file>
<file>images/flag_world.png</file>
<file>images/flag_china.png</file>
</qresource>
</RCC>