diff --git a/rpcs3/rpcs3qt/game_list_actions.cpp b/rpcs3/rpcs3qt/game_list_actions.cpp index ad453dc079..5ff938e990 100644 --- a/rpcs3/rpcs3qt/game_list_actions.cpp +++ b/rpcs3/rpcs3qt/game_list_actions.cpp @@ -846,16 +846,19 @@ bool game_list_actions::RemoveContentList(const std::string& serial, bool is_int RemoveContentBySerial(rpcs3::utils::get_icons_dir(), serial, "icons"); } - // Remove shortcuts in "games/shortcuts" folder and from desktop / start menu (if any) + // Remove shortcuts in "games/shortcuts" folder and from desktop / start menu / Steam (if any) if (content_types & SHORTCUTS) { if (const auto it = m_content_info.name_list.find(serial); it != m_content_info.name_list.cend()) { + std::vector> games; for (const std::string& name : it->second) { - // Remove all shortcuts - gui::utils::remove_shortcuts(name, serial); + games.push_back(std::pair(name, serial)); } + + // Batch remove all shortcuts + gui::utils::batch_remove_shortcuts(games); } } @@ -1452,15 +1455,7 @@ void game_list_actions::CreateShortcuts(const std::vector& games, con return; } - bool success = true; - - for (const game_info& gameinfo : games) - { - if (!gui::utils::create_shortcuts(gameinfo, locations)) - { - success = false; - } - } + const bool success = gui::utils::batch_create_shortcuts(games, locations); #ifdef _WIN32 if (locations.size() == 1 && locations.contains(gui::utils::shortcut_location::rpcs3_shortcuts)) diff --git a/rpcs3/rpcs3qt/shortcut_utils.cpp b/rpcs3/rpcs3qt/shortcut_utils.cpp index 46ceaa003e..d7b5586e75 100644 --- a/rpcs3/rpcs3qt/shortcut_utils.cpp +++ b/rpcs3/rpcs3qt/shortcut_utils.cpp @@ -81,7 +81,7 @@ namespace gui::utils return false; } - if (!gui::utils::create_square_pixmap(icon, size)) + if (!create_square_pixmap(icon, size)) { sys_log.error("Failed to create shortcut. Icon empty."); return false; @@ -109,6 +109,12 @@ namespace gui::utils return true; } + static std::string make_simple_name(const std::string& name) + { + const std::string simple_name = QString::fromStdString(vfs::escape(name, true)).simplified().toStdString(); + return simple_name; + } + bool create_shortcut(const std::string& name, const std::string& path, [[maybe_unused]] const std::string& serial, @@ -118,6 +124,7 @@ namespace gui::utils [[maybe_unused]] const std::string& target_icon_dir, const std::string& src_banner_path, shortcut_location location, + std::shared_ptr steam_sc, std::shared_ptr archive) { if (name.empty()) @@ -127,7 +134,7 @@ namespace gui::utils } // Remove illegal characters from filename - const std::string simple_name = QString::fromStdString(vfs::escape(name, true)).simplified().toStdString(); + const std::string simple_name = make_simple_name(name); if (simple_name.empty() || simple_name == "." || simple_name == "..") { sys_log.error("Failed to create shortcut: Cleaned file name empty or not allowed"); @@ -194,9 +201,8 @@ namespace gui::utils if (location == shortcut_location::steam) { sys_log.notice("Creating %s shortcut with arguments '%s'", location, target_cli_args); - steam_shortcut steam_sc{}; - steam_sc.add_shortcut(simple_name, rpcs3_path, working_dir, target_cli_args, target_icon_path, src_icon_path, src_banner_path, archive); - return steam_sc.write_file(); + ensure(steam_sc)->add_shortcut(simple_name, rpcs3_path, working_dir, target_cli_args, target_icon_path, src_icon_path, src_banner_path, archive); + return true; } sys_log.notice("Creating %s shortcut '%s' with arguments '%s' and .ico dir '%s'", location, link_path, target_cli_args, target_icon_dir); @@ -381,9 +387,8 @@ namespace gui::utils if (location == shortcut_location::steam) { - steam_shortcut steam_sc{}; - steam_sc.add_shortcut(simple_name, link_path, macos_dir, ""/*target_cli_args are already in the launcher*/, "", src_icon_path, src_banner_path, archive); - return steam_sc.write_file(); + ensure(steam_sc)->add_shortcut(simple_name, link_path, macos_dir, ""/*target_cli_args are already in the launcher*/, "", src_icon_path, src_banner_path, archive); + return true; } return true; @@ -410,9 +415,8 @@ namespace gui::utils { sys_log.notice("Creating %s shortcut with arguments '%s'", location, target_cli_args); const std::string working_dir{fs::get_executable_dir()}; - steam_shortcut steam_sc{}; - steam_sc.add_shortcut(simple_name, exe_path, working_dir, target_cli_args, target_icon_path, src_icon_path, src_banner_path, archive); - return steam_sc.write_file(); + ensure(steam_sc)->add_shortcut(simple_name, exe_path, working_dir, target_cli_args, target_icon_path, src_icon_path, src_banner_path, archive); + return true; } fmt::append(link_path, "/%s.desktop", simple_name); @@ -466,7 +470,8 @@ namespace gui::utils } bool create_shortcuts(const std::shared_ptr& game, - const std::set& locations) + const std::set& locations, + std::shared_ptr steam_sc) { if (!game || locations.empty()) return false; @@ -530,11 +535,11 @@ namespace gui::utils const std::string cli_arg_token = is_vsh ? "RPCS3_VFS" : "RPCS3_GAMEID"; const std::string cli_arg_value = is_vsh ? ("dev_flash/" + game->info.path.substr(dev_flash.size())) : gameid_token_value; - for (gui::utils::shortcut_location location : locations) + for (shortcut_location location : locations) { std::string banner_path; - if (location == gui::utils::shortcut_location::steam) + if (location == shortcut_location::steam) { // Try to find a nice banner for steam const std::string sfo_dir = is_iso ? "PS3_GAME" : rpcs3::utils::get_sfo_dir_from_game_path(game->info.path); @@ -550,19 +555,27 @@ namespace gui::utils } #ifdef __linux__ - const std::string percent = location == gui::utils::shortcut_location::steam ? "%" : "%%"; + const std::string percent = location == shortcut_location::steam ? "%" : "%%"; #else const std::string percent = "%"; #endif const std::string target_cli_args = fmt::format("--no-gui \"%s%s%s:%s\"", percent, cli_arg_token, percent, cli_arg_value); - if (!gameid_token_value.empty() && create_shortcut(game->info.name, game->icon_in_archive ? game->info.path : "", game->info.serial, target_cli_args, game->info.name, game->info.icon_path, target_icon_dir, banner_path, location, archive)) + if (!gameid_token_value.empty() && create_shortcut(game->info.name, game->icon_in_archive ? game->info.path : "", game->info.serial, target_cli_args, game->info.name, game->info.icon_path, target_icon_dir, banner_path, location, steam_sc, archive)) { - sys_log.success("Created %s shortcut for %s", location, QString::fromStdString(game->info.name).simplified()); + if (location == shortcut_location::steam) + { + // Creation is done in caller + sys_log.notice("Prepared %s shortcut for '%s'", location, QString::fromStdString(game->info.name).simplified()); + } + else + { + sys_log.success("Created %s shortcut for '%s'", location, QString::fromStdString(game->info.name).simplified()); + } } else { - sys_log.error("Failed to create %s shortcut for %s", location, QString::fromStdString(game->info.name).simplified()); + sys_log.error("Failed to create %s shortcut for '%s'", location, QString::fromStdString(game->info.name).simplified()); success = false; } } @@ -570,9 +583,36 @@ namespace gui::utils return success; } - void remove_shortcuts(const std::string& name, [[maybe_unused]] const std::string& serial) + bool batch_create_shortcuts(const std::vector>& games, + const std::set& locations) + { + if (games.empty() || locations.empty()) return false; + + std::shared_ptr steam_sc; + + if (locations.contains(shortcut_location::steam)) + { + // Batch steam shortcut creation + steam_sc = std::make_shared(); + } + + bool result = true; + + for (const auto& game : games) + { + result &= create_shortcuts(game, locations, steam_sc); + } + + if (steam_sc) + { + result &= steam_sc->write_file(); + } + + return result; + } + + static void remove_shortcuts(const std::string& simple_name, [[maybe_unused]] const std::string& serial) { - const std::string simple_name = QString::fromStdString(vfs::escape(name, true)).simplified().toStdString(); if (simple_name.empty() || simple_name == "." || simple_name == "..") { sys_log.error("Failed to remove shortcuts: Cleaned file name empty or not allowed"); @@ -611,7 +651,7 @@ namespace gui::utils std::vector locations = { shortcut_location::desktop, shortcut_location::applications, - shortcut_location::steam, + //shortcut_location::steam, // Handled separately }; #ifdef _WIN32 locations.push_back(shortcut_location::rpcs3_shortcuts); @@ -631,18 +671,8 @@ namespace gui::utils link_path += "/RPCS3"; break; case shortcut_location::steam: - { - const std::string exe_path = fs::get_executable_path(); - const std::string working_dir = fs::get_executable_dir(); - steam_shortcut steam_sc{}; - steam_sc.remove_shortcut(simple_name, exe_path, working_dir); - if (!steam_sc.write_file()) - { - sys_log.error("Failed to remove steam shortcut for '%s'", simple_name); - } - - continue; - } + fmt::throw_exception("Steam shortcuts should be removed in batch"); + break; #ifdef _WIN32 case shortcut_location::rpcs3_shortcuts: link_path = rpcs3::utils::get_games_shortcuts_dir(); @@ -665,4 +695,32 @@ namespace gui::utils const std::string icon_path = fmt::format("%sIcons/game_icons/%s/shortcut.%s", fs::get_config_dir(), serial, icon_extension); remove_path(icon_path, true); } + + void batch_remove_shortcuts(const std::vector>& games) + { + if (games.empty()) return; + + // Batch steam shortcut removal + const std::string exe_path = fs::get_executable_path(); + const std::string working_dir = fs::get_executable_dir(); + const bool is_steam_installed = steam_shortcut::steam_installed(); + steam_shortcut steam_sc{}; + + for (const auto& [name, serial] : games) + { + const std::string simple_name = make_simple_name(name); + + remove_shortcuts(simple_name, serial); + + if (is_steam_installed) + { + steam_sc.remove_shortcut(simple_name, exe_path, working_dir); + } + } + + if (is_steam_installed && !steam_sc.write_file()) + { + sys_log.error("Failed to remove steam shortcuts"); + } + } } diff --git a/rpcs3/rpcs3qt/shortcut_utils.h b/rpcs3/rpcs3qt/shortcut_utils.h index aec921dadd..d1ed3962e2 100644 --- a/rpcs3/rpcs3qt/shortcut_utils.h +++ b/rpcs3/rpcs3qt/shortcut_utils.h @@ -5,6 +5,8 @@ class iso_archive; namespace gui::utils { + class steam_shortcut; + enum class shortcut_location { desktop, @@ -24,10 +26,11 @@ namespace gui::utils const std::string& target_icon_dir, const std::string& src_banner_path, shortcut_location shortcut_location, + std::shared_ptr steam_sc = nullptr, std::shared_ptr archive = nullptr); - bool create_shortcuts(const std::shared_ptr& game, - const std::set& locations); + bool batch_create_shortcuts(const std::vector>& games, + const std::set& locations); - void remove_shortcuts(const std::string& name, const std::string& serial); + void batch_remove_shortcuts(const std::vector>& games); } diff --git a/rpcs3/rpcs3qt/steam_utils.cpp b/rpcs3/rpcs3qt/steam_utils.cpp index 915cf69915..2642e25b37 100644 --- a/rpcs3/rpcs3qt/steam_utils.cpp +++ b/rpcs3/rpcs3qt/steam_utils.cpp @@ -346,7 +346,9 @@ namespace gui::utils const auto launch_options = entry.value("LaunchOptions"); const auto icon = entry.value("icon"); - if (appid.has_value() && appid.value() == it->appid && + const bool appid_matches = appid.has_value() && appid.value() == it->appid; + + if (appid_matches && exe.has_value() && exe.value() == it->exe && start_dir.has_value() && start_dir.value() == it->start_dir && launch_options.has_value() && launch_options.value() == it->launch_options && @@ -357,6 +359,11 @@ namespace gui::utils } else { + if (appid_matches) + { + sys_log.notice("Entry '%s' already exists in steam shortcut file '%s' but with other parameters. Creating an additional one...", it->app_name, file_path); + } + it++; } } @@ -369,7 +376,7 @@ namespace gui::utils if (m_entries_to_add.empty() && removed_entries.empty()) { - sys_log.notice("No matching entries found in steam shortcut file '%s'.", file_path); + sys_log.notice("Nothing to add or remove in steam shortcut file '%s'.", file_path); return true; }