diff --git a/rpcs3/Emu/Cell/lv2/sys_process.cpp b/rpcs3/Emu/Cell/lv2/sys_process.cpp index 519e57ee2e..ad89d76d61 100644 --- a/rpcs3/Emu/Cell/lv2/sys_process.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_process.cpp @@ -1,4 +1,4 @@ -#include "stdafx.h" +#include "stdafx.h" #include "Emu/Memory/vm.h" #include "Emu/System.h" #include "Emu/IdManager.h" @@ -314,7 +314,7 @@ void _sys_process_exit2(ppu_thread& ppu, s32 status, vm::ptr ar } Emu.SetForceBoot(true); - Emu.BootGame(path, true); + Emu.BootGame(path, "", true); }); ppu.state += cpu_flag::dbg_global_stop; diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 638e243540..ba48fecf1b 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include "Utilities/GDBDebugServer.h" @@ -592,18 +593,18 @@ void Emulator::LimitCacheSize() LOG_SUCCESS(GENERAL, "Cleaned disk cache, removed %.2f MB", size / 1024.0 / 1024.0); } -bool Emulator::BootGame(const std::string& path, bool direct, bool add_only, bool force_global_config) +bool Emulator::BootGame(const std::string& path, const std::string& title_id, bool direct, bool add_only, bool force_global_config) { if (g_cfg.vfs.limit_cache_size) LimitCacheSize(); static const char* boot_list[] = { - "/PS3_GAME/USRDIR/EBOOT.BIN", - "/USRDIR/EBOOT.BIN", - "/EBOOT.BIN", "/eboot.bin", + "/EBOOT.BIN", + "/USRDIR/EBOOT.BIN", "/USRDIR/ISO.BIN.EDAT", + "/PS3_GAME/USRDIR/EBOOT.BIN", }; m_path_old = m_path; @@ -611,10 +612,11 @@ bool Emulator::BootGame(const std::string& path, bool direct, bool add_only, boo if (direct && fs::exists(path)) { m_path = path; - Load(add_only, force_global_config); + Load(title_id, add_only, force_global_config); return true; } + bool success = false; for (std::string elf : boot_list) { elf = path + elf; @@ -622,12 +624,36 @@ bool Emulator::BootGame(const std::string& path, bool direct, bool add_only, boo if (fs::is_file(elf)) { m_path = elf; - Load(add_only, force_global_config); - return true; + Load(title_id, add_only, force_global_config); + success = true; + break; } } - return false; + if (add_only) + { + for (auto&& entry : fs::dir{ path }) + { + if (entry.name == "." || entry.name == "..") + { + continue; + } + + if (entry.is_directory && std::regex_match(entry.name, std::regex("^PS3_GM[[:digit:]]{2}$"))) + { + const std::string elf = path + "/" + entry.name + "/USRDIR/EBOOT.BIN"; + + if (fs::is_file(elf)) + { + m_path = elf; + Load(title_id, add_only, force_global_config); + success = true; + } + } + } + } + + return success; } bool Emulator::InstallPkg(const std::string& path) @@ -679,11 +705,34 @@ std::string Emulator::GetHdd1Dir() return fmt::replace_all(g_cfg.vfs.dev_hdd1, "$(EmulatorDir)", GetEmuDir()); } -std::string Emulator::GetSfoDirFromGamePath(const std::string& game_path, const std::string& user) +std::string Emulator::GetSfoDirFromGamePath(const std::string& game_path, const std::string& user, const std::string& title_id) { if (fs::is_file(game_path + "/PS3_DISC.SFB")) { // This is a disc game. + if (!title_id.empty()) + { + for (auto&& entry : fs::dir{game_path}) + { + if (entry.name == "." || entry.name == "..") + { + continue; + } + + const std::string sfo_path = game_path + "/" + entry.name + "/PARAM.SFO"; + + if (entry.is_directory && fs::is_file(sfo_path)) + { + const auto psf = psf::load_object(fs::file(sfo_path)); + const std::string serial = get_string(psf, "TITLE_ID"); + if (serial == title_id) + { + return game_path + "/" + entry.name; + } + } + } + } + return game_path + "/PS3_GAME"; } @@ -747,13 +796,18 @@ void Emulator::SetForceBoot(bool force_boot) m_force_boot = force_boot; } -void Emulator::Load(bool add_only, bool force_global_config) +void Emulator::Load(const std::string& title_id, bool add_only, bool force_global_config) { if (!IsStopped()) { Stop(); } + if (!title_id.empty()) + { + m_title_id = title_id; + } + try { Init(); @@ -782,14 +836,14 @@ void Emulator::Load(bool add_only, bool force_global_config) if (fs::is_dir(m_path)) { // Special case (directory scan) - m_sfo_dir = GetSfoDirFromGamePath(m_path, GetUsr()); + m_sfo_dir = GetSfoDirFromGamePath(m_path, GetUsr(), m_title_id); } else if (disc.size()) { // Check previously used category before it's overwritten if (m_cat == "DG") { - m_sfo_dir = disc + "/PS3_GAME"; + m_sfo_dir = disc + "/" + m_game_dir; } else if (m_cat == "GD") { @@ -797,12 +851,12 @@ void Emulator::Load(bool add_only, bool force_global_config) } else { - m_sfo_dir = GetSfoDirFromGamePath(disc, GetUsr()); + m_sfo_dir = GetSfoDirFromGamePath(disc, GetUsr(), m_title_id); } } else { - m_sfo_dir = GetSfoDirFromGamePath(elf_dir + "/../", GetUsr()); + m_sfo_dir = GetSfoDirFromGamePath(elf_dir + "/../", GetUsr(), m_title_id); } _psf = psf::load_object(fs::file(m_sfo_dir + "/PARAM.SFO")); @@ -1019,7 +1073,8 @@ void Emulator::Load(bool add_only, bool force_global_config) // Detect boot location const std::string hdd0_game = vfs::get("/dev_hdd0/game/"); const std::string hdd0_disc = vfs::get("/dev_hdd0/disc/"); - const std::size_t bdvd_pos = m_cat == "DG" && bdvd_dir.empty() && disc.empty() ? elf_dir.rfind("/PS3_GAME/") + 1 : 0; + const std::size_t game_dir_size = 8; // size of PS3_GAME and PS3_GMXX + const std::size_t bdvd_pos = m_cat == "DG" && bdvd_dir.empty() && disc.empty() ? elf_dir.rfind("/USRDIR") - game_dir_size : 0; const bool from_hdd0_game = m_path.find(hdd0_game) != -1; if (bdvd_pos && from_hdd0_game) @@ -1045,6 +1100,11 @@ void Emulator::Load(bool add_only, bool force_global_config) { // Mount /dev_bdvd/ if necessary bdvd_dir = elf_dir.substr(0, bdvd_pos); + m_game_dir = elf_dir.substr(bdvd_pos, game_dir_size); + } + else + { + m_game_dir = "PS3_GAME"; // reset } // Booting patch data @@ -1069,6 +1129,9 @@ void Emulator::Load(bool add_only, bool force_global_config) vfs::mount("/dev_bdvd", bdvd_dir); LOG_NOTICE(LOADER, "Disc: %s", vfs::get("/dev_bdvd")); + vfs::mount("/dev_bdvd/PS3_GAME", bdvd_dir + m_game_dir + "/"); + LOG_NOTICE(LOADER, "Game: %s", vfs::get("/dev_bdvd/PS3_GAME")); + if (!sfb_file.open(vfs::get("/dev_bdvd/PS3_DISC.SFB")) || sfb_file.size() < 4 || sfb_file.read() != ".SFB"_u32) { LOG_ERROR(LOADER, "Invalid disc directory for the disc game %s", m_title_id); @@ -1167,9 +1230,10 @@ void Emulator::Load(bool add_only, bool force_global_config) for (auto&& entry : fs::dir{ins_dir}) { - if (!entry.is_directory && ends_with(entry.name, ".PKG") && !InstallPkg(ins_dir + entry.name)) + const std::string pkg = ins_dir + entry.name; + if (!entry.is_directory && ends_with(entry.name, ".PKG") && !InstallPkg(pkg)) { - LOG_ERROR(LOADER, "Failed to install /dev_bdvd/PS3_GAME/INSDIR/%s", entry.name); + LOG_ERROR(LOADER, "Failed to install %s", pkg); return; } } @@ -1187,7 +1251,7 @@ void Emulator::Load(bool add_only, bool force_global_config) if (fs::is_file(pkg_file) && !InstallPkg(pkg_file)) { - LOG_ERROR(LOADER, "Failed to install /dev_bdvd/PS3_GAME/PKGDIR/%s/INSTALL.PKG", entry.name); + LOG_ERROR(LOADER, "Failed to install %s", pkg_file); return; } } @@ -1206,7 +1270,7 @@ void Emulator::Load(bool add_only, bool force_global_config) if (fs::is_file(pkg_file) && !InstallPkg(pkg_file)) { - LOG_ERROR(LOADER, "Failed to install /dev_bdvd/PS3_GAME/PKGDIR/%s/DATA000.PKG", entry.name); + LOG_ERROR(LOADER, "Failed to install %s", pkg_file); return; } } @@ -1319,8 +1383,8 @@ void Emulator::Load(bool add_only, bool force_global_config) else if (!bdvd_dir.empty() && fs::is_dir(bdvd_dir)) { // Disc games are on /dev_bdvd/ - const std::size_t pos = m_path.rfind("PS3_GAME"); - argv[0] = "/dev_bdvd/" + m_path.substr(pos); + const std::size_t pos = m_path.rfind(m_game_dir); + argv[0] = "/dev_bdvd/PS3_GAME/" + m_path.substr(pos + game_dir_size + 1); m_dir = "/dev_bdvd/PS3_GAME/"; } else diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 7d5dc531aa..8ae7c33f05 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -220,6 +220,7 @@ class Emulator final std::string m_cat; std::string m_dir; std::string m_sfo_dir; + std::string m_game_dir{"PS3_GAME"}; std::string m_usr{"00000001"}; u32 m_usrid{1}; @@ -311,7 +312,7 @@ public: std::string PPUCache() const; - bool BootGame(const std::string& path, bool direct = false, bool add_only = false, bool force_global_config = false); + bool BootGame(const std::string& path, const std::string& title_id = "", bool direct = false, bool add_only = false, bool force_global_config = false); bool BootRsxCapture(const std::string& path); bool InstallPkg(const std::string& path); @@ -322,7 +323,7 @@ private: void LimitCacheSize(); public: static std::string GetHddDir(); - static std::string GetSfoDirFromGamePath(const std::string& game_path, const std::string& user); + static std::string GetSfoDirFromGamePath(const std::string& game_path, const std::string& user, const std::string& title_id = ""); static std::string GetCustomConfigDir(); static std::string GetCustomConfigPath(const std::string& title_id, bool get_deprecated_path = false); @@ -331,7 +332,7 @@ public: void SetForceBoot(bool force_boot); - void Load(bool add_only = false, bool force_global_config = false); + void Load(const std::string& title_id = "", bool add_only = false, bool force_global_config = false); void Run(); bool Pause(); void Resume(); diff --git a/rpcs3/main.cpp b/rpcs3/main.cpp index 961fa9ab83..78b6b3168c 100644 --- a/rpcs3/main.cpp +++ b/rpcs3/main.cpp @@ -1,4 +1,4 @@ -// Qt5.2+ frontend implementation for rpcs3. Known to work on Windows, Linux, Mac +// Qt5.2+ frontend implementation for rpcs3. Known to work on Windows, Linux, Mac // by Sacha Refshauge, Megamouse and flash-fire #include @@ -160,7 +160,7 @@ int main(int argc, char** argv) { Emu.argv = std::move(argv); Emu.SetForceBoot(true); - Emu.BootGame(path, true); + Emu.BootGame(path, "", true); }); } diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 7779254a50..29837dafbd 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -386,13 +387,40 @@ void game_list_frame::Refresh(const bool fromDrive, const bool scrollAfter) std::vector path_list; + const auto add_disc_dir = [&](const std::string& path) + { + for (const auto& entry : fs::dir(path)) + { + if (!entry.is_directory || entry.name == "." || entry.name == "..") + { + continue; + } + + if (entry.name == "PS3_GAME" || std::regex_match(entry.name, std::regex("^PS3_GM[[:digit:]]{2}$"))) + { + path_list.emplace_back(path + "/" + entry.name); + } + } + }; + const auto add_dir = [&](const std::string& path) { for (const auto& entry : fs::dir(path)) { - if (entry.is_directory) + if (entry.name == "." || entry.name == "..") { - path_list.emplace_back(path + entry.name); + continue; + } + + const std::string entry_path = path + entry.name; + + if (fs::is_file(entry_path + "/PS3_DISC.SFB")) + { + add_disc_dir(entry_path); + } + else if (entry.is_directory) + { + path_list.emplace_back(entry_path); } } }; @@ -402,8 +430,17 @@ void game_list_frame::Refresh(const bool fromDrive, const bool scrollAfter) for (auto pair : YAML::Load(fs::file{fs::get_config_dir() + "/games.yml", fs::read + fs::create}.to_string())) { - path_list.push_back(pair.second.Scalar()); - path_list.back().resize(path_list.back().find_last_not_of('/') + 1); + const std::string game_dir = pair.second.Scalar(); + + if (fs::is_file(game_dir + "/PS3_DISC.SFB")) + { + add_disc_dir(game_dir); + } + else + { + path_list.push_back(game_dir); + path_list.back().resize(path_list.back().find_last_not_of('/') + 1); + } } // Used to remove duplications from the list (serial -> set of cat names) @@ -648,7 +685,7 @@ void game_list_frame::doubleClickedSlot(QTableWidgetItem *item) } LOG_NOTICE(LOADER, "Booting from gamelist per doubleclick..."); - Q_EMIT RequestBoot(game->info.path); + Q_EMIT RequestBoot(game); } void game_list_frame::ShowContextMenu(const QPoint &pos) @@ -693,7 +730,7 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) connect(boot_custom, &QAction::triggered, [=] { LOG_NOTICE(LOADER, "Booting from gamelist per context menu..."); - Q_EMIT RequestBoot(currGame.path); + Q_EMIT RequestBoot(gameinfo); }); } else @@ -787,7 +824,7 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) connect(boot, &QAction::triggered, [=] { LOG_NOTICE(LOADER, "Booting from gamelist per context menu..."); - Q_EMIT RequestBoot(currGame.path, gameinfo->hasCustomConfig); + Q_EMIT RequestBoot(gameinfo, gameinfo->hasCustomConfig); }); connect(configure, &QAction::triggered, [=] { @@ -835,7 +872,7 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) }); connect(createPPUCache, &QAction::triggered, [=] { - CreatePPUCache(currGame.path); + CreatePPUCache(gameinfo); }); connect(removeGame, &QAction::triggered, [=] { @@ -961,20 +998,20 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) myMenu.exec(globalPos); } -bool game_list_frame::CreatePPUCache(const std::string& path) +bool game_list_frame::CreatePPUCache(const game_info& game) { Emu.SetForceBoot(true); Emu.Stop(); Emu.SetForceBoot(true); - const bool success = Emu.BootGame(path, true); + const bool success = Emu.BootGame(game->info.path, game->info.serial, true); if (success) { - LOG_WARNING(GENERAL, "Creating PPU Cache for %s", path); + LOG_WARNING(GENERAL, "Creating PPU Cache for %s", game->info.path); } else { - LOG_ERROR(GENERAL, "Could not create PPU Cache for %s", path); + LOG_ERROR(GENERAL, "Could not create PPU Cache for %s", game->info.path); } return success; } @@ -1171,12 +1208,7 @@ bool game_list_frame::RemoveSPUCache(const std::string& base_dir, bool is_intera void game_list_frame::BatchCreatePPUCaches() { - std::set paths; - for (const auto& game : m_game_data) - { - paths.emplace(game->info.path); - } - const u32 total = paths.size(); + const u32 total = m_game_data.size(); if (total == 0) { @@ -1191,7 +1223,7 @@ void game_list_frame::BatchCreatePPUCaches() pdlg->show(); u32 created = 0; - for (const auto& path : paths) + for (const auto& game : m_game_data) { if (pdlg->wasCanceled()) { @@ -1200,7 +1232,7 @@ void game_list_frame::BatchCreatePPUCaches() } QApplication::processEvents(); - if (CreatePPUCache(path)) + if (CreatePPUCache(game)) { while (!Emu.IsStopped()) { @@ -1635,7 +1667,7 @@ bool game_list_frame::eventFilter(QObject *object, QEvent *event) return false; LOG_NOTICE(LOADER, "Booting from gamelist by pressing %s...", keyEvent->key() == Qt::Key_Enter ? "Enter" : "Return"); - Q_EMIT RequestBoot(gameinfo->info.path); + Q_EMIT RequestBoot(gameinfo); return true; } diff --git a/rpcs3/rpcs3qt/game_list_frame.h b/rpcs3/rpcs3qt/game_list_frame.h index 00a58bccd5..bb2be2178f 100644 --- a/rpcs3/rpcs3qt/game_list_frame.h +++ b/rpcs3/rpcs3qt/game_list_frame.h @@ -228,7 +228,7 @@ private Q_SLOTS: void doubleClickedSlot(QTableWidgetItem *item); Q_SIGNALS: void GameListFrameClosed(); - void RequestBoot(const std::string& path, bool force_global_config = false); + void RequestBoot(const game_info& game, bool force_global_config = false); void RequestIconSizeChange(const int& val); protected: /** Override inherited method from Qt to allow signalling when close happened.*/ @@ -251,7 +251,7 @@ private: bool RemoveShadersCache(const std::string& base_dir, bool is_interactive = false); bool RemovePPUCache(const std::string& base_dir, bool is_interactive = false); bool RemoveSPUCache(const std::string& base_dir, bool is_interactive = false); - bool CreatePPUCache(const std::string& path); + bool CreatePPUCache(const game_info& game); std::string GetCacheDirBySerial(const std::string& serial); std::string GetDataDirBySerial(const std::string& serial); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index daaac4c4ff..a9b9d32af0 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -185,16 +185,20 @@ QIcon main_window::GetAppIcon() } // loads the appIcon from path and embeds it centered into an empty square icon -void main_window::SetAppIconFromPath(const std::string& path) +void main_window::SetAppIconFromPath(const std::string& path, const std::string& title_id) { // get Icon for the gs_frame from path. this handles presumably all possible use cases - QString qpath = qstr(path); - std::string path_list[] = { path, sstr(qpath.section("/", 0, -2)), sstr(qpath.section("/", 0, -3)) }; - for (std::string pth : path_list) - { - if (!fs::is_dir(pth)) continue; + const QString qpath = qstr(path); + const std::string path_list[] = { path, sstr(qpath.section("/", 0, -2)), sstr(qpath.section("/", 0, -3)) }; - const std::string sfo_dir = Emu.GetSfoDirFromGamePath(pth, Emu.GetUsr()); + for (const std::string& pth : path_list) + { + if (!fs::is_dir(pth)) + { + continue; + } + + const std::string sfo_dir = Emu.GetSfoDirFromGamePath(pth, Emu.GetUsr(), title_id); const std::string ico = sfo_dir + "/ICON0.PNG"; if (fs::is_file(ico)) { @@ -268,7 +272,7 @@ void main_window::OnPlayOrPause() } } -void main_window::Boot(const std::string& path, bool direct, bool add_only, bool force_global_config) +void main_window::Boot(const std::string& path, const std::string& title_id, bool direct, bool add_only, bool force_global_config) { if (!Emu.IsStopped()) { @@ -283,11 +287,11 @@ void main_window::Boot(const std::string& path, bool direct, bool add_only, bool } } - SetAppIconFromPath(path); + SetAppIconFromPath(path, title_id); Emu.SetForceBoot(true); Emu.Stop(); - if (Emu.BootGame(path, direct, add_only, force_global_config)) + if (Emu.BootGame(path, title_id, direct, add_only, force_global_config)) { LOG_SUCCESS(LOADER, "Boot successful."); const std::string serial = Emu.GetTitleID().empty() ? "" : "[" + Emu.GetTitleID() + "] "; @@ -337,7 +341,7 @@ void main_window::BootElf() const std::string path = sstr(QFileInfo(filePath).canonicalFilePath()); LOG_NOTICE(LOADER, "Booting from BootElf..."); - Boot(path, true); + Boot(path, "", true); } void main_window::BootGame() @@ -652,7 +656,7 @@ void main_window::InstallPup(const QString& dropPath) guiSettings->ShowInfoBox(tr("Success!"), tr("Successfully installed PS3 firmware and LLE Modules!"), gui::ib_pup_success, this); Emu.SetForceBoot(true); - Emu.BootGame(g_cfg.vfs.get_dev_flash() + "sys/external/", true); + Emu.BootGame(g_cfg.vfs.get_dev_flash() + "sys/external/", "", true); } } @@ -1022,7 +1026,7 @@ void main_window::BootRecentAction(const QAction* act) } LOG_NOTICE(LOADER, "Booting from recent games list..."); - Boot(path, true); + Boot(path, "", true); }; QAction* main_window::CreateRecentAction(const q_string_pair& entry, const uint& sc_idx) @@ -1578,9 +1582,9 @@ void main_window::CreateDockWindows() } }); - connect(m_gameListFrame, &game_list_frame::RequestBoot, [this](const std::string& path, bool force_global_config) + connect(m_gameListFrame, &game_list_frame::RequestBoot, [this](const game_info& game, bool force_global_config) { - Boot(path, false, false, force_global_config); + Boot(game->info.path, game->info.serial, false, false, force_global_config); }); } @@ -1774,7 +1778,7 @@ void main_window::AddGamesFromDir(const QString& path) const std::string s_path = sstr(path); // search dropped path first or else the direct parent to an elf is wrongly skipped - if (Emu.BootGame(s_path, false, true)) + if (Emu.BootGame(s_path, "", false, true)) { LOG_NOTICE(GENERAL, "Returned from game addition by drag and drop: %s", s_path); } @@ -1785,7 +1789,7 @@ void main_window::AddGamesFromDir(const QString& path) { std::string pth = sstr(dir_iter.next()); - if (Emu.BootGame(pth, false, true)) + if (Emu.BootGame(pth, "", false, true)) { LOG_NOTICE(GENERAL, "Returned from game addition by drag and drop: %s", pth); } @@ -1916,7 +1920,7 @@ void main_window::dropEvent(QDropEvent* event) m_gameListFrame->Refresh(true); break; case drop_type::drop_game: // import valid games to gamelist (games.yaml) - if (Emu.BootGame(sstr(dropPaths.first()), true)) + if (Emu.BootGame(sstr(dropPaths.first()), "", true)) { LOG_SUCCESS(GENERAL, "Elf Boot from drag and drop done: %s", sstr(dropPaths.first())); } diff --git a/rpcs3/rpcs3qt/main_window.h b/rpcs3/rpcs3qt/main_window.h index 51f86298e1..f59dc99b6b 100644 --- a/rpcs3/rpcs3qt/main_window.h +++ b/rpcs3/rpcs3qt/main_window.h @@ -89,7 +89,7 @@ public Q_SLOTS: private Q_SLOTS: void OnPlayOrPause(); - void Boot(const std::string& path, bool direct = false, bool add_only = false, bool force_global_config = false); + void Boot(const std::string& path, const std::string& title_id = "", bool direct = false, bool add_only = false, bool force_global_config = false); void BootElf(); void BootGame(); void BootRsxCapture(std::string path = ""); @@ -108,7 +108,8 @@ protected: void dragEnterEvent(QDragEnterEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override; void dragLeaveEvent(QDragLeaveEvent* event) override; - void SetAppIconFromPath(const std::string& path); + void SetAppIconFromPath(const std::string& path, const std::string& title_id = ""); + private: void RepaintToolBarIcons(); void RepaintThumbnailIcons();