- 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:
parent
71dda8c776
commit
0f27e0edf2
|
@ -6,10 +6,10 @@ Files: CMakeSettings.json
|
||||||
scripts/ps4_names.txt
|
scripts/ps4_names.txt
|
||||||
documents/changelog.txt
|
documents/changelog.txt
|
||||||
documents/readme.txt
|
documents/readme.txt
|
||||||
|
documents/Screenshots/screenshot.png
|
||||||
.github/shadps4.desktop
|
.github/shadps4.desktop
|
||||||
.github/shadps4.png
|
.github/shadps4.png
|
||||||
.gitmodules
|
.gitmodules
|
||||||
documents/Screenshots/screenshot.png
|
|
||||||
src/images/shadps4.ico
|
src/images/shadps4.ico
|
||||||
src/images/controller_icon.png
|
src/images/controller_icon.png
|
||||||
src/images/exit_icon.png
|
src/images/exit_icon.png
|
||||||
|
@ -25,6 +25,12 @@ Files: CMakeSettings.json
|
||||||
src/images/settings_icon.png
|
src/images/settings_icon.png
|
||||||
src/images/stop_icon.png
|
src/images/stop_icon.png
|
||||||
src/images/themes_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.rc
|
||||||
src/shadps4.qrc
|
src/shadps4.qrc
|
||||||
externals/stb_image.h
|
externals/stb_image.h
|
||||||
|
|
|
@ -266,6 +266,8 @@ set(CORE src/core/aerolib/stubs.cpp
|
||||||
src/core/file_format/pkg_type.h
|
src/core/file_format/pkg_type.h
|
||||||
src/core/file_format/psf.cpp
|
src/core/file_format/psf.cpp
|
||||||
src/core/file_format/psf.h
|
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.h
|
||||||
src/core/file_format/splash.cpp
|
src/core/file_format/splash.cpp
|
||||||
src/core/file_sys/fs.cpp
|
src/core/file_sys/fs.cpp
|
||||||
|
@ -433,6 +435,12 @@ set(INPUT src/input/controller.cpp
|
||||||
src/input/controller.h
|
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)
|
# the above is shared in sdl and qt version (TODO share them all)
|
||||||
|
|
||||||
if(ENABLE_QT_GUI)
|
if(ENABLE_QT_GUI)
|
||||||
|
@ -442,9 +450,6 @@ set(QT_GUI
|
||||||
src/qt_gui/main_window_ui.h
|
src/qt_gui/main_window_ui.h
|
||||||
src/qt_gui/main_window.cpp
|
src/qt_gui/main_window.cpp
|
||||||
src/qt_gui/main_window.h
|
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/gui_context_menus.h
|
||||||
src/qt_gui/game_list_utils.h
|
src/qt_gui/game_list_utils.h
|
||||||
src/qt_gui/game_info.cpp
|
src/qt_gui/game_info.cpp
|
||||||
|
@ -457,11 +462,14 @@ set(QT_GUI
|
||||||
src/qt_gui/game_install_dialog.h
|
src/qt_gui/game_install_dialog.h
|
||||||
src/qt_gui/pkg_viewer.cpp
|
src/qt_gui/pkg_viewer.cpp
|
||||||
src/qt_gui/pkg_viewer.h
|
src/qt_gui/pkg_viewer.h
|
||||||
src/qt_gui/settings.cpp
|
src/qt_gui/trophy_viewer.cpp
|
||||||
src/qt_gui/settings.h
|
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.cpp
|
||||||
src/qt_gui/main_window_themes.h
|
src/qt_gui/main_window_themes.h
|
||||||
src/qt_gui/main.cpp
|
src/qt_gui/main.cpp
|
||||||
|
${EMULATOR}
|
||||||
${RESOURCE_FILES}
|
${RESOURCE_FILES}
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
@ -475,8 +483,7 @@ if (ENABLE_QT_GUI)
|
||||||
${CORE}
|
${CORE}
|
||||||
${SHADER_RECOMPILER}
|
${SHADER_RECOMPILER}
|
||||||
${VIDEO_CORE}
|
${VIDEO_CORE}
|
||||||
src/sdl_window.h
|
${EMULATOR}
|
||||||
src/sdl_window.cpp
|
|
||||||
)
|
)
|
||||||
else()
|
else()
|
||||||
add_executable(shadps4
|
add_executable(shadps4
|
||||||
|
@ -486,11 +493,8 @@ else()
|
||||||
${CORE}
|
${CORE}
|
||||||
${SHADER_RECOMPILER}
|
${SHADER_RECOMPILER}
|
||||||
${VIDEO_CORE}
|
${VIDEO_CORE}
|
||||||
|
${EMULATOR}
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/emulator.cpp
|
|
||||||
src/emulator.h
|
|
||||||
src/sdl_window.h
|
|
||||||
src/sdl_window.cpp
|
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
@ -548,7 +552,7 @@ target_include_directories(shadps4 PRIVATE ${HOST_SHADERS_INCLUDE})
|
||||||
|
|
||||||
if (ENABLE_QT_GUI)
|
if (ENABLE_QT_GUI)
|
||||||
set_target_properties(shadps4 PROPERTIES
|
set_target_properties(shadps4 PROPERTIES
|
||||||
WIN32_EXECUTABLE ON
|
# WIN32_EXECUTABLE ON
|
||||||
MACOSX_BUNDLE ON)
|
MACOSX_BUNDLE ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,23 @@ static bool shouldDumpShaders = false;
|
||||||
static bool shouldDumpPM4 = false;
|
static bool shouldDumpPM4 = false;
|
||||||
static bool vkValidation = false;
|
static bool vkValidation = false;
|
||||||
static bool vkValidationSync = 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() {
|
bool isLleLibc() {
|
||||||
return isLibc;
|
return isLibc;
|
||||||
|
@ -85,6 +102,101 @@ bool vkValidationSyncEnabled() {
|
||||||
return vkValidationSync;
|
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) {
|
void load(const std::filesystem::path& path) {
|
||||||
// If the configuration file does not exist, create it and return
|
// If the configuration file does not exist, create it and return
|
||||||
std::error_code error;
|
std::error_code error;
|
||||||
|
@ -152,6 +264,29 @@ void load(const std::filesystem::path& path) {
|
||||||
isLibc = toml::find_or<toml::boolean>(lle, "libc", true);
|
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) {
|
void save(const std::filesystem::path& path) {
|
||||||
toml::basic_value<toml::preserve_comments> data;
|
toml::basic_value<toml::preserve_comments> data;
|
||||||
|
@ -187,6 +322,22 @@ void save(const std::filesystem::path& path) {
|
||||||
data["Vulkan"]["validation_sync"] = vkValidationSync;
|
data["Vulkan"]["validation_sync"] = vkValidationSync;
|
||||||
data["Debug"]["DebugDump"] = isDebugDump;
|
data["Debug"]["DebugDump"] = isDebugDump;
|
||||||
data["LLE"]["libc"] = isLibc;
|
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);
|
std::ofstream file(path, std::ios::out);
|
||||||
file << data;
|
file << data;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace Config {
|
namespace Config {
|
||||||
void load(const std::filesystem::path& path);
|
void load(const std::filesystem::path& path);
|
||||||
|
@ -29,4 +30,36 @@ bool dumpPM4();
|
||||||
bool vkValidationEnabled();
|
bool vkValidationEnabled();
|
||||||
bool vkValidationSyncEnabled();
|
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
|
}; // namespace Config
|
||||||
|
|
|
@ -201,6 +201,11 @@ public:
|
||||||
return WriteSpan(string);
|
return WriteSpan(string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void WriteBytes(const std::filesystem::path path, std::span<u8> vec) {
|
||||||
|
IOFile out(path, FileAccessMode::Write);
|
||||||
|
out.Write(vec);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::filesystem::path file_path;
|
std::filesystem::path file_path;
|
||||||
FileAccessMode file_access_mode{};
|
FileAccessMode file_access_mode{};
|
||||||
|
|
|
@ -4,67 +4,69 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include "crypto.h"
|
#include "crypto.h"
|
||||||
|
|
||||||
RSA::PrivateKey Crypto::key_pkg_derived_key3_keyset_init() {
|
CryptoPP::RSA::PrivateKey Crypto::key_pkg_derived_key3_keyset_init() {
|
||||||
InvertibleRSAFunction params;
|
CryptoPP::InvertibleRSAFunction params;
|
||||||
params.SetPrime1(Integer(pkg_derived_key3_keyset.Prime1, 0x80));
|
params.SetPrime1(CryptoPP::Integer(pkg_derived_key3_keyset.Prime1, 0x80));
|
||||||
params.SetPrime2(Integer(pkg_derived_key3_keyset.Prime2, 0x80));
|
params.SetPrime2(CryptoPP::Integer(pkg_derived_key3_keyset.Prime2, 0x80));
|
||||||
|
|
||||||
params.SetPublicExponent(Integer(pkg_derived_key3_keyset.PublicExponent, 4));
|
params.SetPublicExponent(CryptoPP::Integer(pkg_derived_key3_keyset.PublicExponent, 4));
|
||||||
params.SetPrivateExponent(Integer(pkg_derived_key3_keyset.PrivateExponent, 0x100));
|
params.SetPrivateExponent(CryptoPP::Integer(pkg_derived_key3_keyset.PrivateExponent, 0x100));
|
||||||
|
|
||||||
params.SetModPrime1PrivateExponent(Integer(pkg_derived_key3_keyset.Exponent1, 0x80));
|
params.SetModPrime1PrivateExponent(CryptoPP::Integer(pkg_derived_key3_keyset.Exponent1, 0x80));
|
||||||
params.SetModPrime2PrivateExponent(Integer(pkg_derived_key3_keyset.Exponent2, 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(
|
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;
|
return privateKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
RSA::PrivateKey Crypto::FakeKeyset_keyset_init() {
|
CryptoPP::RSA::PrivateKey Crypto::FakeKeyset_keyset_init() {
|
||||||
InvertibleRSAFunction params;
|
CryptoPP::InvertibleRSAFunction params;
|
||||||
params.SetPrime1(Integer(FakeKeyset_keyset.Prime1, 0x80));
|
params.SetPrime1(CryptoPP::Integer(FakeKeyset_keyset.Prime1, 0x80));
|
||||||
params.SetPrime2(Integer(FakeKeyset_keyset.Prime2, 0x80));
|
params.SetPrime2(CryptoPP::Integer(FakeKeyset_keyset.Prime2, 0x80));
|
||||||
|
|
||||||
params.SetPublicExponent(Integer(FakeKeyset_keyset.PublicExponent, 4));
|
params.SetPublicExponent(CryptoPP::Integer(FakeKeyset_keyset.PublicExponent, 4));
|
||||||
params.SetPrivateExponent(Integer(FakeKeyset_keyset.PrivateExponent, 0x100));
|
params.SetPrivateExponent(CryptoPP::Integer(FakeKeyset_keyset.PrivateExponent, 0x100));
|
||||||
|
|
||||||
params.SetModPrime1PrivateExponent(Integer(FakeKeyset_keyset.Exponent1, 0x80));
|
params.SetModPrime1PrivateExponent(CryptoPP::Integer(FakeKeyset_keyset.Exponent1, 0x80));
|
||||||
params.SetModPrime2PrivateExponent(Integer(FakeKeyset_keyset.Exponent2, 0x80));
|
params.SetModPrime2PrivateExponent(CryptoPP::Integer(FakeKeyset_keyset.Exponent2, 0x80));
|
||||||
|
|
||||||
params.SetModulus(Integer(FakeKeyset_keyset.Modulus, 0x100));
|
params.SetModulus(CryptoPP::Integer(FakeKeyset_keyset.Modulus, 0x100));
|
||||||
params.SetMultiplicativeInverseOfPrime2ModPrime1(Integer(FakeKeyset_keyset.Coefficient, 0x80));
|
params.SetMultiplicativeInverseOfPrime2ModPrime1(
|
||||||
|
CryptoPP::Integer(FakeKeyset_keyset.Coefficient, 0x80));
|
||||||
|
|
||||||
RSA::PrivateKey privateKey(params);
|
CryptoPP::RSA::PrivateKey privateKey(params);
|
||||||
|
|
||||||
return privateKey;
|
return privateKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
RSA::PrivateKey Crypto::DebugRifKeyset_init() {
|
CryptoPP::RSA::PrivateKey Crypto::DebugRifKeyset_init() {
|
||||||
AutoSeededRandomPool rng;
|
CryptoPP::InvertibleRSAFunction params;
|
||||||
InvertibleRSAFunction params;
|
params.SetPrime1(
|
||||||
params.SetPrime1(Integer(DebugRifKeyset_keyset.Prime1, sizeof(DebugRifKeyset_keyset.Prime1)));
|
CryptoPP::Integer(DebugRifKeyset_keyset.Prime1, sizeof(DebugRifKeyset_keyset.Prime1)));
|
||||||
params.SetPrime2(Integer(DebugRifKeyset_keyset.Prime2, sizeof(DebugRifKeyset_keyset.Prime2)));
|
params.SetPrime2(
|
||||||
|
CryptoPP::Integer(DebugRifKeyset_keyset.Prime2, sizeof(DebugRifKeyset_keyset.Prime2)));
|
||||||
|
|
||||||
params.SetPublicExponent(Integer(DebugRifKeyset_keyset.PrivateExponent,
|
params.SetPublicExponent(CryptoPP::Integer(DebugRifKeyset_keyset.PrivateExponent,
|
||||||
sizeof(DebugRifKeyset_keyset.PrivateExponent)));
|
sizeof(DebugRifKeyset_keyset.PrivateExponent)));
|
||||||
params.SetPrivateExponent(Integer(DebugRifKeyset_keyset.PrivateExponent,
|
params.SetPrivateExponent(CryptoPP::Integer(DebugRifKeyset_keyset.PrivateExponent,
|
||||||
sizeof(DebugRifKeyset_keyset.PrivateExponent)));
|
sizeof(DebugRifKeyset_keyset.PrivateExponent)));
|
||||||
|
|
||||||
params.SetModPrime1PrivateExponent(
|
params.SetModPrime1PrivateExponent(CryptoPP::Integer(DebugRifKeyset_keyset.Exponent1,
|
||||||
Integer(DebugRifKeyset_keyset.Exponent1, sizeof(DebugRifKeyset_keyset.Exponent1)));
|
sizeof(DebugRifKeyset_keyset.Exponent1)));
|
||||||
params.SetModPrime2PrivateExponent(
|
params.SetModPrime2PrivateExponent(CryptoPP::Integer(DebugRifKeyset_keyset.Exponent2,
|
||||||
Integer(DebugRifKeyset_keyset.Exponent2, sizeof(DebugRifKeyset_keyset.Exponent2)));
|
sizeof(DebugRifKeyset_keyset.Exponent2)));
|
||||||
|
|
||||||
params.SetModulus(
|
params.SetModulus(
|
||||||
Integer(DebugRifKeyset_keyset.Modulus, sizeof(DebugRifKeyset_keyset.Modulus)));
|
CryptoPP::Integer(DebugRifKeyset_keyset.Modulus, sizeof(DebugRifKeyset_keyset.Modulus)));
|
||||||
params.SetMultiplicativeInverseOfPrime2ModPrime1(
|
params.SetMultiplicativeInverseOfPrime2ModPrime1(CryptoPP::Integer(
|
||||||
Integer(DebugRifKeyset_keyset.Coefficient, sizeof(DebugRifKeyset_keyset.Coefficient)));
|
DebugRifKeyset_keyset.Coefficient, sizeof(DebugRifKeyset_keyset.Coefficient)));
|
||||||
|
|
||||||
RSA::PrivateKey privateKey(params);
|
CryptoPP::RSA::PrivateKey privateKey(params);
|
||||||
|
|
||||||
return privateKey;
|
return privateKey;
|
||||||
}
|
}
|
||||||
|
@ -73,21 +75,21 @@ void Crypto::RSA2048Decrypt(std::span<CryptoPP::byte, 32> dec_key,
|
||||||
std::span<const CryptoPP::byte, 256> ciphertext,
|
std::span<const CryptoPP::byte, 256> ciphertext,
|
||||||
bool is_dk3) { // RSAES_PKCS1v15_
|
bool is_dk3) { // RSAES_PKCS1v15_
|
||||||
// Create an RSA decryptor
|
// Create an RSA decryptor
|
||||||
RSA::PrivateKey privateKey;
|
CryptoPP::RSA::PrivateKey privateKey;
|
||||||
if (is_dk3) {
|
if (is_dk3) {
|
||||||
privateKey = key_pkg_derived_key3_keyset_init();
|
privateKey = key_pkg_derived_key3_keyset_init();
|
||||||
} else {
|
} else {
|
||||||
privateKey = FakeKeyset_keyset_init();
|
privateKey = FakeKeyset_keyset_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
RSAES_PKCS1v15_Decryptor rsaDecryptor(privateKey);
|
CryptoPP::RSAES_PKCS1v15_Decryptor rsaDecryptor(privateKey);
|
||||||
|
|
||||||
// Allocate memory for the decrypted data
|
// Allocate memory for the decrypted data
|
||||||
std::array<CryptoPP::byte, 256> decrypted;
|
std::array<CryptoPP::byte, 256> decrypted;
|
||||||
|
|
||||||
// Perform the decryption
|
// Perform the decryption
|
||||||
AutoSeededRandomPool rng;
|
CryptoPP::AutoSeededRandomPool rng;
|
||||||
DecodingResult result =
|
CryptoPP::DecodingResult result =
|
||||||
rsaDecryptor.Decrypt(rng, ciphertext.data(), decrypted.size(), decrypted.data());
|
rsaDecryptor.Decrypt(rng, ciphertext.data(), decrypted.size(), decrypted.data());
|
||||||
std::copy(decrypted.begin(), decrypted.begin() + dec_key.size(), dec_key.begin());
|
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,
|
void Crypto::PfsGenCryptoKey(std::span<const CryptoPP::byte, 32> ekpfs,
|
||||||
std::span<const CryptoPP::byte, 16> seed,
|
std::span<const CryptoPP::byte, 16> seed,
|
||||||
std::span<CryptoPP::byte, 16> dataKey,
|
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.
|
// Start at 0x10000 to keep the header when decrypting the whole pfs_image.
|
||||||
for (int i = 0; i < src_image.size(); i += 0x1000) {
|
for (int i = 0; i < src_image.size(); i += 0x1000) {
|
||||||
const u64 current_sector = sector + (i / 0x1000);
|
const u64 current_sector = sector + (i / 0x1000);
|
||||||
CryptoPP::ECB_Mode<AES>::Encryption encrypt(tweakKey.data(), tweakKey.size());
|
CryptoPP::ECB_Mode<CryptoPP::AES>::Encryption encrypt(tweakKey.data(), tweakKey.size());
|
||||||
CryptoPP::ECB_Mode<AES>::Decryption decrypt(dataKey.data(), dataKey.size());
|
CryptoPP::ECB_Mode<CryptoPP::AES>::Decryption decrypt(dataKey.data(), dataKey.size());
|
||||||
|
|
||||||
std::array<CryptoPP::byte, 16> tweak{};
|
std::array<CryptoPP::byte, 16> tweak{};
|
||||||
std::array<CryptoPP::byte, 16> encryptedTweak;
|
std::array<CryptoPP::byte, 16> encryptedTweak;
|
||||||
|
|
|
@ -15,17 +15,15 @@
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
#include "keys.h"
|
#include "keys.h"
|
||||||
|
|
||||||
using namespace CryptoPP;
|
|
||||||
|
|
||||||
class Crypto {
|
class Crypto {
|
||||||
public:
|
public:
|
||||||
PkgDerivedKey3Keyset pkg_derived_key3_keyset;
|
PkgDerivedKey3Keyset pkg_derived_key3_keyset;
|
||||||
FakeKeyset FakeKeyset_keyset;
|
FakeKeyset FakeKeyset_keyset;
|
||||||
DebugRifKeyset DebugRifKeyset_keyset;
|
DebugRifKeyset DebugRifKeyset_keyset;
|
||||||
|
|
||||||
RSA::PrivateKey key_pkg_derived_key3_keyset_init();
|
CryptoPP::RSA::PrivateKey key_pkg_derived_key3_keyset_init();
|
||||||
RSA::PrivateKey FakeKeyset_keyset_init();
|
CryptoPP::RSA::PrivateKey FakeKeyset_keyset_init();
|
||||||
RSA::PrivateKey DebugRifKeyset_init();
|
CryptoPP::RSA::PrivateKey DebugRifKeyset_init();
|
||||||
|
|
||||||
void RSA2048Decrypt(std::span<CryptoPP::byte, 32> dk3,
|
void RSA2048Decrypt(std::span<CryptoPP::byte, 32> dk3,
|
||||||
std::span<const CryptoPP::byte, 256> ciphertext,
|
std::span<const CryptoPP::byte, 256> ciphertext,
|
||||||
|
@ -35,6 +33,11 @@ public:
|
||||||
void aesCbcCfb128Decrypt(std::span<const CryptoPP::byte, 32> ivkey,
|
void aesCbcCfb128Decrypt(std::span<const CryptoPP::byte, 32> ivkey,
|
||||||
std::span<const CryptoPP::byte, 256> ciphertext,
|
std::span<const CryptoPP::byte, 256> ciphertext,
|
||||||
std::span<CryptoPP::byte, 256> decrypted);
|
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,
|
void PfsGenCryptoKey(std::span<const CryptoPP::byte, 32> ekpfs,
|
||||||
std::span<const CryptoPP::byte, 16> seed,
|
std::span<const CryptoPP::byte, 16> seed,
|
||||||
std::span<CryptoPP::byte, 16> dataKey,
|
std::span<CryptoPP::byte, 16> dataKey,
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <rsa.h>
|
||||||
|
|
||||||
class FakeKeyset {
|
class FakeKeyset {
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -45,26 +45,54 @@ PKG::PKG() = default;
|
||||||
|
|
||||||
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);
|
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
|
||||||
if (!file.IsOpen()) {
|
if (!file.IsOpen()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
pkgSize = file.GetSize();
|
pkgSize = file.GetSize();
|
||||||
|
|
||||||
PKGHeader pkgheader;
|
|
||||||
file.Read(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
|
// 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.Seek(0x47); // skip first 7 characters of content_id
|
||||||
file.Read(pkgTitleID);
|
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();
|
file.Close();
|
||||||
|
|
||||||
return true;
|
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) {
|
std::string& failreason) {
|
||||||
extract_path = extract;
|
extract_path = extract;
|
||||||
pkgpath = filepath;
|
pkgpath = filepath;
|
||||||
|
@ -75,6 +103,9 @@ bool PKG::Extract(const std::string& filepath, const std::filesystem::path& extr
|
||||||
pkgSize = file.GetSize();
|
pkgSize = file.GetSize();
|
||||||
file.ReadRaw<u8>(&pkgheader, sizeof(PKGHeader));
|
file.ReadRaw<u8>(&pkgheader, sizeof(PKGHeader));
|
||||||
|
|
||||||
|
if (pkgheader.magic != 0x7F434E54)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (pkgheader.pkg_size > pkgSize) {
|
if (pkgheader.pkg_size > pkgSize) {
|
||||||
failreason = "PKG file size is different";
|
failreason = "PKG file size is different";
|
||||||
return false;
|
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 offset = pkgheader.pkg_table_entry_offset;
|
||||||
u32 n_files = pkgheader.pkg_table_entry_count;
|
u32 n_files = pkgheader.pkg_table_entry_count;
|
||||||
|
|
||||||
|
std::array<u8, 64> concatenated_ivkey_dk3;
|
||||||
std::array<u8, 32> seed_digest;
|
std::array<u8, 32> seed_digest;
|
||||||
std::array<std::array<u8, 32>, 7> digest1;
|
std::array<std::array<u8, 32>, 7> digest1;
|
||||||
std::array<std::array<u8, 256>, 7> key1;
|
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
|
// Try to figure out the name
|
||||||
const auto name = GetEntryNameByType(entry.id);
|
const auto name = GetEntryNameByType(entry.id);
|
||||||
|
const auto filepath = extract_path / "sce_sys" / name;
|
||||||
|
std::filesystem::create_directories(filepath.parent_path());
|
||||||
|
|
||||||
if (name.empty()) {
|
if (name.empty()) {
|
||||||
// Just print with id
|
// Just print with id
|
||||||
Common::FS::IOFile out(extract_path / "sce_sys" / std::to_string(entry.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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto filepath = extract_path / "sce_sys" / name;
|
|
||||||
std::filesystem::create_directories(filepath.parent_path());
|
|
||||||
|
|
||||||
if (entry.id == 0x1) { // DIGESTS, seek;
|
if (entry.id == 0x1) { // DIGESTS, seek;
|
||||||
// file.Seek(entry.offset, fsSeekSet);
|
// file.Seek(entry.offset, fsSeekSet);
|
||||||
} else if (entry.id == 0x10) { // ENTRY_KEYS, seek;
|
} 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);
|
file.Read(imgkeydata);
|
||||||
|
|
||||||
// The Concatenated iv + dk3 imagekey for HASH256
|
// 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(), &entry, sizeof(entry));
|
||||||
std::memcpy(concatenated_ivkey_dk3.data() + sizeof(entry), dk3_.data(), sizeof(dk3_));
|
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);
|
Common::FS::IOFile out(extract_path / "sce_sys" / name, Common::FS::FileAccessMode::Write);
|
||||||
out.WriteRaw<u8>(pkg.data() + entry.offset, entry.size);
|
out.WriteRaw<u8>(pkg.data() + entry.offset, entry.size);
|
||||||
out.Close();
|
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
|
// Read the seed
|
||||||
std::array<CryptoPP::byte, 16> seed;
|
std::array<u8, 16> seed;
|
||||||
file.Seek(pkgheader.pfs_image_offset + 0x370);
|
file.Seek(pkgheader.pfs_image_offset + 0x370);
|
||||||
file.Read(seed);
|
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);
|
std::vector<u8> pfs_encrypted(length);
|
||||||
file.Seek(pkgheader.pfs_image_offset);
|
file.Seek(pkgheader.pfs_image_offset);
|
||||||
file.Read(pfs_encrypted);
|
file.Read(pfs_encrypted);
|
||||||
|
file.Close();
|
||||||
// Decrypt the pfs_image.
|
// Decrypt the pfs_image.
|
||||||
std::vector<u8> pfs_decrypted(length);
|
std::vector<u8> pfs_decrypted(length);
|
||||||
PKG::crypto.decryptPFS(dataKey, tweakKey, pfs_encrypted, pfs_decrypted, 0);
|
PKG::crypto.decryptPFS(dataKey, tweakKey, pfs_encrypted, pfs_decrypted, 0);
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "common/endian.h"
|
#include "common/endian.h"
|
||||||
#include "core/crypto/crypto.h"
|
#include "core/crypto/crypto.h"
|
||||||
#include "pfs.h"
|
#include "pfs.h"
|
||||||
|
#include "trp.h"
|
||||||
|
|
||||||
struct PKGHeader {
|
struct PKGHeader {
|
||||||
u32_be magic; // Magic
|
u32_be magic; // Magic
|
||||||
|
@ -103,11 +104,13 @@ public:
|
||||||
PKG();
|
PKG();
|
||||||
~PKG();
|
~PKG();
|
||||||
|
|
||||||
bool Open(const std::string& filepath);
|
bool Open(const std::filesystem::path& filepath);
|
||||||
void ExtractFiles(const int& index);
|
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::string& failreason);
|
||||||
|
|
||||||
|
std::vector<u8> sfo;
|
||||||
|
|
||||||
u32 GetNumberOfFiles() {
|
u32 GetNumberOfFiles() {
|
||||||
return fsTable.size();
|
return fsTable.size();
|
||||||
}
|
}
|
||||||
|
@ -116,16 +119,42 @@ public:
|
||||||
return pkgSize;
|
return pkgSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string GetPkgFlags() {
|
||||||
|
return pkgFlags;
|
||||||
|
}
|
||||||
|
|
||||||
std::string_view GetTitleID() {
|
std::string_view GetTitleID() {
|
||||||
return std::string_view(pkgTitleID, 9);
|
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:
|
private:
|
||||||
Crypto crypto;
|
Crypto crypto;
|
||||||
|
TRP trp;
|
||||||
std::vector<u8> pkg;
|
std::vector<u8> pkg;
|
||||||
u64 pkgSize = 0;
|
u64 pkgSize = 0;
|
||||||
char pkgTitleID[9];
|
char pkgTitleID[9];
|
||||||
PKGHeader pkgheader;
|
PKGHeader pkgheader;
|
||||||
|
std::string pkgFlags;
|
||||||
|
|
||||||
std::unordered_map<int, std::filesystem::path> extractPaths;
|
std::unordered_map<int, std::filesystem::path> extractPaths;
|
||||||
std::vector<pfs_fs_table> fsTable;
|
std::vector<pfs_fs_table> fsTable;
|
||||||
|
@ -133,12 +162,13 @@ private:
|
||||||
std::vector<u64> sectorMap;
|
std::vector<u64> sectorMap;
|
||||||
u64 pfsc_offset;
|
u64 pfsc_offset;
|
||||||
|
|
||||||
std::array<CryptoPP::byte, 32> dk3_;
|
std::array<u8, 32> dk3_;
|
||||||
std::array<CryptoPP::byte, 32> ivKey;
|
std::array<u8, 32> ivKey;
|
||||||
std::array<CryptoPP::byte, 256> imgKey;
|
std::array<u8, 256> imgKey;
|
||||||
std::array<CryptoPP::byte, 32> ekpfsKey;
|
std::array<u8, 32> ekpfsKey;
|
||||||
std::array<CryptoPP::byte, 16> dataKey;
|
std::array<u8, 16> dataKey;
|
||||||
std::array<CryptoPP::byte, 16> tweakKey;
|
std::array<u8, 16> tweakKey;
|
||||||
|
std::vector<u8> decNp;
|
||||||
|
|
||||||
std::filesystem::path pkgpath;
|
std::filesystem::path pkgpath;
|
||||||
std::filesystem::path current_dir;
|
std::filesystem::path current_dir;
|
||||||
|
|
|
@ -12,16 +12,22 @@ PSF::PSF() = default;
|
||||||
|
|
||||||
PSF::~PSF() = default;
|
PSF::~PSF() = default;
|
||||||
|
|
||||||
bool PSF::open(const std::string& filepath) {
|
bool PSF::open(const std::string& filepath, std::vector<u8> psfBuffer) {
|
||||||
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
|
if (!psfBuffer.empty()) {
|
||||||
if (!file.IsOpen()) {
|
psf.resize(psfBuffer.size());
|
||||||
return false;
|
psf = psfBuffer;
|
||||||
}
|
} else {
|
||||||
|
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
|
||||||
|
if (!file.IsOpen()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const u64 psfSize = file.GetSize();
|
const u64 psfSize = file.GetSize();
|
||||||
psf.resize(psfSize);
|
psf.resize(psfSize);
|
||||||
file.Seek(0);
|
file.Seek(0);
|
||||||
file.Read(psf);
|
file.Read(psf);
|
||||||
|
file.Close();
|
||||||
|
}
|
||||||
|
|
||||||
// Parse file contents
|
// Parse file contents
|
||||||
PSFHeader header;
|
PSFHeader header;
|
||||||
|
|
|
@ -35,7 +35,7 @@ public:
|
||||||
PSF();
|
PSF();
|
||||||
~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);
|
std::string GetString(const std::string& key);
|
||||||
u32 GetInteger(const std::string& key);
|
u32 GetInteger(const std::string& key);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
namespace Loader {
|
namespace Loader {
|
||||||
|
|
||||||
FileTypes DetectFileType(const std::string& filepath) {
|
FileTypes DetectFileType(const std::filesystem::path& filepath) {
|
||||||
// No file loaded
|
// No file loaded
|
||||||
if (filepath.empty()) {
|
if (filepath.empty()) {
|
||||||
return FileTypes::Unknown;
|
return FileTypes::Unknown;
|
||||||
|
|
|
@ -14,5 +14,5 @@ enum class FileTypes {
|
||||||
Pkg,
|
Pkg,
|
||||||
};
|
};
|
||||||
|
|
||||||
FileTypes DetectFileType(const std::string& filepath);
|
FileTypes DetectFileType(const std::filesystem::path& filepath);
|
||||||
} // namespace Loader
|
} // namespace Loader
|
||||||
|
|
|
@ -65,7 +65,7 @@ void Emulator::Run(const std::filesystem::path& file) {
|
||||||
for (const auto& entry : std::filesystem::directory_iterator(sce_sys_folder)) {
|
for (const auto& entry : std::filesystem::directory_iterator(sce_sys_folder)) {
|
||||||
if (entry.path().filename() == "param.sfo") {
|
if (entry.path().filename() == "param.sfo") {
|
||||||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
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 id(param_sfo->GetString("CONTENT_ID"), 7, 9);
|
||||||
std::string title(param_sfo->GetString("TITLE"));
|
std::string title(param_sfo->GetString("TITLE"));
|
||||||
LOG_INFO(Loader, "Game id: {} Title: {}", id, title);
|
LOG_INFO(Loader, "Game id: {} Title: {}", id, title);
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 572 B |
Binary file not shown.
After Width: | Height: | Size: 942 B |
Binary file not shown.
After Width: | Height: | Size: 371 B |
Binary file not shown.
After Width: | Height: | Size: 257 B |
Binary file not shown.
After Width: | Height: | Size: 321 B |
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
|
@ -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();
|
||||||
|
}
|
|
@ -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();
|
||||||
|
};
|
|
@ -3,12 +3,9 @@
|
||||||
|
|
||||||
#include "game_grid_frame.h"
|
#include "game_grid_frame.h"
|
||||||
|
|
||||||
GameGridFrame::GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get,
|
GameGridFrame::GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent)
|
||||||
std::shared_ptr<GuiSettings> m_gui_settings, QWidget* parent)
|
: QTableWidget(parent), m_game_info(game_info_get) {
|
||||||
: QTableWidget(parent) {
|
icon_size = Config::getIconSizeGrid();
|
||||||
m_game_info = game_info_get;
|
|
||||||
m_gui_settings_ = m_gui_settings;
|
|
||||||
icon_size = m_gui_settings->GetValue(gui::m_icon_size_grid).toInt();
|
|
||||||
windowWidth = parent->width();
|
windowWidth = parent->width();
|
||||||
this->setShowGrid(false);
|
this->setShowGrid(false);
|
||||||
this->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
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) {
|
connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) {
|
||||||
m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, this, false);
|
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) {
|
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
|
else
|
||||||
m_games_ = m_game_info->m_games;
|
m_games_ = m_game_info->m_games;
|
||||||
m_games_shared = std::make_shared<QVector<GameInfo>>(m_games_);
|
m_games_shared = std::make_shared<QVector<GameInfo>>(m_games_);
|
||||||
|
icon_size = Config::getIconSizeGrid(); // update icon size for resize event.
|
||||||
icon_size = m_gui_settings_->GetValue(gui::m_icon_size_grid)
|
|
||||||
.toInt(); // update icon size for resize event.
|
|
||||||
|
|
||||||
int gamesPerRow = windowWidth / (icon_size + 20); // 2 x cell widget border size.
|
int gamesPerRow = windowWidth / (icon_size + 20); // 2 x cell widget border size.
|
||||||
int row = 0;
|
int row = 0;
|
||||||
|
@ -62,10 +63,10 @@ void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool from
|
||||||
QWidget* widget = new QWidget();
|
QWidget* widget = new QWidget();
|
||||||
QVBoxLayout* layout = new QVBoxLayout();
|
QVBoxLayout* layout = new QVBoxLayout();
|
||||||
QLabel* image_label = new QLabel();
|
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);
|
QSize(icon_size, icon_size), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||||
image_label->setFixedSize(icon.width(), icon.height());
|
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));
|
QLabel* name_label = new QLabel(QString::fromStdString(m_games_[gameCounter].serial));
|
||||||
name_label->setAlignment(Qt::AlignHCenter);
|
name_label->setAlignment(Qt::AlignHCenter);
|
||||||
layout->addWidget(image_label);
|
layout->addWidget(image_label);
|
||||||
|
@ -86,8 +87,7 @@ void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool from
|
||||||
"color: #000000;"
|
"color: #000000;"
|
||||||
"border: 1px solid #000000;"
|
"border: 1px solid #000000;"
|
||||||
"padding: 2px;"
|
"padding: 2px;"
|
||||||
"font-size: 12px; }")
|
"font-size: 12px; }");
|
||||||
.arg(tooltipText);
|
|
||||||
widget->setStyleSheet(tooltipStyle);
|
widget->setStyleSheet(tooltipStyle);
|
||||||
this->setCellWidget(row, column, widget);
|
this->setCellWidget(row, column, widget);
|
||||||
|
|
||||||
|
@ -134,10 +134,12 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameGridFrame::RefreshGridBackgroundImage() {
|
void GameGridFrame::RefreshGridBackgroundImage() {
|
||||||
QPixmap blurredPixmap = QPixmap::fromImage(backgroundImage);
|
if (!backgroundImage.isNull()) {
|
||||||
QPalette palette;
|
QPalette palette;
|
||||||
palette.setBrush(QPalette::Base, QBrush(blurredPixmap.scaled(size(), Qt::IgnoreAspectRatio)));
|
palette.setBrush(QPalette::Base,
|
||||||
QColor transparentColor = QColor(135, 206, 235, 40);
|
QBrush(backgroundImage.scaled(size(), Qt::IgnoreAspectRatio)));
|
||||||
palette.setColor(QPalette::Highlight, transparentColor);
|
QColor transparentColor = QColor(135, 206, 235, 40);
|
||||||
this->setPalette(palette);
|
palette.setColor(QPalette::Highlight, transparentColor);
|
||||||
|
this->setPalette(palette);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -14,6 +14,7 @@
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QtConcurrent/QtConcurrent>
|
#include <QtConcurrent/QtConcurrent>
|
||||||
|
#include "common/config.h"
|
||||||
#include "game_info.h"
|
#include "game_info.h"
|
||||||
#include "game_list_utils.h"
|
#include "game_list_utils.h"
|
||||||
#include "gui_context_menus.h"
|
#include "gui_context_menus.h"
|
||||||
|
@ -33,14 +34,16 @@ private:
|
||||||
GameListUtils m_game_list_utils;
|
GameListUtils m_game_list_utils;
|
||||||
GuiContextMenus m_gui_context_menus;
|
GuiContextMenus m_gui_context_menus;
|
||||||
std::shared_ptr<GameInfoClass> m_game_info;
|
std::shared_ptr<GameInfoClass> m_game_info;
|
||||||
std::shared_ptr<GuiSettings> m_gui_settings_;
|
|
||||||
std::shared_ptr<QVector<GameInfo>> m_games_shared;
|
std::shared_ptr<QVector<GameInfo>> m_games_shared;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get,
|
explicit GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent = nullptr);
|
||||||
std::shared_ptr<GuiSettings> m_gui_settings, QWidget* parent = nullptr);
|
|
||||||
void PopulateGameGrid(QVector<GameInfo> m_games, bool fromSearch);
|
void PopulateGameGrid(QVector<GameInfo> m_games, bool fromSearch);
|
||||||
|
|
||||||
|
bool cellClicked = false;
|
||||||
int icon_size;
|
int icon_size;
|
||||||
int windowWidth;
|
int windowWidth;
|
||||||
|
int crtRow;
|
||||||
|
int crtColumn;
|
||||||
|
int columnCnt;
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,25 +3,43 @@
|
||||||
|
|
||||||
#include <future>
|
#include <future>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <QProgressDialog>
|
||||||
|
#include <QtConcurrent/QtConcurrent>
|
||||||
#include "game_info.h"
|
#include "game_info.h"
|
||||||
|
|
||||||
void GameInfoClass::GetGameInfo() {
|
GameInfoClass::GameInfoClass() = default;
|
||||||
QString installDir = m_gui_settings->GetValue(gui::settings_install_dir).toString();
|
GameInfoClass::~GameInfoClass() = default;
|
||||||
std::filesystem::path parent_folder(installDir.toStdString());
|
|
||||||
std::vector<std::string> filePaths;
|
void GameInfoClass::GetGameInfo(QWidget* parent) {
|
||||||
for (const auto& dir : std::filesystem::directory_iterator(parent_folder)) {
|
QString installDir = QString::fromStdString(Config::getGameInstallDir());
|
||||||
if (dir.is_directory()) {
|
QStringList filePaths;
|
||||||
filePaths.push_back(dir.path().string());
|
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) {
|
// Progress bar, please be patient :)
|
||||||
futures.emplace_back(std::async(std::launch::async, readGameInfo, filePath));
|
QProgressDialog dialog("Loading game list, please wait :3", "Cancel", 0, 0, parent);
|
||||||
}
|
dialog.setWindowTitle("Loading...");
|
||||||
|
|
||||||
for (auto& future : futures) {
|
QFutureWatcher<void> futureWatcher;
|
||||||
m_games.push_back(future.get());
|
GameListUtils game_util;
|
||||||
}
|
bool finished = false;
|
||||||
std::sort(m_games.begin(), m_games.end(), CompareStrings);
|
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();
|
||||||
}
|
}
|
|
@ -3,34 +3,20 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <QFuture>
|
#include <QFuture>
|
||||||
|
#include <QObject>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QtConcurrent/QtConcurrent>
|
#include <QtConcurrent/QtConcurrent>
|
||||||
|
#include "common/config.h"
|
||||||
#include "core/file_format/psf.h"
|
#include "core/file_format/psf.h"
|
||||||
#include "game_list_utils.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:
|
public:
|
||||||
void GetGameInfo();
|
GameInfoClass();
|
||||||
|
~GameInfoClass();
|
||||||
std::shared_ptr<GuiSettings> m_gui_settings = std::make_shared<GuiSettings>();
|
void GetGameInfo(QWidget* parent = nullptr);
|
||||||
QVector<GameInfo> m_games;
|
QVector<GameInfo> m_games;
|
||||||
|
|
||||||
static bool CompareStrings(GameInfo& a, GameInfo& b) {
|
static bool CompareStrings(GameInfo& a, GameInfo& b) {
|
||||||
|
@ -39,19 +25,17 @@ public:
|
||||||
|
|
||||||
static GameInfo readGameInfo(const std::string& filePath) {
|
static GameInfo readGameInfo(const std::string& filePath) {
|
||||||
GameInfo game;
|
GameInfo game;
|
||||||
GameListUtils game_util;
|
|
||||||
game.size = game_util.GetFolderSize(QDir(QString::fromStdString(filePath))).toStdString();
|
|
||||||
game.path = filePath;
|
game.path = filePath;
|
||||||
|
|
||||||
PSF psf;
|
PSF psf;
|
||||||
if (psf.open(game.path + "/sce_sys/param.sfo")) {
|
if (psf.open(game.path + "/sce_sys/param.sfo", {})) {
|
||||||
QString iconpath(QString::fromStdString(game.path) + "/sce_sys/icon0.png");
|
game.icon_path = game.path + "/sce_sys/icon0.png";
|
||||||
QString picpath(QString::fromStdString(game.path) + "/sce_sys/pic1.png");
|
QString iconpath = QString::fromStdString(game.icon_path);
|
||||||
game.icon_path = iconpath.toStdString();
|
game.icon = QImage(iconpath);
|
||||||
game.icon = QPixmap(iconpath);
|
game.pic_path = game.path + "/sce_sys/pic1.png";
|
||||||
game.pic_path = picpath.toStdString();
|
|
||||||
game.name = psf.GetString("TITLE");
|
game.name = psf.GetString("TITLE");
|
||||||
game.serial = psf.GetString("TITLE_ID");
|
game.serial = psf.GetString("TITLE_ID");
|
||||||
|
game.region = GameListUtils::GetRegion(psf.GetString("CONTENT_ID").at(0)).toStdString();
|
||||||
u32 fw_int = psf.GetInteger("SYSTEM_VER");
|
u32 fw_int = psf.GetInteger("SYSTEM_VER");
|
||||||
QString fw = QString::number(fw_int, 16);
|
QString fw = QString::number(fw_int, 16);
|
||||||
QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.')
|
QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.')
|
||||||
|
|
|
@ -13,10 +13,8 @@
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include "gui_settings.h"
|
|
||||||
|
|
||||||
GameInstallDialog::GameInstallDialog(std::shared_ptr<GuiSettings> gui_settings)
|
GameInstallDialog::GameInstallDialog() : m_gamesDirectory(nullptr) {
|
||||||
: m_gamesDirectory(nullptr), m_gui_settings(std::move(gui_settings)) {
|
|
||||||
auto layout = new QVBoxLayout(this);
|
auto layout = new QVBoxLayout(this);
|
||||||
|
|
||||||
layout->addWidget(SetupGamesDirectory());
|
layout->addWidget(SetupGamesDirectory());
|
||||||
|
@ -43,7 +41,7 @@ QWidget* GameInstallDialog::SetupGamesDirectory() {
|
||||||
|
|
||||||
// Input.
|
// Input.
|
||||||
m_gamesDirectory = new QLineEdit();
|
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);
|
m_gamesDirectory->setMinimumWidth(400);
|
||||||
|
|
||||||
layout->addWidget(m_gamesDirectory);
|
layout->addWidget(m_gamesDirectory);
|
||||||
|
@ -78,7 +76,8 @@ void GameInstallDialog::Save() {
|
||||||
return;
|
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();
|
accept();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,14 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include "gui_settings.h"
|
#include "common/config.h"
|
||||||
|
#include "common/path_util.h"
|
||||||
|
|
||||||
class QLineEdit;
|
class QLineEdit;
|
||||||
|
|
||||||
class GameInstallDialog final : public QDialog {
|
class GameInstallDialog final : public QDialog {
|
||||||
public:
|
public:
|
||||||
GameInstallDialog(std::shared_ptr<GuiSettings> gui_settings);
|
GameInstallDialog();
|
||||||
~GameInstallDialog();
|
~GameInstallDialog();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
@ -23,5 +24,4 @@ private:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QLineEdit* m_gamesDirectory;
|
QLineEdit* m_gamesDirectory;
|
||||||
std::shared_ptr<GuiSettings> m_gui_settings;
|
|
||||||
};
|
};
|
|
@ -1,14 +1,12 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include "game_list_frame.h"
|
#include "game_list_frame.h"
|
||||||
|
|
||||||
GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get,
|
GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent)
|
||||||
std::shared_ptr<GuiSettings> m_gui_settings, QWidget* parent)
|
: QTableWidget(parent), m_game_info(game_info_get) {
|
||||||
: QTableWidget(parent) {
|
icon_size = Config::getIconSize();
|
||||||
m_game_info = game_info_get;
|
|
||||||
icon_size = m_gui_settings->GetValue(gui::m_icon_size).toInt();
|
|
||||||
|
|
||||||
this->setShowGrid(false);
|
this->setShowGrid(false);
|
||||||
this->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
this->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||||
this->setSelectionBehavior(QAbstractItemView::SelectRows);
|
this->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||||
|
@ -25,17 +23,19 @@ GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get,
|
||||||
this->horizontalHeader()->setSortIndicatorShown(true);
|
this->horizontalHeader()->setSortIndicatorShown(true);
|
||||||
this->horizontalHeader()->setStretchLastSection(true);
|
this->horizontalHeader()->setStretchLastSection(true);
|
||||||
this->setContextMenuPolicy(Qt::CustomContextMenu);
|
this->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
this->setColumnCount(8);
|
this->setColumnCount(9);
|
||||||
this->setColumnWidth(1, 250);
|
this->setColumnWidth(1, 250);
|
||||||
this->setColumnWidth(2, 110);
|
this->setColumnWidth(2, 110);
|
||||||
this->setColumnWidth(3, 80);
|
this->setColumnWidth(3, 80);
|
||||||
this->setColumnWidth(4, 90);
|
this->setColumnWidth(4, 90);
|
||||||
this->setColumnWidth(5, 80);
|
this->setColumnWidth(5, 80);
|
||||||
this->setColumnWidth(6, 80);
|
this->setColumnWidth(6, 80);
|
||||||
|
this->setColumnWidth(7, 80);
|
||||||
QStringList headers;
|
QStringList headers;
|
||||||
headers << "Icon"
|
headers << "Icon"
|
||||||
<< "Name"
|
<< "Name"
|
||||||
<< "Serial"
|
<< "Serial"
|
||||||
|
<< "Region"
|
||||||
<< "Firmware"
|
<< "Firmware"
|
||||||
<< "Size"
|
<< "Size"
|
||||||
<< "Version"
|
<< "Version"
|
||||||
|
@ -81,13 +81,14 @@ void GameListFrame::PopulateGameList() {
|
||||||
ResizeIcons(icon_size);
|
ResizeIcons(icon_size);
|
||||||
|
|
||||||
for (int i = 0; i < m_game_info->m_games.size(); i++) {
|
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(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(i, 2, QString::fromStdString(m_game_info->m_games[i].serial));
|
||||||
SetTableItem(this, i, 3, QString::fromStdString(m_game_info->m_games[i].fw));
|
SetRegionFlag(i, 3, QString::fromStdString(m_game_info->m_games[i].region));
|
||||||
SetTableItem(this, i, 4, QString::fromStdString(m_game_info->m_games[i].size));
|
SetTableItem(i, 4, QString::fromStdString(m_game_info->m_games[i].fw));
|
||||||
SetTableItem(this, i, 5, QString::fromStdString(m_game_info->m_games[i].version));
|
SetTableItem(i, 5, QString::fromStdString(m_game_info->m_games[i].size));
|
||||||
SetTableItem(this, i, 6, QString::fromStdString(m_game_info->m_games[i].category));
|
SetTableItem(i, 6, QString::fromStdString(m_game_info->m_games[i].version));
|
||||||
SetTableItem(this, i, 7, QString::fromStdString(m_game_info->m_games[i].path));
|
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() {
|
void GameListFrame::RefreshListBackgroundImage() {
|
||||||
QPixmap blurredPixmap = QPixmap::fromImage(backgroundImage);
|
if (!backgroundImage.isNull()) {
|
||||||
QPalette palette;
|
QPalette palette;
|
||||||
palette.setBrush(QPalette::Base, QBrush(blurredPixmap.scaled(size(), Qt::IgnoreAspectRatio)));
|
palette.setBrush(QPalette::Base,
|
||||||
QColor transparentColor = QColor(135, 206, 235, 40);
|
QBrush(backgroundImage.scaled(size(), Qt::IgnoreAspectRatio)));
|
||||||
palette.setColor(QPalette::Highlight, transparentColor);
|
QColor transparentColor = QColor(135, 206, 235, 40);
|
||||||
this->setPalette(palette);
|
palette.setColor(QPalette::Highlight, transparentColor);
|
||||||
|
this->setPalette(palette);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameListFrame::SortNameAscending(int columnIndex) {
|
void GameListFrame::SortNameAscending(int columnIndex) {
|
||||||
|
@ -142,22 +145,66 @@ void GameListFrame::SortNameDescending(int columnIndex) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameListFrame::ResizeIcons(int iconSize) {
|
void GameListFrame::ResizeIcons(int iconSize) {
|
||||||
QList<int> indices;
|
for (int index = 0; auto& game : m_game_info->m_games) {
|
||||||
for (int i = 0; i < m_game_info->m_games.size(); i++) {
|
QImage scaledPixmap = game.icon.scaled(QSize(iconSize, iconSize), Qt::KeepAspectRatio,
|
||||||
indices.append(i);
|
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);
|
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);
|
||||||
}
|
}
|
|
@ -23,8 +23,7 @@
|
||||||
class GameListFrame : public QTableWidget {
|
class GameListFrame : public QTableWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit GameListFrame(std::shared_ptr<GameInfoClass> game_info_get,
|
explicit GameListFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent = nullptr);
|
||||||
std::shared_ptr<GuiSettings> m_gui_settings, QWidget* parent = nullptr);
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void GameListFrameClosed();
|
void GameListFrameClosed();
|
||||||
|
|
||||||
|
@ -35,6 +34,8 @@ public Q_SLOTS:
|
||||||
void SortNameDescending(int columnIndex);
|
void SortNameDescending(int columnIndex);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void SetTableItem(int row, int column, QString itemStr);
|
||||||
|
void SetRegionFlag(int row, int column, QString itemStr);
|
||||||
QList<QAction*> m_columnActs;
|
QList<QAction*> m_columnActs;
|
||||||
GameInfoClass* game_inf_get = nullptr;
|
GameInfoClass* game_inf_get = nullptr;
|
||||||
bool ListSortedAsc = true;
|
bool ListSortedAsc = true;
|
||||||
|
@ -50,30 +51,6 @@ public:
|
||||||
|
|
||||||
int icon_size;
|
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) {
|
static bool CompareStringsAscending(GameInfo a, GameInfo b, int columnIndex) {
|
||||||
if (columnIndex == 1) {
|
if (columnIndex == 1) {
|
||||||
return a.name < b.name;
|
return a.name < b.name;
|
||||||
|
|
|
@ -8,6 +8,21 @@
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QString>
|
#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 {
|
class GameListUtils {
|
||||||
public:
|
public:
|
||||||
static QString FormatSize(qint64 size) {
|
static QString FormatSize(qint64 size) {
|
||||||
|
@ -33,25 +48,49 @@ public:
|
||||||
return sizeString + " " + suffixes[suffixIndex];
|
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);
|
QDirIterator it(dir.absolutePath(), QDirIterator::Subdirectories);
|
||||||
qint64 total = 0;
|
qint64 total = 0;
|
||||||
|
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
// check if entry is file
|
it.next();
|
||||||
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()) {
|
|
||||||
total += it.fileInfo().size();
|
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) {
|
QImage BlurImage(const QImage& image, const QRect& rect, int radius) {
|
||||||
|
|
|
@ -10,8 +10,10 @@
|
||||||
#include <QTreeWidget>
|
#include <QTreeWidget>
|
||||||
#include <QTreeWidgetItem>
|
#include <QTreeWidgetItem>
|
||||||
#include "game_info.h"
|
#include "game_info.h"
|
||||||
|
#include "trophy_viewer.h"
|
||||||
|
|
||||||
class GuiContextMenus {
|
class GuiContextMenus : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
void RequestGameMenu(const QPoint& pos, QVector<GameInfo> m_games, QTableWidget* widget,
|
void RequestGameMenu(const QPoint& pos, QVector<GameInfo> m_games, QTableWidget* widget,
|
||||||
bool isList) {
|
bool isList) {
|
||||||
|
@ -27,9 +29,11 @@ public:
|
||||||
QMenu menu(widget);
|
QMenu menu(widget);
|
||||||
QAction openFolder("Open Game Folder", widget);
|
QAction openFolder("Open Game Folder", widget);
|
||||||
QAction openSfoViewer("SFO Viewer", widget);
|
QAction openSfoViewer("SFO Viewer", widget);
|
||||||
|
QAction openTrophyViewer("Trophy Viewer", widget);
|
||||||
|
|
||||||
menu.addAction(&openFolder);
|
menu.addAction(&openFolder);
|
||||||
menu.addAction(&openSfoViewer);
|
menu.addAction(&openSfoViewer);
|
||||||
|
menu.addAction(&openTrophyViewer);
|
||||||
// Show menu.
|
// Show menu.
|
||||||
auto selected = menu.exec(global_pos);
|
auto selected = menu.exec(global_pos);
|
||||||
if (!selected) {
|
if (!selected) {
|
||||||
|
@ -43,9 +47,13 @@ public:
|
||||||
|
|
||||||
if (selected == &openSfoViewer) {
|
if (selected == &openSfoViewer) {
|
||||||
PSF psf;
|
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();
|
int rows = psf.map_strings.size() + psf.map_integers.size();
|
||||||
QTableWidget* tableWidget = new QTableWidget(rows, 2);
|
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
|
tableWidget->verticalHeader()->setVisible(false); // Hide vertical header
|
||||||
int row = 0;
|
int row = 0;
|
||||||
|
|
||||||
|
@ -88,6 +96,15 @@ public:
|
||||||
tableWidget->show();
|
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) {
|
int GetRowIndex(QTreeWidget* treeWidget, QTreeWidgetItem* item) {
|
||||||
|
@ -112,9 +129,9 @@ public:
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RequestGameMenuPKGViewer(const QPoint& pos, QStringList m_pkg_app_list,
|
void RequestGameMenuPKGViewer(
|
||||||
QTreeWidget* treeWidget,
|
const QPoint& pos, QStringList m_pkg_app_list, QTreeWidget* treeWidget,
|
||||||
std::function<void(std::string, int, int)> InstallDragDropPkg) {
|
std::function<void(std::filesystem::path, int, int)> InstallDragDropPkg) {
|
||||||
QPoint global_pos = treeWidget->viewport()->mapToGlobal(pos); // context menu position
|
QPoint global_pos = treeWidget->viewport()->mapToGlobal(pos); // context menu position
|
||||||
QTreeWidgetItem* currentItem = treeWidget->currentItem(); // current clicked item
|
QTreeWidgetItem* currentItem = treeWidget->currentItem(); // current clicked item
|
||||||
int itemIndex = GetRowIndex(treeWidget, currentItem); // row
|
int itemIndex = GetRowIndex(treeWidget, currentItem); // row
|
||||||
|
@ -131,15 +148,11 @@ public:
|
||||||
|
|
||||||
if (selected == &installPackage) {
|
if (selected == &installPackage) {
|
||||||
QStringList pkg_app_ = m_pkg_app_list[itemIndex].split(";;");
|
QStringList pkg_app_ = m_pkg_app_list[itemIndex].split(";;");
|
||||||
std::string pkg_to_install = pkg_app_[9].toStdString();
|
std::filesystem::path path(pkg_app_[9].toStdString());
|
||||||
InstallDragDropPkg(pkg_to_install, 1, 1);
|
#ifdef _WIN32
|
||||||
|
path = std::filesystem::path(pkg_app_[9].toStdWString());
|
||||||
QFile file("log.txt");
|
#endif
|
||||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text))
|
InstallDragDropPkg(path, 1, 1);
|
||||||
return;
|
|
||||||
|
|
||||||
QTextStream stream(&file);
|
|
||||||
stream << QString::fromStdString(pkg_to_install) << Qt::endl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -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};
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
};
|
|
|
@ -3,21 +3,33 @@
|
||||||
|
|
||||||
#include <QtWidgets/QApplication>
|
#include <QtWidgets/QApplication>
|
||||||
|
|
||||||
|
#include "common/config.h"
|
||||||
|
#include "core/file_sys/fs.h"
|
||||||
#include "qt_gui/game_install_dialog.h"
|
#include "qt_gui/game_install_dialog.h"
|
||||||
#include "qt_gui/gui_settings.h"
|
|
||||||
#include "qt_gui/main_window.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[]) {
|
int main(int argc, char* argv[]) {
|
||||||
QApplication a(argc, argv);
|
QApplication a(argc, argv);
|
||||||
auto m_gui_settings = std::make_shared<GuiSettings>();
|
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
||||||
if (m_gui_settings->GetValue(gui::settings_install_dir) == "") {
|
Config::load(config_dir / "config.toml");
|
||||||
GameInstallDialog dlg(m_gui_settings);
|
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();
|
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();
|
m_main_window->Init();
|
||||||
|
|
||||||
return a.exec();
|
return a.exec();
|
||||||
|
|
|
@ -13,38 +13,41 @@
|
||||||
#include "core/file_format/pkg.h"
|
#include "core/file_format/pkg.h"
|
||||||
#include "core/loader.h"
|
#include "core/loader.h"
|
||||||
#include "game_install_dialog.h"
|
#include "game_install_dialog.h"
|
||||||
#include "gui_settings.h"
|
|
||||||
#include "main_window.h"
|
#include "main_window.h"
|
||||||
|
|
||||||
MainWindow::MainWindow(std::shared_ptr<GuiSettings> gui_settings, QWidget* parent)
|
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
|
||||||
: QMainWindow(parent), ui(new Ui::MainWindow), m_gui_settings(std::move(gui_settings)) {
|
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
setAttribute(Qt::WA_DeleteOnClose);
|
setAttribute(Qt::WA_DeleteOnClose);
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow() {
|
MainWindow::~MainWindow() {
|
||||||
SaveWindowState();
|
SaveWindowState();
|
||||||
|
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
||||||
|
Config::save(config_dir / "config.toml");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::Init() {
|
bool MainWindow::Init() {
|
||||||
auto start = std::chrono::steady_clock::now();
|
auto start = std::chrono::steady_clock::now();
|
||||||
|
// setup ui
|
||||||
AddUiWidgets();
|
AddUiWidgets();
|
||||||
CreateActions();
|
CreateActions();
|
||||||
|
CreateRecentGameActions();
|
||||||
|
ConfigureGuiFromSettings();
|
||||||
CreateDockWindows();
|
CreateDockWindows();
|
||||||
CreateConnects();
|
CreateConnects();
|
||||||
SetLastUsedTheme();
|
SetLastUsedTheme();
|
||||||
SetLastIconSizeBullet();
|
SetLastIconSizeBullet();
|
||||||
ConfigureGuiFromSettings();
|
// show ui
|
||||||
LoadGameLists();
|
|
||||||
|
|
||||||
setMinimumSize(350, minimumSizeHint().height());
|
setMinimumSize(350, minimumSizeHint().height());
|
||||||
setWindowTitle(QString::fromStdString("shadPS4 v" + std::string(Common::VERSION)));
|
setWindowTitle(QString::fromStdString("shadPS4 v" + std::string(Common::VERSION)));
|
||||||
show();
|
this->show();
|
||||||
|
// load game list
|
||||||
|
LoadGameLists();
|
||||||
|
|
||||||
auto end = std::chrono::steady_clock::now();
|
auto end = std::chrono::steady_clock::now();
|
||||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||||
statusBar = new QStatusBar(this);
|
statusBar.reset(new QStatusBar);
|
||||||
m_main_window->setStatusBar(statusBar);
|
this->setStatusBar(statusBar.data());
|
||||||
// Update status bar
|
// Update status bar
|
||||||
int numGames = m_game_info->m_games.size();
|
int numGames = m_game_info->m_games.size();
|
||||||
QString statusMessage = "Games: " + QString::number(numGames) + " (" +
|
QString statusMessage = "Games: " + QString::number(numGames) + " (" +
|
||||||
|
@ -93,45 +96,59 @@ void MainWindow::AddUiWidgets() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::CreateDockWindows() {
|
void MainWindow::CreateDockWindows() {
|
||||||
m_main_window = new QMainWindow();
|
// place holder widget is needed for good health they say :)
|
||||||
m_main_window->setContextMenuPolicy(Qt::PreventContextMenu);
|
QWidget* phCentralWidget = new QWidget(this);
|
||||||
|
setCentralWidget(phCentralWidget);
|
||||||
|
|
||||||
// resize window to last W and H
|
m_dock_widget.reset(new QDockWidget("Game List", this));
|
||||||
QSize window_size = m_gui_settings->GetValue(gui::m_window_size).toSize();
|
m_game_list_frame.reset(new GameListFrame(m_game_info, this));
|
||||||
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_game_list_frame->setObjectName("gamelist");
|
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_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;
|
int slider_pos = 0;
|
||||||
if (table_mode == 0) { // List
|
if (table_mode == 0) { // List
|
||||||
m_game_grid_frame->hide();
|
m_game_grid_frame->hide();
|
||||||
m_dock_widget->setWidget(m_game_list_frame);
|
m_elf_viewer->hide();
|
||||||
slider_pos = m_gui_settings->GetValue(gui::m_slide_pos).toInt();
|
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;
|
ui->sizeSlider->setSliderPosition(slider_pos); // set slider pos at start;
|
||||||
isTableList = true;
|
isTableList = true;
|
||||||
} else { // Grid
|
} else if (table_mode == 1) { // Grid
|
||||||
m_game_list_frame->hide();
|
m_game_list_frame->hide();
|
||||||
m_dock_widget->setWidget(m_game_grid_frame);
|
m_elf_viewer->hide();
|
||||||
slider_pos = m_gui_settings->GetValue(gui::m_slide_pos_grid).toInt();
|
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;
|
ui->sizeSlider->setSliderPosition(slider_pos); // set slider pos at start;
|
||||||
isTableList = false;
|
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_dock_widget->setAllowedAreas(Qt::AllDockWidgetAreas);
|
||||||
m_main_window->setDockNestingEnabled(true);
|
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() {
|
void MainWindow::LoadGameLists() {
|
||||||
// Get game info from game folders.
|
// Get game info from game folders.
|
||||||
m_game_info->GetGameInfo();
|
m_game_info->GetGameInfo(this);
|
||||||
if (isTableList) {
|
if (isTableList) {
|
||||||
m_game_list_frame->PopulateGameList();
|
m_game_list_frame->PopulateGameList();
|
||||||
} else {
|
} else {
|
||||||
|
@ -141,111 +158,150 @@ void MainWindow::LoadGameLists() {
|
||||||
|
|
||||||
void MainWindow::CreateConnects() {
|
void MainWindow::CreateConnects() {
|
||||||
connect(this, &MainWindow::WindowResized, this, &MainWindow::HandleResize);
|
connect(this, &MainWindow::WindowResized, this, &MainWindow::HandleResize);
|
||||||
|
|
||||||
connect(ui->mw_searchbar, &QLineEdit::textChanged, this, &MainWindow::SearchGameTable);
|
connect(ui->mw_searchbar, &QLineEdit::textChanged, this, &MainWindow::SearchGameTable);
|
||||||
|
|
||||||
connect(ui->exitAct, &QAction::triggered, this, &QWidget::close);
|
connect(ui->exitAct, &QAction::triggered, this, &QWidget::close);
|
||||||
|
|
||||||
connect(ui->refreshGameListAct, &QAction::triggered, this, &MainWindow::RefreshGameTable);
|
connect(ui->refreshGameListAct, &QAction::triggered, this, &MainWindow::RefreshGameTable);
|
||||||
|
connect(this, &MainWindow::ExtractionFinished, this, &MainWindow::RefreshGameTable);
|
||||||
|
|
||||||
connect(ui->sizeSlider, &QSlider::valueChanged, this, [this](int value) {
|
connect(ui->sizeSlider, &QSlider::valueChanged, this, [this](int value) {
|
||||||
if (isTableList) {
|
if (isTableList) {
|
||||||
m_game_list_frame->icon_size =
|
m_game_list_frame->icon_size =
|
||||||
36 + value; // 36 is the minimum icon size to use due to text disappearing.
|
36 + value; // 36 is the minimum icon size to use due to text disappearing.
|
||||||
m_game_list_frame->ResizeIcons(36 + value);
|
m_game_list_frame->ResizeIcons(36 + value);
|
||||||
m_gui_settings->SetValue(gui::m_icon_size, 36 + value);
|
Config::setIconSize(36 + value);
|
||||||
m_gui_settings->SetValue(gui::m_slide_pos, value);
|
Config::setSliderPositon(value);
|
||||||
} else {
|
} else {
|
||||||
m_game_grid_frame->icon_size = 69 + value;
|
m_game_grid_frame->icon_size = 69 + value;
|
||||||
m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false);
|
m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false);
|
||||||
m_gui_settings->SetValue(gui::m_icon_size_grid, 69 + value);
|
Config::setIconSizeGrid(69 + value);
|
||||||
m_gui_settings->SetValue(gui::m_slide_pos_grid, 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) {
|
if (isTableList) {
|
||||||
m_game_list_frame->icon_size =
|
m_game_list_frame->icon_size =
|
||||||
36; // 36 is the minimum icon size to use due to text disappearing.
|
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
|
ui->sizeSlider->setValue(0); // icone_size - 36
|
||||||
m_gui_settings->SetValue(gui::m_slide_pos, 0);
|
Config::setIconSize(36);
|
||||||
|
Config::setSliderPositon(0);
|
||||||
} else {
|
} else {
|
||||||
m_gui_settings->SetValue(gui::m_icon_size_grid, 69); // nice :3
|
ui->sizeSlider->setValue(0); // icone_size - 36
|
||||||
ui->sizeSlider->setValue(0); // icone_size - 36
|
Config::setIconSizeGrid(69);
|
||||||
m_gui_settings->SetValue(gui::m_slide_pos_grid, 0);
|
Config::setSliderPositonGrid(0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(ui->setIconSizeSmallAct, &QAction::triggered, this, [this](int value) {
|
connect(ui->setIconSizeSmallAct, &QAction::triggered, this, [this]() {
|
||||||
if (isTableList) {
|
if (isTableList) {
|
||||||
m_game_list_frame->icon_size = 64;
|
m_game_list_frame->icon_size = 64;
|
||||||
m_gui_settings->SetValue(gui::m_icon_size, 64);
|
|
||||||
ui->sizeSlider->setValue(28);
|
ui->sizeSlider->setValue(28);
|
||||||
m_gui_settings->SetValue(gui::m_slide_pos, 28);
|
Config::setIconSize(64);
|
||||||
|
Config::setSliderPositon(28);
|
||||||
} else {
|
} else {
|
||||||
m_gui_settings->SetValue(gui::m_icon_size_grid, 97);
|
|
||||||
ui->sizeSlider->setValue(28);
|
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) {
|
if (isTableList) {
|
||||||
m_game_list_frame->icon_size = 128;
|
m_game_list_frame->icon_size = 128;
|
||||||
m_gui_settings->SetValue(gui::m_icon_size, 128);
|
|
||||||
ui->sizeSlider->setValue(92);
|
ui->sizeSlider->setValue(92);
|
||||||
m_gui_settings->SetValue(gui::m_slide_pos, 92);
|
Config::setIconSize(128);
|
||||||
|
Config::setSliderPositon(92);
|
||||||
} else {
|
} else {
|
||||||
m_gui_settings->SetValue(gui::m_icon_size_grid, 160);
|
|
||||||
ui->sizeSlider->setValue(92);
|
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) {
|
if (isTableList) {
|
||||||
m_game_list_frame->icon_size = 256;
|
m_game_list_frame->icon_size = 256;
|
||||||
m_gui_settings->SetValue(gui::m_icon_size, 256);
|
|
||||||
ui->sizeSlider->setValue(220);
|
ui->sizeSlider->setValue(220);
|
||||||
m_gui_settings->SetValue(gui::m_slide_pos, 220);
|
Config::setIconSize(256);
|
||||||
|
Config::setSliderPositon(220);
|
||||||
} else {
|
} else {
|
||||||
m_gui_settings->SetValue(gui::m_icon_size_grid, 256);
|
|
||||||
ui->sizeSlider->setValue(220);
|
ui->sizeSlider->setValue(220);
|
||||||
m_gui_settings->SetValue(gui::m_slide_pos_grid, 220);
|
Config::setIconSizeGrid(256);
|
||||||
|
Config::setSliderPositonGrid(220);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// List
|
||||||
connect(ui->setlistModeListAct, &QAction::triggered, m_dock_widget, [this]() {
|
connect(ui->setlistModeListAct, &QAction::triggered, m_dock_widget.data(), [this]() {
|
||||||
m_dock_widget->setWidget(m_game_list_frame);
|
m_dock_widget->setWidget(m_game_list_frame.data());
|
||||||
m_game_list_frame->show();
|
|
||||||
m_game_grid_frame->hide();
|
m_game_grid_frame->hide();
|
||||||
|
m_elf_viewer->hide();
|
||||||
|
m_game_list_frame->show();
|
||||||
if (m_game_list_frame->item(0, 0) == nullptr) {
|
if (m_game_list_frame->item(0, 0) == nullptr) {
|
||||||
m_game_list_frame->clearContents();
|
m_game_list_frame->clearContents();
|
||||||
m_game_list_frame->PopulateGameList();
|
m_game_list_frame->PopulateGameList();
|
||||||
}
|
}
|
||||||
isTableList = true;
|
isTableList = true;
|
||||||
m_gui_settings->SetValue(gui::m_table_mode, 0); // save table mode
|
Config::setTableMode(0);
|
||||||
int slider_pos = m_gui_settings->GetValue(gui::m_slide_pos).toInt();
|
int slider_pos = Config::getSliderPositon();
|
||||||
|
ui->sizeSlider->setEnabled(true);
|
||||||
ui->sizeSlider->setSliderPosition(slider_pos);
|
ui->sizeSlider->setSliderPosition(slider_pos);
|
||||||
});
|
});
|
||||||
|
// Grid
|
||||||
connect(ui->setlistModeGridAct, &QAction::triggered, m_dock_widget, [this]() {
|
connect(ui->setlistModeGridAct, &QAction::triggered, m_dock_widget.data(), [this]() {
|
||||||
m_dock_widget->setWidget(m_game_grid_frame);
|
m_dock_widget->setWidget(m_game_grid_frame.data());
|
||||||
m_game_grid_frame->show();
|
m_game_grid_frame->show();
|
||||||
m_game_list_frame->hide();
|
m_game_list_frame->hide();
|
||||||
|
m_elf_viewer->hide();
|
||||||
if (m_game_grid_frame->item(0, 0) == nullptr) {
|
if (m_game_grid_frame->item(0, 0) == nullptr) {
|
||||||
m_game_grid_frame->clearContents();
|
m_game_grid_frame->clearContents();
|
||||||
m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false);
|
m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false);
|
||||||
}
|
}
|
||||||
isTableList = false;
|
isTableList = false;
|
||||||
m_gui_settings->SetValue(gui::m_table_mode, 1); // save table mode
|
Config::setTableMode(1);
|
||||||
int slider_pos_grid = m_gui_settings->GetValue(gui::m_slide_pos_grid).toInt();
|
int slider_pos_grid = Config::getSliderPositonGrid();
|
||||||
|
ui->sizeSlider->setEnabled(true);
|
||||||
ui->sizeSlider->setSliderPosition(slider_pos_grid);
|
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.
|
// Dump game list.
|
||||||
connect(ui->dumpGameListAct, &QAction::triggered, this, [this] {
|
connect(ui->dumpGameListAct, &QAction::triggered, this, [&] {
|
||||||
QString filePath = qApp->applicationDirPath().append("/GameList.txt");
|
QString filePath = qApp->applicationDirPath().append("/GameList.txt");
|
||||||
QFile file(filePath);
|
QFile file(filePath);
|
||||||
QTextStream out(&file);
|
QTextStream out(&file);
|
||||||
|
@ -270,21 +326,26 @@ void MainWindow::CreateConnects() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Package install.
|
// Package install.
|
||||||
connect(ui->bootInstallPkgAct, &QAction::triggered, this, [this] { InstallPkg(); });
|
connect(ui->bootInstallPkgAct, &QAction::triggered, this, &MainWindow::InstallPkg);
|
||||||
connect(ui->gameInstallPathAct, &QAction::triggered, this, [this] { InstallDirectory(); });
|
connect(ui->gameInstallPathAct, &QAction::triggered, this, &MainWindow::InstallDirectory);
|
||||||
|
|
||||||
|
// elf viewer
|
||||||
|
connect(ui->addElfFolderAct, &QAction::triggered, m_elf_viewer.data(),
|
||||||
|
&ElfViewer::OpenElfFolder);
|
||||||
|
|
||||||
// Package Viewer.
|
// Package Viewer.
|
||||||
connect(ui->pkgViewerAct, &QAction::triggered, this, [this]() {
|
connect(ui->pkgViewerAct, &QAction::triggered, this, [this]() {
|
||||||
PKGViewer* pkgViewer = new PKGViewer(m_game_info, m_gui_settings,
|
PKGViewer* pkgViewer = new PKGViewer(
|
||||||
[this](std::string file, int pkgNum, int nPkg) {
|
m_game_info, this, [this](std::filesystem::path file, int pkgNum, int nPkg) {
|
||||||
this->InstallDragDropPkg(file, pkgNum, nPkg);
|
this->InstallDragDropPkg(file, pkgNum, nPkg);
|
||||||
});
|
});
|
||||||
pkgViewer->show();
|
pkgViewer->show();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Themes
|
// Themes
|
||||||
connect(ui->setThemeLight, &QAction::triggered, &m_window_themes, [this]() {
|
connect(ui->setThemeLight, &QAction::triggered, &m_window_themes, [this]() {
|
||||||
m_window_themes.SetWindowTheme(Theme::Light, ui->mw_searchbar);
|
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) {
|
if (!isIconBlack) {
|
||||||
SetUiIcons(true);
|
SetUiIcons(true);
|
||||||
isIconBlack = true;
|
isIconBlack = true;
|
||||||
|
@ -292,7 +353,7 @@ void MainWindow::CreateConnects() {
|
||||||
});
|
});
|
||||||
connect(ui->setThemeDark, &QAction::triggered, &m_window_themes, [this]() {
|
connect(ui->setThemeDark, &QAction::triggered, &m_window_themes, [this]() {
|
||||||
m_window_themes.SetWindowTheme(Theme::Dark, ui->mw_searchbar);
|
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) {
|
if (isIconBlack) {
|
||||||
SetUiIcons(false);
|
SetUiIcons(false);
|
||||||
isIconBlack = false;
|
isIconBlack = false;
|
||||||
|
@ -300,7 +361,7 @@ void MainWindow::CreateConnects() {
|
||||||
});
|
});
|
||||||
connect(ui->setThemeGreen, &QAction::triggered, &m_window_themes, [this]() {
|
connect(ui->setThemeGreen, &QAction::triggered, &m_window_themes, [this]() {
|
||||||
m_window_themes.SetWindowTheme(Theme::Green, ui->mw_searchbar);
|
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) {
|
if (isIconBlack) {
|
||||||
SetUiIcons(false);
|
SetUiIcons(false);
|
||||||
isIconBlack = false;
|
isIconBlack = false;
|
||||||
|
@ -308,7 +369,7 @@ void MainWindow::CreateConnects() {
|
||||||
});
|
});
|
||||||
connect(ui->setThemeBlue, &QAction::triggered, &m_window_themes, [this]() {
|
connect(ui->setThemeBlue, &QAction::triggered, &m_window_themes, [this]() {
|
||||||
m_window_themes.SetWindowTheme(Theme::Blue, ui->mw_searchbar);
|
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) {
|
if (isIconBlack) {
|
||||||
SetUiIcons(false);
|
SetUiIcons(false);
|
||||||
isIconBlack = false;
|
isIconBlack = false;
|
||||||
|
@ -316,7 +377,7 @@ void MainWindow::CreateConnects() {
|
||||||
});
|
});
|
||||||
connect(ui->setThemeViolet, &QAction::triggered, &m_window_themes, [this]() {
|
connect(ui->setThemeViolet, &QAction::triggered, &m_window_themes, [this]() {
|
||||||
m_window_themes.SetWindowTheme(Theme::Violet, ui->mw_searchbar);
|
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) {
|
if (isIconBlack) {
|
||||||
SetUiIcons(false);
|
SetUiIcons(false);
|
||||||
isIconBlack = false;
|
isIconBlack = false;
|
||||||
|
@ -345,8 +406,8 @@ void MainWindow::SearchGameTable(const QString& text) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::RefreshGameTable() {
|
void MainWindow::RefreshGameTable() {
|
||||||
m_game_info->m_games.clear();
|
// m_game_info->m_games.clear();
|
||||||
m_game_info->GetGameInfo();
|
m_game_info->GetGameInfo(this);
|
||||||
m_game_list_frame->clearContents();
|
m_game_list_frame->clearContents();
|
||||||
m_game_list_frame->PopulateGameList();
|
m_game_list_frame->PopulateGameList();
|
||||||
m_game_grid_frame->clearContents();
|
m_game_grid_frame->clearContents();
|
||||||
|
@ -358,16 +419,10 @@ void MainWindow::RefreshGameTable() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::ConfigureGuiFromSettings() {
|
void MainWindow::ConfigureGuiFromSettings() {
|
||||||
// Restore GUI state if needed. We need to if they exist.
|
setGeometry(Config::getMainWindowGeometryX(), Config::getMainWindowGeometryY(),
|
||||||
if (!restoreGeometry(m_gui_settings->GetValue(gui::main_window_geometry).toByteArray())) {
|
Config::getMainWindowGeometryW(), Config::getMainWindowGeometryH());
|
||||||
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());
|
|
||||||
|
|
||||||
|
ui->showGameListAct->setChecked(true);
|
||||||
if (isTableList) {
|
if (isTableList) {
|
||||||
ui->setlistModeListAct->setChecked(true);
|
ui->setlistModeListAct->setChecked(true);
|
||||||
} else {
|
} else {
|
||||||
|
@ -376,87 +431,155 @@ void MainWindow::ConfigureGuiFromSettings() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::SaveWindowState() const {
|
void MainWindow::SaveWindowState() const {
|
||||||
// Save gui settings
|
Config::setMainWindowWidth(this->width());
|
||||||
m_gui_settings->SetValue(gui::main_window_geometry, saveGeometry());
|
Config::setMainWindowHeight(this->height());
|
||||||
m_gui_settings->SetValue(gui::main_window_windowState, saveState());
|
Config::setMainWindowGeometry(this->geometry().x(), this->geometry().y(),
|
||||||
m_gui_settings->SetValue(gui::m_window_size,
|
this->geometry().width(), this->geometry().height());
|
||||||
QSize(m_main_window->width(), m_main_window->height()));
|
|
||||||
m_gui_settings->SetValue(gui::main_window_mwState, m_main_window->saveState());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::InstallPkg() {
|
void MainWindow::InstallPkg() {
|
||||||
QStringList fileNames = QFileDialog::getOpenFileNames(
|
QFileDialog dialog;
|
||||||
this, tr("Install PKG Files"), QDir::currentPath(), tr("PKG File (*.PKG)"));
|
dialog.setFileMode(QFileDialog::ExistingFiles);
|
||||||
int nPkg = fileNames.size();
|
dialog.setNameFilter(tr("PKG File (*.PKG)"));
|
||||||
int pkgNum = 0;
|
if (dialog.exec()) {
|
||||||
for (const QString& file : fileNames) {
|
QStringList fileNames = dialog.selectedFiles();
|
||||||
pkgNum++;
|
int nPkg = fileNames.size();
|
||||||
MainWindow::InstallDragDropPkg(file.toStdString(), pkgNum, nPkg);
|
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) {
|
if (Loader::DetectFileType(file) == Loader::FileTypes::Pkg) {
|
||||||
PKG pkg;
|
pkg = PKG();
|
||||||
pkg.Open(file);
|
pkg.Open(file);
|
||||||
std::string failreason;
|
std::string failreason;
|
||||||
const auto extract_path =
|
const auto extract_path =
|
||||||
std::filesystem::path(
|
std::filesystem::path(Config::getGameInstallDir()) / pkg.GetTitleID();
|
||||||
m_gui_settings->GetValue(gui::settings_install_dir).toString().toStdString()) /
|
QString pkgType = QString::fromStdString(pkg.GetPkgFlags());
|
||||||
pkg.GetTitleID();
|
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)) {
|
if (!pkg.Extract(file, extract_path, failreason)) {
|
||||||
QMessageBox::critical(this, "PKG ERROR", QString::fromStdString(failreason),
|
QMessageBox::critical(this, "PKG ERROR", QString::fromStdString(failreason));
|
||||||
QMessageBox::Ok);
|
|
||||||
} else {
|
} else {
|
||||||
int nfiles = pkg.GetNumberOfFiles();
|
int nfiles = pkg.GetNumberOfFiles();
|
||||||
|
|
||||||
QList<int> indices;
|
QVector<int> indices;
|
||||||
for (int i = 0; i < nfiles; i++) {
|
for (int i = 0; i < nfiles; i++) {
|
||||||
indices.append(i);
|
indices.append(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
QProgressDialog dialog;
|
QProgressDialog dialog;
|
||||||
dialog.setWindowTitle("PKG Extraction");
|
dialog.setWindowTitle("PKG Extraction");
|
||||||
|
dialog.setWindowModality(Qt::WindowModal);
|
||||||
QString extractmsg = QString("Extracting PKG %1/%2").arg(pkgNum).arg(nPkg);
|
QString extractmsg = QString("Extracting PKG %1/%2").arg(pkgNum).arg(nPkg);
|
||||||
dialog.setLabelText(extractmsg);
|
dialog.setLabelText(extractmsg);
|
||||||
|
dialog.setAutoClose(true);
|
||||||
|
dialog.setRange(0, nfiles);
|
||||||
|
|
||||||
// Create a QFutureWatcher and connect signals and slots.
|
|
||||||
QFutureWatcher<void> futureWatcher;
|
QFutureWatcher<void> futureWatcher;
|
||||||
QObject::connect(&futureWatcher, SIGNAL(finished()), &dialog, SLOT(reset()));
|
connect(&futureWatcher, &QFutureWatcher<void>::finished, this, [=, this]() {
|
||||||
QObject::connect(&dialog, SIGNAL(canceled()), &futureWatcher, SLOT(cancel()));
|
if (pkgNum == nPkg) {
|
||||||
QObject::connect(&futureWatcher, SIGNAL(progressRangeChanged(int, int)), &dialog,
|
QString path = QString::fromStdString(Config::getGameInstallDir());
|
||||||
SLOT(setRange(int, int)));
|
QMessageBox extractMsgBox(this);
|
||||||
QObject::connect(&futureWatcher, SIGNAL(progressValueChanged(int)), &dialog,
|
extractMsgBox.setWindowTitle("Extraction Finished");
|
||||||
SLOT(setValue(int)));
|
extractMsgBox.setText(QString("Game successfully installed at %1").arg(path));
|
||||||
|
extractMsgBox.addButton(QMessageBox::Ok);
|
||||||
futureWatcher.setFuture(QtConcurrent::map(
|
extractMsgBox.setDefaultButton(QMessageBox::Ok);
|
||||||
indices, std::bind(&PKG::ExtractFiles, pkg, std::placeholders::_1)));
|
connect(&extractMsgBox, &QMessageBox::buttonClicked, this,
|
||||||
|
[&](QAbstractButton* button) {
|
||||||
// Display the dialog and start the event loop.
|
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();
|
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 {
|
} else {
|
||||||
QMessageBox::critical(this, "PKG ERROR", "File doesn't appear to be a valid PKG file",
|
QMessageBox::critical(this, "PKG ERROR", "File doesn't appear to be a valid PKG file");
|
||||||
QMessageBox::Ok);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::InstallDirectory() {
|
void MainWindow::InstallDirectory() {
|
||||||
GameInstallDialog dlg(m_gui_settings);
|
GameInstallDialog dlg;
|
||||||
dlg.exec();
|
dlg.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::SetLastUsedTheme() {
|
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);
|
m_window_themes.SetWindowTheme(lastTheme, ui->mw_searchbar);
|
||||||
|
|
||||||
switch (lastTheme) {
|
switch (lastTheme) {
|
||||||
|
@ -489,7 +612,7 @@ void MainWindow::SetLastUsedTheme() {
|
||||||
|
|
||||||
void MainWindow::SetLastIconSizeBullet() {
|
void MainWindow::SetLastIconSizeBullet() {
|
||||||
// set QAction bullet point if applicable
|
// set QAction bullet point if applicable
|
||||||
int lastSize = m_gui_settings->GetValue(gui::m_icon_size).toInt();
|
int lastSize = Config::getIconSize();
|
||||||
switch (lastSize) {
|
switch (lastSize) {
|
||||||
case 36:
|
case 36:
|
||||||
ui->setIconSizeTinyAct->setChecked(true);
|
ui->setIconSizeTinyAct->setChecked(true);
|
||||||
|
@ -507,47 +630,30 @@ void MainWindow::SetLastIconSizeBullet() {
|
||||||
}
|
}
|
||||||
|
|
||||||
QIcon MainWindow::RecolorIcon(const QIcon& icon, bool isWhite) {
|
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);
|
QColor clr(isWhite ? Qt::white : Qt::black);
|
||||||
QBitmap mask = pixmap.createMaskFromColor(clr, Qt::MaskOutColor);
|
QBitmap mask = pixmap.createMaskFromColor(clr, Qt::MaskOutColor);
|
||||||
pixmap.fill(QColor(isWhite ? Qt::black : Qt::white));
|
pixmap.fill(QColor(isWhite ? Qt::black : Qt::white));
|
||||||
pixmap.setMask(mask);
|
pixmap.setMask(mask);
|
||||||
QIcon newIcon(pixmap);
|
return QIcon(pixmap);
|
||||||
return newIcon;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::SetUiIcons(bool isWhite) {
|
void MainWindow::SetUiIcons(bool isWhite) {
|
||||||
QIcon icon;
|
ui->bootInstallPkgAct->setIcon(RecolorIcon(ui->bootInstallPkgAct->icon(), isWhite));
|
||||||
icon = RecolorIcon(ui->bootInstallPkgAct->icon(), isWhite);
|
ui->exitAct->setIcon(RecolorIcon(ui->exitAct->icon(), isWhite));
|
||||||
ui->bootInstallPkgAct->setIcon(icon);
|
ui->setlistModeListAct->setIcon(RecolorIcon(ui->setlistModeListAct->icon(), isWhite));
|
||||||
icon = RecolorIcon(ui->exitAct->icon(), isWhite);
|
ui->setlistModeGridAct->setIcon(RecolorIcon(ui->setlistModeGridAct->icon(), isWhite));
|
||||||
ui->exitAct->setIcon(icon);
|
ui->gameInstallPathAct->setIcon(RecolorIcon(ui->gameInstallPathAct->icon(), isWhite));
|
||||||
icon = RecolorIcon(ui->setlistModeListAct->icon(), isWhite);
|
ui->menuThemes->setIcon(RecolorIcon(ui->menuThemes->icon(), isWhite));
|
||||||
ui->setlistModeListAct->setIcon(icon);
|
ui->menuGame_List_Icons->setIcon(RecolorIcon(ui->menuGame_List_Icons->icon(), isWhite));
|
||||||
icon = RecolorIcon(ui->setlistModeGridAct->icon(), isWhite);
|
ui->playButton->setIcon(RecolorIcon(ui->playButton->icon(), isWhite));
|
||||||
ui->setlistModeGridAct->setIcon(icon);
|
ui->pauseButton->setIcon(RecolorIcon(ui->pauseButton->icon(), isWhite));
|
||||||
icon = RecolorIcon(ui->gameInstallPathAct->icon(), isWhite);
|
ui->stopButton->setIcon(RecolorIcon(ui->stopButton->icon(), isWhite));
|
||||||
ui->gameInstallPathAct->setIcon(icon);
|
ui->settingsButton->setIcon(RecolorIcon(ui->settingsButton->icon(), isWhite));
|
||||||
icon = RecolorIcon(ui->menuThemes->icon(), isWhite);
|
ui->controllerButton->setIcon(RecolorIcon(ui->controllerButton->icon(), isWhite));
|
||||||
ui->menuThemes->setIcon(icon);
|
ui->refreshGameListAct->setIcon(RecolorIcon(ui->refreshGameListAct->icon(), isWhite));
|
||||||
icon = RecolorIcon(ui->menuGame_List_Icons->icon(), isWhite);
|
ui->menuGame_List_Mode->setIcon(RecolorIcon(ui->menuGame_List_Mode->icon(), isWhite));
|
||||||
ui->menuGame_List_Icons->setIcon(icon);
|
ui->pkgViewerAct->setIcon(RecolorIcon(ui->pkgViewerAct->icon(), isWhite));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::resizeEvent(QResizeEvent* event) {
|
void MainWindow::resizeEvent(QResizeEvent* event) {
|
||||||
|
@ -563,4 +669,44 @@ void MainWindow::HandleResize(QResizeEvent* event) {
|
||||||
m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false);
|
m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false);
|
||||||
m_game_grid_frame->RefreshGridBackgroundImage();
|
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());
|
||||||
|
});
|
||||||
}
|
}
|
|
@ -3,10 +3,19 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAbstractButton>
|
||||||
#include <QActionGroup>
|
#include <QActionGroup>
|
||||||
#include <QDragEnterEvent>
|
#include <QDragEnterEvent>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QMimeData>
|
#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_grid_frame.h"
|
||||||
#include "game_info.h"
|
#include "game_info.h"
|
||||||
#include "game_list_frame.h"
|
#include "game_list_frame.h"
|
||||||
|
@ -15,26 +24,19 @@
|
||||||
#include "main_window_ui.h"
|
#include "main_window_ui.h"
|
||||||
#include "pkg_viewer.h"
|
#include "pkg_viewer.h"
|
||||||
|
|
||||||
class GuiSettings;
|
|
||||||
class GameListFrame;
|
class GameListFrame;
|
||||||
|
|
||||||
class MainWindow : public QMainWindow {
|
class MainWindow : public QMainWindow {
|
||||||
Q_OBJECT
|
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:
|
signals:
|
||||||
void WindowResized(QResizeEvent* event);
|
void WindowResized(QResizeEvent* event);
|
||||||
|
void ExtractionFinished();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MainWindow(std::shared_ptr<GuiSettings> gui_settings, QWidget* parent = nullptr);
|
explicit MainWindow(QWidget* parent = nullptr);
|
||||||
~MainWindow();
|
~MainWindow();
|
||||||
bool Init();
|
bool Init();
|
||||||
void InstallPkg();
|
void InstallDragDropPkg(std::filesystem::path file, int pkgNum, int nPkg);
|
||||||
void InstallDragDropPkg(std::string file, int pkgNum, int nPkg);
|
|
||||||
void InstallDirectory();
|
void InstallDirectory();
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
|
@ -45,38 +47,40 @@ private Q_SLOTS:
|
||||||
void HandleResize(QResizeEvent* event);
|
void HandleResize(QResizeEvent* event);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Ui_MainWindow* ui;
|
||||||
void AddUiWidgets();
|
void AddUiWidgets();
|
||||||
void CreateActions();
|
void CreateActions();
|
||||||
|
void CreateRecentGameActions();
|
||||||
void CreateDockWindows();
|
void CreateDockWindows();
|
||||||
void LoadGameLists();
|
void LoadGameLists();
|
||||||
void CreateConnects();
|
void CreateConnects();
|
||||||
void SetLastUsedTheme();
|
void SetLastUsedTheme();
|
||||||
void SetLastIconSizeBullet();
|
void SetLastIconSizeBullet();
|
||||||
void SetUiIcons(bool isWhite);
|
void SetUiIcons(bool isWhite);
|
||||||
|
void InstallPkg();
|
||||||
|
void AddRecentFiles(QString filePath);
|
||||||
QIcon RecolorIcon(const QIcon& icon, bool isWhite);
|
QIcon RecolorIcon(const QIcon& icon, bool isWhite);
|
||||||
|
|
||||||
bool isIconBlack = false;
|
bool isIconBlack = false;
|
||||||
bool isTableList = true;
|
bool isTableList = true;
|
||||||
|
|
||||||
QActionGroup* m_icon_size_act_group = nullptr;
|
QActionGroup* m_icon_size_act_group = nullptr;
|
||||||
QActionGroup* m_list_mode_act_group = nullptr;
|
QActionGroup* m_list_mode_act_group = nullptr;
|
||||||
QActionGroup* m_theme_act_group = nullptr;
|
QActionGroup* m_theme_act_group = nullptr;
|
||||||
|
QActionGroup* m_recent_files_group = nullptr;
|
||||||
|
PKG pkg;
|
||||||
// Dockable widget frames
|
// Dockable widget frames
|
||||||
QMainWindow* m_main_window = nullptr;
|
|
||||||
WindowThemes m_window_themes;
|
WindowThemes m_window_themes;
|
||||||
GameListUtils m_game_list_utils;
|
GameListUtils m_game_list_utils;
|
||||||
QDockWidget* m_dock_widget = nullptr;
|
QScopedPointer<QDockWidget> m_dock_widget;
|
||||||
// Game Lists
|
// Game Lists
|
||||||
GameListFrame* m_game_list_frame = nullptr;
|
QScopedPointer<GameListFrame> m_game_list_frame;
|
||||||
GameGridFrame* m_game_grid_frame = nullptr;
|
QScopedPointer<GameGridFrame> m_game_grid_frame;
|
||||||
// Packge Viewer
|
QScopedPointer<ElfViewer> m_elf_viewer;
|
||||||
PKGViewer* m_pkg_viewer = nullptr;
|
|
||||||
// Status Bar.
|
// Status Bar.
|
||||||
QStatusBar* statusBar = nullptr;
|
QScopedPointer<QStatusBar> statusBar;
|
||||||
|
|
||||||
|
PSF psf;
|
||||||
|
|
||||||
std::shared_ptr<GameInfoClass> m_game_info = std::make_shared<GameInfoClass>();
|
std::shared_ptr<GameInfoClass> m_game_info = std::make_shared<GameInfoClass>();
|
||||||
std::shared_ptr<GuiSettings> m_gui_settings;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void dragEnterEvent(QDragEnterEvent* event1) override {
|
void dragEnterEvent(QDragEnterEvent* event1) override {
|
||||||
|
@ -93,7 +97,11 @@ protected:
|
||||||
int nPkg = urlList.size();
|
int nPkg = urlList.size();
|
||||||
for (const QUrl& url : urlList) {
|
for (const QUrl& url : urlList) {
|
||||||
pkgNum++;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ QT_BEGIN_NAMESPACE
|
||||||
class Ui_MainWindow {
|
class Ui_MainWindow {
|
||||||
public:
|
public:
|
||||||
QAction* bootInstallPkgAct;
|
QAction* bootInstallPkgAct;
|
||||||
|
QAction* addElfFolderAct;
|
||||||
QAction* exitAct;
|
QAction* exitAct;
|
||||||
QAction* showGameListAct;
|
QAction* showGameListAct;
|
||||||
QAction* refreshGameListAct;
|
QAction* refreshGameListAct;
|
||||||
|
@ -39,6 +40,7 @@ public:
|
||||||
QAction* setIconSizeLargeAct;
|
QAction* setIconSizeLargeAct;
|
||||||
QAction* setlistModeListAct;
|
QAction* setlistModeListAct;
|
||||||
QAction* setlistModeGridAct;
|
QAction* setlistModeGridAct;
|
||||||
|
QAction* setlistElfAct;
|
||||||
QAction* gameInstallPathAct;
|
QAction* gameInstallPathAct;
|
||||||
QAction* dumpGameListAct;
|
QAction* dumpGameListAct;
|
||||||
QAction* pkgViewerAct;
|
QAction* pkgViewerAct;
|
||||||
|
@ -54,14 +56,13 @@ public:
|
||||||
QPushButton* stopButton;
|
QPushButton* stopButton;
|
||||||
QPushButton* settingsButton;
|
QPushButton* settingsButton;
|
||||||
QPushButton* controllerButton;
|
QPushButton* controllerButton;
|
||||||
QWidget* emuRunWidget;
|
|
||||||
QHBoxLayout* emuRunLayer;
|
|
||||||
|
|
||||||
QWidget* sizeSliderContainer;
|
QWidget* sizeSliderContainer;
|
||||||
QHBoxLayout* sizeSliderContainer_layout;
|
QHBoxLayout* sizeSliderContainer_layout;
|
||||||
QSlider* sizeSlider;
|
QSlider* sizeSlider;
|
||||||
QMenuBar* menuBar;
|
QMenuBar* menuBar;
|
||||||
QMenu* menuFile;
|
QMenu* menuFile;
|
||||||
|
QMenu* menuRecent;
|
||||||
QMenu* menuView;
|
QMenu* menuView;
|
||||||
QMenu* menuGame_List_Icons;
|
QMenu* menuGame_List_Icons;
|
||||||
QMenu* menuGame_List_Mode;
|
QMenu* menuGame_List_Mode;
|
||||||
|
@ -91,6 +92,8 @@ public:
|
||||||
bootInstallPkgAct = new QAction(MainWindow);
|
bootInstallPkgAct = new QAction(MainWindow);
|
||||||
bootInstallPkgAct->setObjectName("bootInstallPkgAct");
|
bootInstallPkgAct->setObjectName("bootInstallPkgAct");
|
||||||
bootInstallPkgAct->setIcon(QIcon(":images/file_icon.png"));
|
bootInstallPkgAct->setIcon(QIcon(":images/file_icon.png"));
|
||||||
|
addElfFolderAct = new QAction(MainWindow);
|
||||||
|
addElfFolderAct->setObjectName("addElfFolderAct");
|
||||||
exitAct = new QAction(MainWindow);
|
exitAct = new QAction(MainWindow);
|
||||||
exitAct->setObjectName("exitAct");
|
exitAct->setObjectName("exitAct");
|
||||||
exitAct->setIcon(QIcon(":images/exit_icon.png"));
|
exitAct->setIcon(QIcon(":images/exit_icon.png"));
|
||||||
|
@ -99,7 +102,7 @@ public:
|
||||||
showGameListAct->setCheckable(true);
|
showGameListAct->setCheckable(true);
|
||||||
refreshGameListAct = new QAction(MainWindow);
|
refreshGameListAct = new QAction(MainWindow);
|
||||||
refreshGameListAct->setObjectName("refreshGameListAct");
|
refreshGameListAct->setObjectName("refreshGameListAct");
|
||||||
refreshGameListAct->setIcon(QIcon(":/images/refresh_icon.png"));
|
refreshGameListAct->setIcon(QIcon(":images/refresh_icon.png"));
|
||||||
setIconSizeTinyAct = new QAction(MainWindow);
|
setIconSizeTinyAct = new QAction(MainWindow);
|
||||||
setIconSizeTinyAct->setObjectName("setIconSizeTinyAct");
|
setIconSizeTinyAct->setObjectName("setIconSizeTinyAct");
|
||||||
setIconSizeTinyAct->setCheckable(true);
|
setIconSizeTinyAct->setCheckable(true);
|
||||||
|
@ -121,6 +124,9 @@ public:
|
||||||
setlistModeGridAct->setObjectName("setlistModeGridAct");
|
setlistModeGridAct->setObjectName("setlistModeGridAct");
|
||||||
setlistModeGridAct->setCheckable(true);
|
setlistModeGridAct->setCheckable(true);
|
||||||
setlistModeGridAct->setIcon(QIcon(":images/grid_icon.png"));
|
setlistModeGridAct->setIcon(QIcon(":images/grid_icon.png"));
|
||||||
|
setlistElfAct = new QAction(MainWindow);
|
||||||
|
setlistElfAct->setObjectName("setlistModeGridAct");
|
||||||
|
setlistElfAct->setCheckable(true);
|
||||||
gameInstallPathAct = new QAction(MainWindow);
|
gameInstallPathAct = new QAction(MainWindow);
|
||||||
gameInstallPathAct->setObjectName("gameInstallPathAct");
|
gameInstallPathAct->setObjectName("gameInstallPathAct");
|
||||||
gameInstallPathAct->setIcon(QIcon(":images/folder_icon.png"));
|
gameInstallPathAct->setIcon(QIcon(":images/folder_icon.png"));
|
||||||
|
@ -219,6 +225,8 @@ public:
|
||||||
menuBar->setContextMenuPolicy(Qt::PreventContextMenu);
|
menuBar->setContextMenuPolicy(Qt::PreventContextMenu);
|
||||||
menuFile = new QMenu(menuBar);
|
menuFile = new QMenu(menuBar);
|
||||||
menuFile->setObjectName("menuFile");
|
menuFile->setObjectName("menuFile");
|
||||||
|
menuRecent = new QMenu(menuFile);
|
||||||
|
menuRecent->setObjectName("menuRecent");
|
||||||
menuView = new QMenu(menuBar);
|
menuView = new QMenu(menuBar);
|
||||||
menuView->setObjectName("menuView");
|
menuView->setObjectName("menuView");
|
||||||
menuGame_List_Icons = new QMenu(menuView);
|
menuGame_List_Icons = new QMenu(menuView);
|
||||||
|
@ -243,6 +251,9 @@ public:
|
||||||
menuBar->addAction(menuView->menuAction());
|
menuBar->addAction(menuView->menuAction());
|
||||||
menuBar->addAction(menuSettings->menuAction());
|
menuBar->addAction(menuSettings->menuAction());
|
||||||
menuFile->addAction(bootInstallPkgAct);
|
menuFile->addAction(bootInstallPkgAct);
|
||||||
|
menuFile->addAction(addElfFolderAct);
|
||||||
|
menuFile->addSeparator();
|
||||||
|
menuFile->addAction(menuRecent->menuAction());
|
||||||
menuFile->addSeparator();
|
menuFile->addSeparator();
|
||||||
menuFile->addAction(exitAct);
|
menuFile->addAction(exitAct);
|
||||||
menuView->addAction(showGameListAct);
|
menuView->addAction(showGameListAct);
|
||||||
|
@ -262,6 +273,7 @@ public:
|
||||||
menuGame_List_Icons->addAction(setIconSizeLargeAct);
|
menuGame_List_Icons->addAction(setIconSizeLargeAct);
|
||||||
menuGame_List_Mode->addAction(setlistModeListAct);
|
menuGame_List_Mode->addAction(setlistModeListAct);
|
||||||
menuGame_List_Mode->addAction(setlistModeGridAct);
|
menuGame_List_Mode->addAction(setlistModeGridAct);
|
||||||
|
menuGame_List_Mode->addAction(setlistElfAct);
|
||||||
menuSettings->addAction(gameInstallPathAct);
|
menuSettings->addAction(gameInstallPathAct);
|
||||||
menuSettings->addAction(menuUtils->menuAction());
|
menuSettings->addAction(menuUtils->menuAction());
|
||||||
menuUtils->addAction(dumpGameListAct);
|
menuUtils->addAction(dumpGameListAct);
|
||||||
|
@ -274,12 +286,15 @@ public:
|
||||||
|
|
||||||
void retranslateUi(QMainWindow* MainWindow) {
|
void retranslateUi(QMainWindow* MainWindow) {
|
||||||
MainWindow->setWindowTitle(QCoreApplication::translate("MainWindow", "Shadps4", nullptr));
|
MainWindow->setWindowTitle(QCoreApplication::translate("MainWindow", "Shadps4", nullptr));
|
||||||
|
addElfFolderAct->setText(
|
||||||
|
QCoreApplication::translate("MainWindow", "Open/Add Elf Folder", nullptr));
|
||||||
bootInstallPkgAct->setText(
|
bootInstallPkgAct->setText(
|
||||||
QCoreApplication::translate("MainWindow", "Install Packages (PKG)", nullptr));
|
QCoreApplication::translate("MainWindow", "Install Packages (PKG)", nullptr));
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
bootInstallPkgAct->setToolTip(QCoreApplication::translate(
|
bootInstallPkgAct->setToolTip(QCoreApplication::translate(
|
||||||
"MainWindow", "Install application from a .pkg file", nullptr));
|
"MainWindow", "Install application from a .pkg file", nullptr));
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
menuRecent->setTitle(QCoreApplication::translate("MainWindow", "Recent Games", nullptr));
|
||||||
exitAct->setText(QCoreApplication::translate("MainWindow", "Exit", nullptr));
|
exitAct->setText(QCoreApplication::translate("MainWindow", "Exit", nullptr));
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
exitAct->setToolTip(QCoreApplication::translate("MainWindow", "Exit Shadps4", nullptr));
|
exitAct->setToolTip(QCoreApplication::translate("MainWindow", "Exit Shadps4", nullptr));
|
||||||
|
@ -300,6 +315,7 @@ public:
|
||||||
QCoreApplication::translate("MainWindow", "List View", nullptr));
|
QCoreApplication::translate("MainWindow", "List View", nullptr));
|
||||||
setlistModeGridAct->setText(
|
setlistModeGridAct->setText(
|
||||||
QCoreApplication::translate("MainWindow", "Grid View", nullptr));
|
QCoreApplication::translate("MainWindow", "Grid View", nullptr));
|
||||||
|
setlistElfAct->setText(QCoreApplication::translate("MainWindow", "Elf Viewer", nullptr));
|
||||||
gameInstallPathAct->setText(
|
gameInstallPathAct->setText(
|
||||||
QCoreApplication::translate("MainWindow", "Game Install Directory", nullptr));
|
QCoreApplication::translate("MainWindow", "Game Install Directory", nullptr));
|
||||||
dumpGameListAct->setText(
|
dumpGameListAct->setText(
|
||||||
|
@ -307,8 +323,6 @@ public:
|
||||||
pkgViewerAct->setText(QCoreApplication::translate("MainWindow", "PKG Viewer", nullptr));
|
pkgViewerAct->setText(QCoreApplication::translate("MainWindow", "PKG Viewer", nullptr));
|
||||||
mw_searchbar->setPlaceholderText(
|
mw_searchbar->setPlaceholderText(
|
||||||
QCoreApplication::translate("MainWindow", "Search...", nullptr));
|
QCoreApplication::translate("MainWindow", "Search...", nullptr));
|
||||||
// darkModeSwitch->setText(
|
|
||||||
// QCoreApplication::translate("MainWindow", "Game", nullptr));
|
|
||||||
menuFile->setTitle(QCoreApplication::translate("MainWindow", "File", nullptr));
|
menuFile->setTitle(QCoreApplication::translate("MainWindow", "File", nullptr));
|
||||||
menuView->setTitle(QCoreApplication::translate("MainWindow", "View", nullptr));
|
menuView->setTitle(QCoreApplication::translate("MainWindow", "View", nullptr));
|
||||||
menuGame_List_Icons->setTitle(
|
menuGame_List_Icons->setTitle(
|
||||||
|
|
|
@ -5,14 +5,16 @@
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include "pkg_viewer.h"
|
#include "pkg_viewer.h"
|
||||||
|
|
||||||
PKGViewer::PKGViewer(std::shared_ptr<GameInfoClass> game_info_get,
|
PKGViewer::PKGViewer(std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent,
|
||||||
std::shared_ptr<GuiSettings> m_gui_settings,
|
std::function<void(std::filesystem::path, int, int)> InstallDragDropPkg)
|
||||||
std::function<void(std::string, int, int)> InstallDragDropPkg)
|
: QMainWindow(), m_game_info(game_info_get) {
|
||||||
: QMainWindow() {
|
|
||||||
this->resize(1280, 720);
|
this->resize(1280, 720);
|
||||||
m_gui_settings_ = m_gui_settings;
|
this->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
m_game_info = game_info_get;
|
dir_list_std = Config::getPkgViewer();
|
||||||
dir_list = m_gui_settings->GetValue(gui::m_pkg_viewer).toStringList();
|
dir_list.clear();
|
||||||
|
for (const auto& str : dir_list_std) {
|
||||||
|
dir_list.append(QString::fromStdString(str));
|
||||||
|
}
|
||||||
statusBar = new QStatusBar(treeWidget);
|
statusBar = new QStatusBar(treeWidget);
|
||||||
this->setStatusBar(statusBar);
|
this->setStatusBar(statusBar);
|
||||||
treeWidget = new QTreeWidget(this);
|
treeWidget = new QTreeWidget(this);
|
||||||
|
@ -20,8 +22,8 @@ PKGViewer::PKGViewer(std::shared_ptr<GameInfoClass> game_info_get,
|
||||||
QStringList headers;
|
QStringList headers;
|
||||||
headers << "Name"
|
headers << "Name"
|
||||||
<< "Serial"
|
<< "Serial"
|
||||||
<< "Size"
|
|
||||||
<< "Installed"
|
<< "Installed"
|
||||||
|
<< "Size"
|
||||||
<< "Category"
|
<< "Category"
|
||||||
<< "Type"
|
<< "Type"
|
||||||
<< "App Ver"
|
<< "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,
|
m_gui_context_menus.RequestGameMenuPKGViewer(pos, m_full_pkg_list, treeWidget,
|
||||||
InstallDragDropPkg);
|
InstallDragDropPkg);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connect(parent, &QWidget::destroyed, this, [parent, this]() { this->deleteLater(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
PKGViewer::~PKGViewer() {}
|
PKGViewer::~PKGViewer() {}
|
||||||
|
@ -59,18 +63,21 @@ void PKGViewer::OpenPKGFolder() {
|
||||||
QFileDialog::getExistingDirectory(this, tr("Open Folder"), QDir::homePath());
|
QFileDialog::getExistingDirectory(this, tr("Open Folder"), QDir::homePath());
|
||||||
if (!dir_list.contains(folderPath)) {
|
if (!dir_list.contains(folderPath)) {
|
||||||
dir_list.append(folderPath);
|
dir_list.append(folderPath);
|
||||||
if (!folderPath.isEmpty()) {
|
QDir directory(folderPath);
|
||||||
for (const auto& dir : std::filesystem::directory_iterator(folderPath.toStdString())) {
|
QFileInfoList fileInfoList = directory.entryInfoList(QDir::Files);
|
||||||
QString file_ext =
|
for (const QFileInfo& fileInfo : fileInfoList) {
|
||||||
QString::fromStdString(dir.path().extension().string()).toLower();
|
QString file_ext = fileInfo.suffix();
|
||||||
if (std::filesystem::is_regular_file(dir.path()) && file_ext == ".pkg") {
|
if (fileInfo.isFile() && file_ext == "pkg") {
|
||||||
m_pkg_list.append(QString::fromStdString(dir.path().string()));
|
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 {
|
} else {
|
||||||
// qDebug() << "Folder selection canceled.";
|
// qDebug() << "Folder selection canceled.";
|
||||||
}
|
}
|
||||||
|
@ -78,11 +85,13 @@ void PKGViewer::OpenPKGFolder() {
|
||||||
|
|
||||||
void PKGViewer::CheckPKGFolders() { // Check for new PKG file additions.
|
void PKGViewer::CheckPKGFolders() { // Check for new PKG file additions.
|
||||||
m_pkg_list.clear();
|
m_pkg_list.clear();
|
||||||
for (const QString& paths : dir_list) {
|
for (const QString& dir : dir_list) {
|
||||||
for (const auto& dir : std::filesystem::directory_iterator(paths.toStdString())) {
|
QDir directory(dir);
|
||||||
QString file_ext = QString::fromStdString(dir.path().extension().string()).toLower();
|
QFileInfoList fileInfoList = directory.entryInfoList(QDir::Files);
|
||||||
if (std::filesystem::is_regular_file(dir.path()) && file_ext == ".pkg") {
|
for (const QFileInfo& fileInfo : fileInfoList) {
|
||||||
m_pkg_list.append(QString::fromStdString(dir.path().string()));
|
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_pkg_patch_list.clear();
|
||||||
m_full_pkg_list.clear();
|
m_full_pkg_list.clear();
|
||||||
for (int i = 0; i < m_pkg_list.size(); i++) {
|
for (int i = 0; i < m_pkg_list.size(); i++) {
|
||||||
Common::FS::IOFile file(m_pkg_list[i].toStdString(), Common::FS::FileAccessMode::Read);
|
std::filesystem::path path(m_pkg_list[i].toStdString());
|
||||||
if (!file.IsOpen()) {
|
#ifdef _WIN32
|
||||||
// return false;
|
path = std::filesystem::path(m_pkg_list[i].toStdWString());
|
||||||
}
|
#endif
|
||||||
|
package.Open(path);
|
||||||
file.ReadRaw<u8>(&pkgheader, sizeof(PKGHeader));
|
psf.open("", package.sfo);
|
||||||
file.Seek(0);
|
QString title_name = QString::fromStdString(psf.GetString("TITLE"));
|
||||||
pkgSize = file.GetSize();
|
QString title_id = QString::fromStdString(psf.GetString("TITLE_ID"));
|
||||||
pkg.resize(pkgheader.pkg_promote_size);
|
QString app_type = game_list_util.GetAppType(psf.GetInteger("APP_TYPE"));
|
||||||
file.Read(pkg);
|
QString app_version = QString::fromStdString(psf.GetString("APP_VER"));
|
||||||
|
QString title_category = QString::fromStdString(psf.GetString("CATEGORY"));
|
||||||
u32 offset = pkgheader.pkg_table_entry_offset;
|
QString pkg_size = game_list_util.FormatSize(package.GetPkgHeader().pkg_size);
|
||||||
u32 n_files = pkgheader.pkg_table_entry_count;
|
pkg_content_flag = package.GetPkgHeader().pkg_content_flags;
|
||||||
|
|
||||||
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;
|
|
||||||
QString flagss = "";
|
QString flagss = "";
|
||||||
for (const auto& flag : flagNames) {
|
for (const auto& flag : package.flagNames) {
|
||||||
if (isFlagSet(pkg_content_flag, flag.first)) {
|
if (package.isFlagSet(pkg_content_flag, flag.first)) {
|
||||||
if (!flagss.isEmpty())
|
if (!flagss.isEmpty())
|
||||||
flagss.append(", ");
|
flagss += (", ");
|
||||||
flagss.append(QString::fromStdString(flag.second));
|
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 = QString::number(fw_int, 16);
|
||||||
QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.')
|
QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.')
|
||||||
: fw.left(3).insert(1, '.');
|
: fw.left(3).insert(1, '.');
|
||||||
fw_ = (fw_int == 0) ? "0.00" : fw_;
|
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 = "";
|
QString pkg_info = "";
|
||||||
if (title_category == "gd") {
|
if (title_category == "gd" && !flagss.contains("PATCH")) {
|
||||||
title_category = "App";
|
title_category = "App";
|
||||||
pkg_info = title_name + ";;" + title_id + ";;" + pkg_size + ";;" + title_category +
|
pkg_info = title_name + ";;" + title_id + ";;" + pkg_size + ";;" + title_category +
|
||||||
";;" + app_type + ";;" + app_version + ";;" + fw_ + ";;" +
|
";;" + 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);
|
m_pkg_app_list.append(pkg_info);
|
||||||
} else {
|
} else {
|
||||||
title_category = "Patch";
|
title_category = "Patch";
|
||||||
pkg_info = title_name + ";;" + title_id + ";;" + pkg_size + ";;" + title_category +
|
pkg_info = title_name + ";;" + title_id + ";;" + pkg_size + ";;" + title_category +
|
||||||
";;" + app_type + ";;" + app_version + ";;" + fw_ + ";;" +
|
";;" + 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);
|
m_pkg_patch_list.append(pkg_info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,7 +172,7 @@ void PKGViewer::ProcessPKGInfo() {
|
||||||
treeItem->setText(9, pkg_app_[8]);
|
treeItem->setText(9, pkg_app_[8]);
|
||||||
treeItem->setText(10, pkg_app_[9]);
|
treeItem->setText(10, pkg_app_[9]);
|
||||||
for (const GameInfo& info : m_game_info->m_games) { // Check if game is installed.
|
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->setText(2, QChar(0x2713));
|
||||||
treeItem->setTextAlignment(2, Qt::AlignCenter);
|
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) {
|
for (int column = 0; column < treeWidget->columnCount() - 2; ++column) {
|
||||||
// Resize the column to fit its contents
|
// Resize the column to fit its contents
|
||||||
|
@ -244,18 +211,4 @@ void PKGViewer::ProcessPKGInfo() {
|
||||||
int numPkgs = m_pkg_list.size();
|
int numPkgs = m_pkg_list.size();
|
||||||
QString statusMessage = QString::number(numPkgs) + " Package.";
|
QString statusMessage = QString::number(numPkgs) + " Package.";
|
||||||
statusBar->showMessage(statusMessage);
|
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;
|
|
||||||
}
|
}
|
|
@ -22,33 +22,29 @@
|
||||||
#include "game_info.h"
|
#include "game_info.h"
|
||||||
#include "game_list_utils.h"
|
#include "game_list_utils.h"
|
||||||
#include "gui_context_menus.h"
|
#include "gui_context_menus.h"
|
||||||
#include "gui_settings.h"
|
|
||||||
|
|
||||||
class PKGViewer : public QMainWindow {
|
class PKGViewer : public QMainWindow {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit PKGViewer(std::shared_ptr<GameInfoClass> game_info_get,
|
explicit PKGViewer(
|
||||||
std::shared_ptr<GuiSettings> m_gui_settings,
|
std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent,
|
||||||
std::function<void(std::string, int, int)> InstallDragDropPkg = nullptr);
|
std::function<void(std::filesystem::path, int, int)> InstallDragDropPkg = nullptr);
|
||||||
~PKGViewer();
|
~PKGViewer();
|
||||||
void OpenPKGFolder();
|
void OpenPKGFolder();
|
||||||
void CheckPKGFolders();
|
void CheckPKGFolders();
|
||||||
void ProcessPKGInfo();
|
void ProcessPKGInfo();
|
||||||
QString GetString(const std::string& key);
|
|
||||||
u32 GetInteger(const std::string& key);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GuiContextMenus m_gui_context_menus;
|
GuiContextMenus m_gui_context_menus;
|
||||||
PSF psf_;
|
PKG package;
|
||||||
|
PSF psf;
|
||||||
PKGHeader pkgheader;
|
PKGHeader pkgheader;
|
||||||
PKGEntry entry;
|
PKGEntry entry;
|
||||||
PSFHeader header;
|
PSFHeader header;
|
||||||
PSFEntry psfentry;
|
PSFEntry psfentry;
|
||||||
char pkgTitleID[9];
|
char pkgTitleID[9];
|
||||||
std::vector<u8> pkg;
|
std::vector<u8> pkg;
|
||||||
std::vector<u8> psf;
|
|
||||||
u64 pkgSize = 0;
|
u64 pkgSize = 0;
|
||||||
std::shared_ptr<GuiSettings> m_gui_settings_;
|
|
||||||
std::unordered_map<std::string, std::string> map_strings;
|
std::unordered_map<std::string, std::string> map_strings;
|
||||||
std::unordered_map<std::string, u32> map_integers;
|
std::unordered_map<std::string, u32> map_integers;
|
||||||
|
|
||||||
|
@ -58,18 +54,6 @@ private:
|
||||||
// Status bar
|
// Status bar
|
||||||
QStatusBar* statusBar;
|
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 = {
|
std::vector<std::pair<int, QString>> appTypes = {
|
||||||
{0, "FULL APP"},
|
{0, "FULL APP"},
|
||||||
{1, "UPGRADABLE"},
|
{1, "UPGRADABLE"},
|
||||||
|
@ -77,47 +61,11 @@ private:
|
||||||
{3, "FREEMIUM"},
|
{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_full_pkg_list;
|
||||||
QStringList m_pkg_app_list;
|
QStringList m_pkg_app_list;
|
||||||
QStringList m_pkg_patch_list;
|
QStringList m_pkg_patch_list;
|
||||||
QStringList m_pkg_list;
|
QStringList m_pkg_list;
|
||||||
QStringList dir_list;
|
QStringList dir_list;
|
||||||
|
std::vector<std::string> dir_list_std;
|
||||||
QTreeWidget* treeWidget = nullptr;
|
QTreeWidget* treeWidget = nullptr;
|
||||||
};
|
};
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
};
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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";
|
||||||
|
}
|
||||||
|
};
|
|
@ -15,5 +15,11 @@
|
||||||
<file>images/controller_icon.png</file>
|
<file>images/controller_icon.png</file>
|
||||||
<file>images/refresh_icon.png</file>
|
<file>images/refresh_icon.png</file>
|
||||||
<file>images/list_mode_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>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
Loading…
Reference in New Issue