From 40b0ea1086627e88c124f2ebfc6089183a5112b5 Mon Sep 17 00:00:00 2001 From: MerryMage Date: Thu, 15 Dec 2016 09:53:26 +0000 Subject: [PATCH 1/4] archive_source_sd_savedata: Add static method to get a specific save data path --- src/core/file_sys/archive_source_sd_savedata.cpp | 5 +++++ src/core/file_sys/archive_source_sd_savedata.h | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/core/file_sys/archive_source_sd_savedata.cpp b/src/core/file_sys/archive_source_sd_savedata.cpp index 2d8a950a3..287322d3e 100644 --- a/src/core/file_sys/archive_source_sd_savedata.cpp +++ b/src/core/file_sys/archive_source_sd_savedata.cpp @@ -90,4 +90,9 @@ ResultVal ArchiveSource_SDSaveData::GetFormatInfo(u64 program return MakeResult(info); } +std::string ArchiveSource_SDSaveData::GetSaveDataPathFor(const std::string& mount_point, + u64 program_id) { + return GetSaveDataPath(GetSaveDataContainerPath(mount_point), program_id); +} + } // namespace FileSys diff --git a/src/core/file_sys/archive_source_sd_savedata.h b/src/core/file_sys/archive_source_sd_savedata.h index b33126c31..b5fe43cc1 100644 --- a/src/core/file_sys/archive_source_sd_savedata.h +++ b/src/core/file_sys/archive_source_sd_savedata.h @@ -23,6 +23,8 @@ public: ResultCode Format(u64 program_id, const FileSys::ArchiveFormatInfo& format_info); ResultVal GetFormatInfo(u64 program_id) const; + static std::string GetSaveDataPathFor(const std::string& mount_point, u64 program_id); + private: std::string mount_point; }; From 351b5d23f4b332a36e4b5c7668809deae4de4af1 Mon Sep 17 00:00:00 2001 From: MerryMage Date: Thu, 15 Dec 2016 09:54:25 +0000 Subject: [PATCH 2/4] loader: Implement ReadProgramId --- src/core/loader/loader.h | 9 +++++++++ src/core/loader/ncch.cpp | 12 ++++++++++++ src/core/loader/ncch.h | 7 +++++++ 3 files changed, 28 insertions(+) diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 5e3d46638..a6c2a745f 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -143,6 +143,15 @@ public: return ResultStatus::ErrorNotImplemented; } + /** + * Get the program id of the application + * @param out_program_id Reference to store program id into + * @return ResultStatus result of function + */ + virtual ResultStatus ReadProgramId(u64& out_program_id) { + return ResultStatus::ErrorNotImplemented; + } + /** * Get the RomFS of the application * Since the RomFS can be huge, we return a file reference instead of copying to a buffer diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index d4be61e0e..6f2164428 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -344,6 +344,18 @@ ResultStatus AppLoader_NCCH::ReadLogo(std::vector& buffer) { return LoadSectionExeFS("logo", buffer); } +ResultStatus AppLoader_NCCH::ReadProgramId(u64& out_program_id) { + if (!file.IsOpen()) + return ResultStatus::Error; + + ResultStatus result = LoadExeFS(); + if (result != ResultStatus::Success) + return result; + + out_program_id = ncch_header.program_id; + return ResultStatus::Success; +} + ResultStatus AppLoader_NCCH::ReadRomFS(std::shared_ptr& romfs_file, u64& offset, u64& size) { if (!file.IsOpen()) diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h index bcf3ae6e3..6c93d46d8 100644 --- a/src/core/loader/ncch.h +++ b/src/core/loader/ncch.h @@ -219,6 +219,13 @@ public: */ ResultStatus ReadLogo(std::vector& buffer) override; + /** + * Get the program id of the application + * @param out_program_id Reference to store program id into + * @return ResultStatus result of function + */ + ResultStatus ReadProgramId(u64& out_program_id) override; + /** * Get the RomFS of the application * @param romfs_file Reference to buffer to store data From f50dcc88bf160100336c94673aa7679403405cdd Mon Sep 17 00:00:00 2001 From: MerryMage Date: Thu, 15 Dec 2016 09:55:03 +0000 Subject: [PATCH 3/4] game_list: Implement context menu for items in list * Add a context menu with a "Open Save Data Location" action --- src/citra_qt/game_list.cpp | 28 +++++++++++++++++++++++++--- src/citra_qt/game_list.h | 3 +++ src/citra_qt/game_list_p.h | 5 ++++- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index e536628dd..09469f3c5 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include +#include #include #include #include "common/common_paths.h" @@ -28,6 +29,7 @@ GameList::GameList(QWidget* parent) : QWidget{parent} { tree_view->setSortingEnabled(true); tree_view->setEditTriggers(QHeaderView::NoEditTriggers); tree_view->setUniformRowHeights(true); + tree_view->setContextMenuPolicy(Qt::CustomContextMenu); item_model->insertColumns(0, COLUMN_COUNT); item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, "Name"); @@ -35,10 +37,10 @@ GameList::GameList(QWidget* parent) : QWidget{parent} { item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size"); connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); + connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); // We must register all custom types with the Qt Automoc system so that we are able to use it - // with - // signals/slots. In this case, QList falls under the umbrells of custom types. + // with signals/slots. In this case, QList falls under the umbrells of custom types. qRegisterMetaType>("QList"); layout->addWidget(tree_view); @@ -71,6 +73,23 @@ void GameList::DonePopulating() { tree_view->setEnabled(true); } +void GameList::PopupContextMenu(const QPoint& menu_location) { + QModelIndex item = tree_view->indexAt(menu_location); + if (!item.isValid()) + return; + + int row = item_model->itemFromIndex(item)->row(); + QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME); + u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong(); + + QMenu context_menu; + QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); + open_save_location->setEnabled(program_id != 0); + connect(open_save_location, &QAction::triggered, + [&]() { emit OpenSaveFolderRequested(program_id); }); + context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location)); +} + void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { if (!FileUtil::Exists(dir_path.toStdString()) || !FileUtil::IsDirectory(dir_path.toStdString())) { @@ -128,8 +147,11 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign std::vector smdh; loader->ReadIcon(smdh); + u64 program_id = 0; + loader->ReadProgramId(program_id); + emit EntryReady({ - new GameListItemPath(QString::fromStdString(physical_name), smdh), + new GameListItemPath(QString::fromStdString(physical_name), smdh, program_id), new GameListItem( QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), new GameListItemSize(FileUtil::GetSize(physical_name)), diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h index 30b2c79a8..1abf10051 100644 --- a/src/citra_qt/game_list.h +++ b/src/citra_qt/game_list.h @@ -36,12 +36,15 @@ public: signals: void GameChosen(QString game_path); void ShouldCancelWorker(); + void OpenSaveFolderRequested(u64 program_id); private: void AddEntry(const QList& entry_items); void ValidateEntry(const QModelIndex& item); void DonePopulating(); + void PopupContextMenu(const QPoint& menu_location); + QTreeView* tree_view = nullptr; QStandardItemModel* item_model = nullptr; GameListWorker* current_worker = nullptr; diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h index 5ca3fe991..a15f06c5f 100644 --- a/src/citra_qt/game_list_p.h +++ b/src/citra_qt/game_list_p.h @@ -71,10 +71,13 @@ class GameListItemPath : public GameListItem { public: static const int FullPathRole = Qt::UserRole + 1; static const int TitleRole = Qt::UserRole + 2; + static const int ProgramIdRole = Qt::UserRole + 3; GameListItemPath() : GameListItem() {} - GameListItemPath(const QString& game_path, const std::vector& smdh_data) : GameListItem() { + GameListItemPath(const QString& game_path, const std::vector& smdh_data, u64 program_id) + : GameListItem() { setData(game_path, FullPathRole); + setData(qulonglong(program_id), ProgramIdRole); if (!Loader::IsValidSMDH(smdh_data)) { // SMDH is not valid, set a default icon From 5a4e1b469d959e8ad24195185c7f5176b6a3d2cb Mon Sep 17 00:00:00 2001 From: MerryMage Date: Thu, 15 Dec 2016 09:56:32 +0000 Subject: [PATCH 4/4] main: Open folder when open save folder location context menu is clicked --- src/citra_qt/main.cpp | 19 +++++++++++++++++++ src/citra_qt/main.h | 1 + 2 files changed, 20 insertions(+) diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index a3887f9ab..ad6221739 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include #include @@ -41,6 +42,7 @@ #include "common/string_util.h" #include "core/arm/disassembler/load_symbol_map.h" #include "core/core.h" +#include "core/file_sys/archive_source_sd_savedata.h" #include "core/gdbstub/gdbstub.h" #include "core/loader/loader.h" #include "core/settings.h" @@ -171,6 +173,8 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { // Setup connections connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString)), Qt::DirectConnection); + connect(game_list, SIGNAL(OpenSaveFolderRequested(u64)), this, + SLOT(OnGameListOpenSaveFolder(u64)), Qt::DirectConnection); connect(ui.action_Configure, SIGNAL(triggered()), this, SLOT(OnConfigure())); connect(ui.action_Load_File, SIGNAL(triggered()), this, SLOT(OnMenuLoadFile()), Qt::DirectConnection); @@ -460,6 +464,21 @@ void GMainWindow::OnGameListLoadFile(QString game_path) { BootGame(game_path.toStdString()); } +void GMainWindow::OnGameListOpenSaveFolder(u64 program_id) { + std::string sdmc_dir = FileUtil::GetUserPath(D_SDMC_IDX); + std::string path = FileSys::ArchiveSource_SDSaveData::GetSaveDataPathFor(sdmc_dir, program_id); + QString qpath = QString::fromStdString(path); + + QDir dir(qpath); + if (!dir.exists()) { + QMessageBox::critical(this, tr("Error Opening Save Folder"), tr("Folder does not exist!")); + return; + } + + LOG_INFO(Frontend, "Opening save data path for program_id=%" PRIu64, program_id); + QDesktopServices::openUrl(QUrl::fromLocalFile(qpath)); +} + void GMainWindow::OnMenuLoadFile() { QString filename = QFileDialog::getOpenFileName(this, tr("Load File"), UISettings::values.roms_path, diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index f87178227..035b68a35 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -105,6 +105,7 @@ private slots: void OnStopGame(); /// Called whenever a user selects a game in the game list widget. void OnGameListLoadFile(QString game_path); + void OnGameListOpenSaveFolder(u64 program_id); void OnMenuLoadFile(); void OnMenuLoadSymbolMap(); /// Called whenever a user selects the "File->Select Game List Root" menu item