diff --git a/rpcs3/Crypto/unpkg.cpp b/rpcs3/Crypto/unpkg.cpp index 8571080765..37a4bdafd0 100644 --- a/rpcs3/Crypto/unpkg.cpp +++ b/rpcs3/Crypto/unpkg.cpp @@ -858,6 +858,12 @@ bool package_reader::extract_data(atomic_t& sync) else { pkg_log.notice("Created file %s", path); + + if (name == "USRDIR/EBOOT.BIN" && entry.file_size > 4) + { + // Expose the creation of a bootable file + m_bootable_file_path = path; + } } } else diff --git a/rpcs3/Crypto/unpkg.h b/rpcs3/Crypto/unpkg.h index 8b351d479b..bc27658b71 100644 --- a/rpcs3/Crypto/unpkg.h +++ b/rpcs3/Crypto/unpkg.h @@ -312,6 +312,11 @@ public: bool extract_data(atomic_t& sync); psf::registry get_psf() const { return m_psf; } + std::string try_get_bootable_file_path_if_created_new() const + { + return m_bootable_file_path; + } + private: bool read_header(); bool read_metadata(); @@ -334,4 +339,7 @@ private: PKGHeader m_header{}; PKGMetaData m_metadata{}; psf::registry m_psf{}; + + // Expose bootable file installed (if installed such) + std::string m_bootable_file_path; }; diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 330ac3bbc4..dd1da429ea 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -1021,7 +1021,7 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) const std::string target_cli_args = fmt::format("--no-gui \"%s\"", gameinfo->info.path); const std::string target_icon_dir = fmt::format("%sIcons/game_icons/%s/", fs::get_config_dir(), gameinfo->info.serial); - if (gui::utils::create_shortcut(gameinfo->info.name, target_cli_args, gameinfo->info.name, gameinfo->info.icon_path, target_icon_dir, is_desktop_shortcut)) + if (gui::utils::create_shortcut(gameinfo->info.name, target_cli_args, gameinfo->info.name, gameinfo->info.icon_path, target_icon_dir, is_desktop_shortcut ? gui::utils::shortcut_location::desktop : gui::utils::shortcut_location::rpcs3_shortcuts)) { game_list_log.success("Created %s shortcut for %s", is_desktop_shortcut ? "desktop" : "application menu", sstr(qstr(gameinfo->info.name).simplified())); QMessageBox::information(this, tr("Success!"), tr("Successfully created a shortcut.")); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 32ace88724..a5bed6163d 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -30,6 +30,7 @@ #include "input_dialog.h" #include "camera_settings_dialog.h" #include "ipc_settings_dialog.h" +#include "shortcut_utils.h" #include #include @@ -40,6 +41,8 @@ #include #include #include +#include +#include #include "rpcs3_version.h" #include "Emu/IdManager.h" @@ -846,6 +849,7 @@ void main_window::HandlePackageInstallation(QStringList file_paths) package_error error = package_error::no_error; bool cancelled = false; + std::map bootable_paths_installed; // -> title for (usz i = 0, count = packages.size(); i < count; i++) { @@ -882,15 +886,21 @@ void main_window::HandlePackageInstallation(QStringList file_paths) const std::string path = sstr(package.path); const std::string file_name = sstr(file_info.fileName()); + std::string bootable_path; + // Run PKG unpacking asynchronously - named_thread worker("PKG Installer", [path, &progress, &error] + named_thread worker("PKG Installer", [path, &progress, &error, &bootable_path] { package_reader reader(path); error = reader.check_target_app_version(); if (error == package_error::no_error) { - return reader.extract_data(progress); + if (reader.extract_data(progress)) + { + bootable_path = reader.try_get_bootable_file_path_if_created_new(); + return true; + } } return false; @@ -922,7 +932,7 @@ void main_window::HandlePackageInstallation(QStringList file_paths) } else { - pdlg.setHidden(true); + pdlg.hide(); pdlg.SignalFailure(); } @@ -931,9 +941,132 @@ void main_window::HandlePackageInstallation(QStringList file_paths) m_game_list_frame->Refresh(true); gui_log.success("Successfully installed %s (title_id=%s, title=%s, version=%s).", file_name, sstr(package.title_id), sstr(package.title), sstr(package.version)); + if (!bootable_path.empty()) + { + bootable_paths_installed[bootable_path] = package.title; + } + if (i == (count - 1)) { - m_gui_settings->ShowInfoBox(tr("Success!"), tr("Successfully installed software from package(s)!"), gui::ib_pkg_success, this); + pdlg.hide(); + + bool create_desktop_shortcuts = false; + bool create_app_shortcut = false; + + if (bootable_paths_installed.empty()) + { + m_gui_settings->ShowInfoBox(tr("Success!"), tr("Successfully installed software from package(s)!"), gui::ib_pkg_success, this); + } + else + { + auto dlg = new QDialog(this); + dlg->setWindowTitle(tr("Success!")); + + QVBoxLayout* vlayout = new QVBoxLayout(dlg); + + QCheckBox* desk_check = new QCheckBox(tr("Add desktop shortcut(s)")); +#ifdef _WIN32 + QCheckBox* quick_check = new QCheckBox(tr("Add Start menu shortcut(s)")); +#elif defined(__APPLE__) + QCheckBox* quick_check = new QCheckBox(tr("Add dock shortcut(s)")); +#else + QCheckBox* quick_check = new QCheckBox(tr("Add launcher shortcut(s)")); +#endif + QLabel* label = new QLabel(tr("Successfully installed software from package(s)!\nWould you like to install shortcuts to the installed software? (%1 new software detected)\n\n").arg(bootable_paths_installed.size()), dlg); + + vlayout->addWidget(label); + vlayout->addStretch(10); + vlayout->addWidget(desk_check); + vlayout->addStretch(3); + vlayout->addWidget(quick_check); + vlayout->addStretch(3); + + QDialogButtonBox* btn_box = new QDialogButtonBox(QDialogButtonBox::Ok); + + vlayout->addWidget(btn_box); + dlg->setLayout(vlayout); + + connect(btn_box, &QDialogButtonBox::accepted, this, [&]() + { + create_desktop_shortcuts = desk_check->isChecked(); + create_app_shortcut = quick_check->isChecked(); + dlg->accept(); + }); + + dlg->setAttribute(Qt::WA_DeleteOnClose); + dlg->exec(); + } + + for (const auto& [boot_path, title] : bootable_paths_installed) + { + if (std::string game_dir = fs::get_parent_dir(boot_path, 2); fs::is_dir(game_dir) && fs::is_file(boot_path)) + { + const std::string target_cli_args = fmt::format("--no-gui \"%%RPCS3_GAMEID%%:%s\"", game_dir.substr(game_dir.find_last_of(fs::delim) + 1)); + const std::string std_title_id = sstr(package.title_id); + const std::string target_icon_dir = fmt::format("%sIcons/game_icons/%s/", fs::get_config_dir(), std_title_id); + + // Copy the icon used by rpcs3 to a file + QTemporaryFile tmp_file(QDir::tempPath() + "/tempFile"); + if (!tmp_file.open()) + { + gui_log.error("Failed to create icon for '%s'", sstr(title.simplified())); + continue; + } + + const QIcon icon = gui::utils::get_app_icon_from_path(rpcs3::utils::get_sfo_dir_from_game_path(boot_path + "/../../"), std_title_id); + QPixmap pix = icon.pixmap(icon.actualSize(QSize(1000, 1000))); + QByteArray bytes; + QBuffer buffer(&bytes); + buffer.open(QIODevice::ReadWrite); + pix.save(&buffer, "PNG"); + tmp_file.write(bytes.data(), bytes.size()); + + std::string icon_path = sstr(tmp_file.fileName()); +#ifdef _WIN32 + if (gui::utils::create_shortcut(sstr(title), target_cli_args, sstr(title), icon_path, target_icon_dir, gui::utils::shortcut_location::rpcs3_shortcuts)) + { + gui_log.success("Created a shortcut for '%s' at '%s/games/shortcuts/'", sstr(title.simplified()), fs::get_config_dir()); + } +#endif + + struct install_shortcut_info + { + std::string type; + gui::utils::shortcut_location location; + bool to_install; + }; + + std::initializer_list installing_locations = + { + {"desktop", gui::utils::shortcut_location::desktop, create_desktop_shortcuts}, +#ifdef _WIN32 + {"Start menu", gui::utils::shortcut_location::applications, create_app_shortcut}, +#elif defined(__APPLE__) + {"dock", gui::utils::shortcut_location::applications, create_app_shortcut}, +#else + {"launcher", gui::utils::shortcut_location::applications, create_app_shortcut}, +#endif + }; + + + for (const auto& loc : installing_locations) + { + if (!loc.to_install) + { + continue; + } + + if (gui::utils::create_shortcut(sstr(title), target_cli_args, sstr(title), icon_path, target_icon_dir, loc.location)) + { + gui_log.success("Created %s shortcut for %s", loc.type, sstr(title.simplified())); + } + else + { + gui_log.error("Failed to create %s shortcut for %s", loc.type, sstr(title.simplified())); + } + } + } + } } } else diff --git a/rpcs3/rpcs3qt/shortcut_utils.cpp b/rpcs3/rpcs3qt/shortcut_utils.cpp index d896d30aaf..d4c99d25da 100644 --- a/rpcs3/rpcs3qt/shortcut_utils.cpp +++ b/rpcs3/rpcs3qt/shortcut_utils.cpp @@ -66,7 +66,7 @@ namespace gui::utils [[maybe_unused]] const std::string& description, [[maybe_unused]] const std::string& src_icon_path, [[maybe_unused]] const std::string& target_icon_dir, - bool is_desktop_shortcut) + shortcut_location location) { if (name.empty()) { @@ -84,14 +84,21 @@ namespace gui::utils std::string link_path; - if (is_desktop_shortcut) + if (location == shortcut_location::desktop) { link_path = QStandardPaths::writableLocation(QStandardPaths::StandardLocation::DesktopLocation).toStdString(); } - else + else if (location == shortcut_location::applications) { link_path = QStandardPaths::writableLocation(QStandardPaths::StandardLocation::ApplicationsLocation).toStdString(); } +#ifdef _WIN32 + else if (location == shortcut_location::rpcs3_shortcuts) + { + link_path = fs::get_config_dir() + "/games/shortcuts/"; + fs::create_dir(link_path); + } +#endif if (!fs::is_dir(link_path)) { @@ -99,7 +106,7 @@ namespace gui::utils return false; } - if (!is_desktop_shortcut) + if (location == shortcut_location::applications) { link_path += "/RPCS3"; @@ -197,7 +204,7 @@ namespace gui::utils res = pPersistFile->Save(w_link_file.c_str(), TRUE); if (FAILED(res)) { - if (is_desktop_shortcut) + if (location == shortcut_location::desktop) { return cleanup(false, fmt::format("Saving file to desktop failed (%s)", str_error(res))); } @@ -348,7 +355,7 @@ namespace gui::utils } shortcut_file.close(); - if (is_desktop_shortcut) + if (location == shortcut_location::desktop) { if (chmod(link_path.c_str(), S_IRWXU) != 0) // enables user to execute file { diff --git a/rpcs3/rpcs3qt/shortcut_utils.h b/rpcs3/rpcs3qt/shortcut_utils.h index ea8fad8def..eff4cdb32a 100644 --- a/rpcs3/rpcs3qt/shortcut_utils.h +++ b/rpcs3/rpcs3qt/shortcut_utils.h @@ -2,10 +2,19 @@ namespace gui::utils { + enum shortcut_location + { + desktop, + applications, +#ifdef _WIN32 + rpcs3_shortcuts, +#endif + }; + bool create_shortcut(const std::string& name, const std::string& target_cli_args, const std::string& description, const std::string& src_icon_path, const std::string& target_icon_dir, - bool is_desktop_shortcut); + shortcut_location shortcut_location); }