diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index cdea73dbbf..b620371052 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -229,7 +229,7 @@ void Emulator::SetPath(const std::string& path, const std::string& elf_path) m_elf_path = elf_path; } -bool Emulator::BootGame(const std::string& path, bool direct) +bool Emulator::BootGame(const std::string& path, bool direct, bool add_only) { static const char* boot_list[] = { @@ -242,7 +242,7 @@ bool Emulator::BootGame(const std::string& path, bool direct) if (direct && fs::is_file(path)) { SetPath(path); - Load(); + Load(add_only); return true; } @@ -254,7 +254,7 @@ bool Emulator::BootGame(const std::string& path, bool direct) if (fs::is_file(elf)) { SetPath(elf); - Load(); + Load(add_only); return true; } @@ -279,7 +279,7 @@ std::string Emulator::GetLibDir() return fmt::replace_all(g_cfg.vfs.dev_flash, "$(EmulatorDir)", emu_dir) + "sys/external/"; } -void Emulator::Load() +void Emulator::Load(bool add_only) { Stop(); @@ -442,6 +442,12 @@ void Emulator::Load() return; } + if (add_only) + { + LOG_NOTICE(LOADER, "Finished to add data to games.yml by boot for: %s", m_path); + return; + } + // Check game updates const std::string hdd0_boot = hdd0_game + m_title_id + "/USRDIR/EBOOT.BIN"; diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 76fa1105b1..bb4be13ad7 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -238,12 +238,12 @@ public: return m_pause_amend_time; } - bool BootGame(const std::string& path, bool direct = false); + bool BootGame(const std::string& path, bool direct = false, bool add_only = false); static std::string GetHddDir(); static std::string GetLibDir(); - void Load(); + void Load(bool add_only = false); void Run(); bool Pause(); void Resume(); diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 54e33dd41f..10e0813bc4 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -23,6 +23,7 @@ #include #include #include +#include static const std::string m_class_name = "GameViewer"; inline std::string sstr(const QString& _in) { return _in.toUtf8().toStdString(); } @@ -31,6 +32,8 @@ inline QSize sizeFromSlider(const int& pos) { return GUI::gl_icon_size_min + (GU game_list_frame::game_list_frame(std::shared_ptr settings, const Render_Creator& r_Creator, QWidget *parent) : QDockWidget(tr("Game List"), parent), xgui_settings(settings), m_Render_Creator(r_Creator) { + setAcceptDrops(true); + m_isListLayout = xgui_settings->GetValue(GUI::gl_listMode).toBool(); m_icon_size_index = xgui_settings->GetValue(GUI::gl_iconSize).toInt(); m_Margin_Factor = xgui_settings->GetValue(GUI::gl_marginFactor).toReal(); @@ -384,8 +387,8 @@ void game_list_frame::Refresh(bool fromDrive) // std::set is used to remove duplicates from the list for (const auto& dir : std::set(std::make_move_iterator(path_list.begin()), std::make_move_iterator(path_list.end()))) { - const std::string& sfb = dir + "/PS3_DISC.SFB"; - const std::string& sfo = dir + (fs::is_file(sfb) ? "/PS3_GAME/PARAM.SFO" : "/PARAM.SFO"); + const std::string sfb = dir + "/PS3_DISC.SFB"; + const std::string sfo = dir + (fs::is_file(sfb) ? "/PS3_GAME/PARAM.SFO" : "/PARAM.SFO"); const fs::file sfo_file(sfo); if (!sfo_file) @@ -541,19 +544,9 @@ void game_list_frame::doubleClickedSlot(const QModelIndex& index) if (1) { - Q_EMIT RequestIconPathSet(m_game_data[i].info.path); - - Emu.Stop(); - - if (!Emu.BootGame(m_game_data[i].info.path)) - { - LOG_ERROR(LOADER, "Failed to boot %s", m_game_data[i].info.path); - } - else + if (Boot(m_game_data[i].info)) { LOG_SUCCESS(LOADER, "Boot from gamelist per doubleclick: done"); - Q_EMIT RequestAddRecentGame(q_string_pair(qstr(Emu.GetBoot()), qstr("[" + m_game_data[i].info.serial + "] " + m_game_data[i].info.name))); - Refresh(true); } } else @@ -622,7 +615,12 @@ void game_list_frame::ShowSpecifiedContextMenu(const QPoint &pos, int row) myMenu.addSeparator(); QAction* checkCompat = myMenu.addAction(tr("&Check Game Compatibility")); - connect(boot, &QAction::triggered, [=]() {Boot(row); }); + connect(boot, &QAction::triggered, [=]() { + if (Boot(m_game_data[row].info)) + { + LOG_SUCCESS(LOADER, "Boot from gamelist per Boot: done"); + } + }); connect(configure, &QAction::triggered, [=]() { settings_dialog (xgui_settings, m_Render_Creator, 0, this, &currGame).exec(); Refresh(true); @@ -670,22 +668,23 @@ void game_list_frame::ShowSpecifiedContextMenu(const QPoint &pos, int row) myMenu.exec(globalPos); } -void game_list_frame::Boot(int row) +bool game_list_frame::Boot(const GameInfo& game) { - Q_EMIT RequestIconPathSet(m_game_data[row].info.path); + Q_EMIT RequestIconPathSet(game.path); Emu.Stop(); - if (!Emu.BootGame(m_game_data[row].info.path)) + if (!Emu.BootGame(game.path)) { - QMessageBox::warning(this, tr("Warning!"), tr("Failed to boot ") + qstr(m_game_data[row].info.path)); - LOG_ERROR(LOADER, "Failed to boot %s", m_game_data[row].info.path); + QMessageBox::warning(this, tr("Warning!"), tr("Failed to boot ") + qstr(game.path)); + LOG_ERROR(LOADER, "Failed to boot %s", game.path); + return false; } else { - LOG_SUCCESS(LOADER, "Boot from gamelist per Boot: done"); - Q_EMIT RequestAddRecentGame(q_string_pair(qstr(Emu.GetBoot()), qstr("[" + m_game_data[row].info.serial + "] " + m_game_data[row].info.name))); + Q_EMIT RequestAddRecentGame(q_string_pair(qstr(Emu.GetBoot()), qstr("[" + game.serial + "] " + game.name))); Refresh(true); + return true; } } @@ -1070,3 +1069,186 @@ std::string game_list_frame::GetStringFromU32(const u32& key, const std::map list = md.urls(); // get list of all the dropped file urls + + for (int i = 0; i < list.count(); i++) // check each file in url list for valid type + { + const QString path = list[i].toLocalFile(); // convert url to filepath + + // check for directories first, only valid if all other paths led to directories until now. + if (QFileInfo(path).isDir()) + { + if (i != 0 && dropType != DROP_DIR) return DROP_ERROR; + + dropType = DROP_DIR; + + if (dropPaths) + { + dropPaths->append(path); + } + continue; + } + + // now that we know it has to be a file we get the file ending + QString suffix = QFileInfo(list[i].fileName()).suffix().toLower(); + + if (suffix.isEmpty()) return DROP_ERROR; // NANI the heck would you want such a file? + + QString last_suffix; + + if (i == 0) // the first item defines our file type + { + last_suffix = suffix; + } + else if (last_suffix == "pup" || last_suffix == "bin") // we only accept one firmware or eboot file + { + return list.count() != 1 ? dropType : DROP_ERROR; + } + else if (last_suffix != suffix) // we don't accept multiple file types + { + return DROP_ERROR; + } + + // set drop type by file ending + if (suffix == "pkg") + { + dropType = DROP_PKG; + } + else if (suffix == "pup") + { + dropType = DROP_PUP; + } + else if (suffix == "rap") + { + dropType = DROP_RAP; + } + else if (suffix == "bin") + { + dropType = DROP_GAME; + } + else // if (suffix == "kuso") + { + return DROP_ERROR; + } + + if (dropPaths) // we only need to know the paths on drop + { + dropPaths->append(path); + } + } + return dropType; +} + +void game_list_frame::dropEvent(QDropEvent* event) +{ + QStringList dropPaths; + + switch (IsValidFile(*event->mimeData(), &dropPaths)) // get valid file paths and drop type + { + case DROP_ERROR: + break; + case DROP_PKG: // install the package + Q_EMIT RequestPackageInstall(dropPaths); + break; + case DROP_PUP: // install the firmware + Q_EMIT RequestFirmwareInstall(dropPaths.first()); + break; + case DROP_RAP: // import rap files to exdata dir + for (const auto& rap : dropPaths) + { + const std::string rapname = sstr(QFileInfo(rap).fileName()); + + // TODO: use correct user ID once User Manager is implemented + if (!fs::copy_file(sstr(rap), fmt::format("%s/home/%s/exdata/%s", Emu.GetHddDir(), "00000001", rapname), false)) + { + LOG_WARNING(GENERAL, "Could not copy rap file by drop: %s", rapname); + } + else + { + LOG_SUCCESS(GENERAL, "Successfully copied rap file by drop: %s", rapname); + } + } + break; + case DROP_DIR: // import valid games to gamelist (games.yaml) + for (const auto& path : dropPaths) + { + AddGamesFromDir(path); + } + Refresh(true); + break; + case DROP_GAME: // import valid games to gamelist (games.yaml) + if (Emu.BootGame(sstr(dropPaths.first()), true)) + { + LOG_SUCCESS(GENERAL, "Elf Boot from drag and drop done: %s", sstr(dropPaths.first())); + } + Refresh(true); + break; + default: + LOG_WARNING(GENERAL, "Invalid dropType in gamelist dropEvent"); + break; + } +} + +void game_list_frame::dragEnterEvent(QDragEnterEvent* event) +{ + if (IsValidFile(*event->mimeData())) + { + event->accept(); + } +} + +void game_list_frame::dragMoveEvent(QDragMoveEvent* event) +{ + if (IsValidFile(*event->mimeData())) + { + event->accept(); + } +} + +void game_list_frame::dragLeaveEvent(QDragLeaveEvent* event) +{ + event->accept(); +} diff --git a/rpcs3/rpcs3qt/game_list_frame.h b/rpcs3/rpcs3qt/game_list_frame.h index 6f372fd77e..62d1cc4d66 100644 --- a/rpcs3/rpcs3qt/game_list_frame.h +++ b/rpcs3/rpcs3qt/game_list_frame.h @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -167,6 +168,15 @@ struct Tool_Bar_Button bool isActive; }; +enum { + DROP_ERROR, + DROP_PKG, + DROP_PUP, + DROP_RAP, + DROP_DIR, + DROP_GAME +}; + class game_list_frame : public QDockWidget { Q_OBJECT @@ -205,7 +215,6 @@ public Q_SLOTS: void RepaintToolBarIcons(); private Q_SLOTS: - void Boot(int row); void RemoveCustomConfiguration(int row); void OnColClicked(int col); void ShowContextMenu(const QPoint &pos); @@ -213,24 +222,33 @@ private Q_SLOTS: void doubleClickedSlot(const QModelIndex& index); Q_SIGNALS: void GameListFrameClosed(); - void RequestIconPathSet(const std::string path); + void RequestIconPathSet(const std::string& path); void RequestAddRecentGame(const q_string_pair& entry); void RequestIconSizeActSet(const int& idx); void RequestListModeActSet(const bool& isList); void RequestCategoryActSet(const int& id); void RequestSaveSliderPos(const bool& save); + void RequestPackageInstall(const QStringList& paths); + void RequestFirmwareInstall(const QString& path); protected: /** Override inherited method from Qt to allow signalling when close happened.*/ void closeEvent(QCloseEvent* event) override; void resizeEvent(QResizeEvent *event) override; + void dropEvent(QDropEvent* event) override; + void dragEnterEvent(QDragEnterEvent* event) override; + void dragMoveEvent(QDragMoveEvent* event) override; + void dragLeaveEvent(QDragLeaveEvent* event) override; private: QPixmap PaintedPixmap(const QImage& img, bool paintConfigIcon = false); + bool Boot(const GameInfo& info); void PopulateGameGrid(uint maxCols, const QSize& image_size, const QColor& image_color); void FilterData(); void SortGameList(); int PopulateGameList(); bool SearchMatchesApp(const std::string& name, const std::string& serial); + int IsValidFile(const QMimeData& md, QStringList* dropPaths = nullptr); + void AddGamesFromDir(const QString& path); std::string CurrentSelectionIconPath(); std::string GetStringFromU32(const u32& key, const std::map& map, bool combined = false); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 0b1514e4e6..b2e1b73d65 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -285,10 +285,24 @@ void main_window::BootGame() } } -void main_window::InstallPkg() +void main_window::InstallPkg(const QString& dropPath) { - QString path_last_PKG = guiSettings->GetValue(GUI::fd_install_pkg).toString(); - QString filePath = QFileDialog::getOpenFileName(this, tr("Select PKG To Install"), path_last_PKG, tr("PKG files (*.pkg);;All files (*.*)")); + QString filePath = dropPath; + + if (filePath.isEmpty()) + { + QString path_last_PKG = guiSettings->GetValue(GUI::fd_install_pkg).toString(); + filePath = QFileDialog::getOpenFileName(this, tr("Select PKG To Install"), path_last_PKG, tr("PKG files (*.pkg);;All files (*.*)")); + } + else + { + if (QMessageBox::question(this, tr("PKG Decrypter / Installer"), tr("Install package: %1?").arg(filePath), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) + { + LOG_NOTICE(LOADER, "PKG: Cancelled installation from drop. File: %s", sstr(filePath)); + return; + } + } if (filePath == NULL) { @@ -418,10 +432,24 @@ void main_window::InstallPkg() } } -void main_window::InstallPup() +void main_window::InstallPup(const QString& dropPath) { - QString path_last_PUP = guiSettings->GetValue(GUI::fd_install_pup).toString(); - QString filePath = QFileDialog::getOpenFileName(this, tr("Select PS3UPDAT.PUP To Install"), path_last_PUP, tr("PS3 update file (PS3UPDAT.PUP)")); + QString filePath = dropPath; + + if (filePath.isEmpty()) + { + QString path_last_PUP = guiSettings->GetValue(GUI::fd_install_pup).toString(); + filePath = QFileDialog::getOpenFileName(this, tr("Select PS3UPDAT.PUP To Install"), path_last_PUP, tr("PS3 update file (PS3UPDAT.PUP)")); + } + else + { + if (QMessageBox::question(this, tr("RPCS3 Firmware Installer"), tr("Install firmware: %1?").arg(filePath), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) + { + LOG_NOTICE(LOADER, "Firmware: Cancelled installation from drop. File: %s", sstr(filePath)); + return; + } + } if (filePath == NULL) { @@ -1020,8 +1048,8 @@ void main_window::CreateConnects() connect(ui->freezeRecentAct, &QAction::triggered, [=](bool checked) { guiSettings->SetValue(GUI::rg_freeze, checked); }); - connect(ui->bootInstallPkgAct, &QAction::triggered, this, &main_window::InstallPkg); - connect(ui->bootInstallPupAct, &QAction::triggered, this, &main_window::InstallPup); + connect(ui->bootInstallPkgAct, &QAction::triggered, [this] {InstallPkg(); }); + connect(ui->bootInstallPupAct, &QAction::triggered, [this] {InstallPup(); }); connect(ui->exitAct, &QAction::triggered, this, &QWidget::close); connect(ui->sysPauseAct, &QAction::triggered, Pause); connect(ui->sysStopAct, &QAction::triggered, [=]() { Emu.Stop(); }); @@ -1275,6 +1303,13 @@ void main_window::CreateDockWindows() }); connect(gameListFrame, &game_list_frame::RequestIconPathSet, this, &main_window::SetAppIconFromPath); connect(gameListFrame, &game_list_frame::RequestAddRecentGame, this, &main_window::AddRecentAction); + connect(gameListFrame, &game_list_frame::RequestPackageInstall, [this](const QStringList& paths){ + for (const auto& path : paths) + { + InstallPkg(path); + } + }); + connect(gameListFrame, &game_list_frame::RequestFirmwareInstall, this, &main_window::InstallPup); } void main_window::ConfigureGuiFromSettings(bool configureAll) diff --git a/rpcs3/rpcs3qt/main_window.h b/rpcs3/rpcs3qt/main_window.h index 2e861e2912..dd3a4c5ed1 100644 --- a/rpcs3/rpcs3qt/main_window.h +++ b/rpcs3/rpcs3qt/main_window.h @@ -76,8 +76,6 @@ public Q_SLOTS: private Q_SLOTS: void BootElf(); void BootGame(); - void InstallPkg(); - void InstallPup(); void DecryptSPRXLibraries(); void SaveWindowState(); @@ -85,6 +83,8 @@ private Q_SLOTS: protected: void closeEvent(QCloseEvent *event) override; + void keyPressEvent(QKeyEvent *keyEvent) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; void SetAppIconFromPath(const std::string path); private: void CreateActions(); @@ -92,9 +92,8 @@ private: void CreateDockWindows(); void ConfigureGuiFromSettings(bool configureAll = false); void EnableMenus(bool enabled); - - void keyPressEvent(QKeyEvent *keyEvent) override; - void mouseDoubleClickEvent(QMouseEvent *event) override; + void InstallPkg(const QString& dropPath = ""); + void InstallPup(const QString& dropPath = ""); QAction* CreateRecentAction(const q_string_pair& entry, const uint& sc_idx); void BootRecentAction(const QAction* act);