citra-emu
/
citra
Archived
1
0
Fork 0

Merge pull request #4229 from zhaowenlan1779/open-folder

citra_qt, core: game list "Open XXX Location" improvements
This commit is contained in:
Pengfei Zhu 2018-10-05 00:46:43 -05:00 committed by GitHub
commit 87e16c80ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 153 additions and 20 deletions

View File

@ -27,6 +27,8 @@
#include "citra_qt/ui_settings.h" #include "citra_qt/ui_settings.h"
#include "common/common_paths.h" #include "common/common_paths.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/file_sys/archive_extsavedata.h"
#include "core/file_sys/archive_source_sd_savedata.h"
#include "core/hle/service/fs/archive.h" #include "core/hle/service/fs/archive.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"
@ -417,7 +419,9 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
QMenu context_menu; QMenu context_menu;
switch (selected.data(GameListItem::TypeRole).value<GameListItemType>()) { switch (selected.data(GameListItem::TypeRole).value<GameListItemType>()) {
case GameListItemType::Game: case GameListItemType::Game:
AddGamePopup(context_menu, selected.data(GameListItemPath::ProgramIdRole).toULongLong()); AddGamePopup(context_menu, selected.data(GameListItemPath::FullPathRole).toString(),
selected.data(GameListItemPath::ProgramIdRole).toULongLong(),
selected.data(GameListItemPath::ExtdataIdRole).toULongLong());
break; break;
case GameListItemType::CustomDir: case GameListItemType::CustomDir:
AddPermDirPopup(context_menu, selected); AddPermDirPopup(context_menu, selected);
@ -431,23 +435,46 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location)); context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
} }
void GameList::AddGamePopup(QMenu& context_menu, u64 program_id) { void GameList::AddGamePopup(QMenu& context_menu, const QString& path, u64 program_id,
u64 extdata_id) {
QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
QAction* open_extdata_location = context_menu.addAction(tr("Open Extra Data Location"));
QAction* open_application_location = context_menu.addAction(tr("Open Application Location")); QAction* open_application_location = context_menu.addAction(tr("Open Application Location"));
QAction* open_update_location = context_menu.addAction(tr("Open Update Data Location")); QAction* open_update_location = context_menu.addAction(tr("Open Update Data Location"));
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
open_save_location->setEnabled(program_id != 0); const bool is_application =
open_application_location->setVisible(FileUtil::Exists( 0x0004000000000000 <= program_id && program_id <= 0x00040000FFFFFFFF;
Service::AM::GetTitleContentPath(Service::FS::MediaType::SDMC, program_id)));
open_update_location->setEnabled(0x0004000000000000 <= program_id && std::string sdmc_dir = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir);
program_id <= 0x00040000FFFFFFFF); open_save_location->setVisible(
is_application && FileUtil::Exists(FileSys::ArchiveSource_SDSaveData::GetSaveDataPathFor(
sdmc_dir, program_id)));
if (extdata_id) {
open_extdata_location->setVisible(
is_application &&
FileUtil::Exists(FileSys::GetExtDataPathFromId(sdmc_dir, extdata_id)));
} else {
open_extdata_location->setVisible(false);
}
auto media_type = Service::AM::GetTitleMediaType(program_id);
open_application_location->setVisible(path.toStdString() ==
Service::AM::GetTitleContentPath(media_type, program_id));
open_update_location->setVisible(
is_application && FileUtil::Exists(Service::AM::GetTitlePath(Service::FS::MediaType::SDMC,
program_id + 0xe00000000) +
"content/"));
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
navigate_to_gamedb_entry->setVisible(it != compatibility_list.end()); navigate_to_gamedb_entry->setVisible(it != compatibility_list.end());
connect(open_save_location, &QAction::triggered, [this, program_id] { connect(open_save_location, &QAction::triggered, [this, program_id] {
emit OpenFolderRequested(program_id, GameListOpenTarget::SAVE_DATA); emit OpenFolderRequested(program_id, GameListOpenTarget::SAVE_DATA);
}); });
connect(open_extdata_location, &QAction::triggered, [this, extdata_id] {
emit OpenFolderRequested(extdata_id, GameListOpenTarget::EXT_DATA);
});
connect(open_application_location, &QAction::triggered, [this, program_id] { connect(open_application_location, &QAction::triggered, [this, program_id] {
emit OpenFolderRequested(program_id, GameListOpenTarget::APPLICATION); emit OpenFolderRequested(program_id, GameListOpenTarget::APPLICATION);
}); });
@ -659,6 +686,9 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
u64 program_id = 0; u64 program_id = 0;
loader->ReadProgramId(program_id); loader->ReadProgramId(program_id);
u64 extdata_id = 0;
loader->ReadExtdataId(extdata_id);
std::vector<u8> smdh = [program_id, &loader]() -> std::vector<u8> { std::vector<u8> smdh = [program_id, &loader]() -> std::vector<u8> {
std::vector<u8> original_smdh; std::vector<u8> original_smdh;
loader->ReadIcon(original_smdh); loader->ReadIcon(original_smdh);
@ -691,7 +721,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
emit EntryReady( emit EntryReady(
{ {
new GameListItemPath(QString::fromStdString(physical_name), smdh, program_id), new GameListItemPath(QString::fromStdString(physical_name), smdh, program_id,
extdata_id),
new GameListItemCompat(compatibility), new GameListItemCompat(compatibility),
new GameListItemRegion(smdh), new GameListItemRegion(smdh),
new GameListItem( new GameListItem(

View File

@ -27,7 +27,7 @@ class QTreeView;
class QToolButton; class QToolButton;
class QVBoxLayout; class QVBoxLayout;
enum class GameListOpenTarget { SAVE_DATA = 0, APPLICATION = 1, UPDATE_DATA = 2 }; enum class GameListOpenTarget { SAVE_DATA = 0, EXT_DATA = 1, APPLICATION = 2, UPDATE_DATA = 3 };
class GameList : public QWidget { class GameList : public QWidget {
Q_OBJECT Q_OBJECT
@ -89,7 +89,7 @@ private:
void RefreshGameDirectory(); void RefreshGameDirectory();
void PopupContextMenu(const QPoint& menu_location); void PopupContextMenu(const QPoint& menu_location);
void AddGamePopup(QMenu& context_menu, u64 program_id); void AddGamePopup(QMenu& context_menu, const QString& path, u64 program_id, u64 extdata_id);
void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected); void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected);
void AddPermDirPopup(QMenu& context_menu, QModelIndex selected); void AddPermDirPopup(QMenu& context_menu, QModelIndex selected);

View File

@ -135,12 +135,15 @@ public:
static const int TitleRole = SortRole; static const int TitleRole = SortRole;
static const int FullPathRole = SortRole + 1; static const int FullPathRole = SortRole + 1;
static const int ProgramIdRole = SortRole + 2; static const int ProgramIdRole = SortRole + 2;
static const int ExtdataIdRole = SortRole + 3;
GameListItemPath() = default; GameListItemPath() = default;
GameListItemPath(const QString& game_path, const std::vector<u8>& smdh_data, u64 program_id) { GameListItemPath(const QString& game_path, const std::vector<u8>& smdh_data, u64 program_id,
u64 extdata_id) {
setData(type(), TypeRole); setData(type(), TypeRole);
setData(game_path, FullPathRole); setData(game_path, FullPathRole);
setData(qulonglong(program_id), ProgramIdRole); setData(qulonglong(program_id), ProgramIdRole);
setData(qulonglong(extdata_id), ExtdataIdRole);
if (!Loader::IsValidSMDH(smdh_data)) { if (!Loader::IsValidSMDH(smdh_data)) {
// SMDH is not valid, set a default icon // SMDH is not valid, set a default icon

View File

@ -52,6 +52,7 @@
#include "common/scm_rev.h" #include "common/scm_rev.h"
#include "common/scope_exit.h" #include "common/scope_exit.h"
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/archive_extsavedata.h"
#include "core/file_sys/archive_source_sd_savedata.h" #include "core/file_sys/archive_source_sd_savedata.h"
#include "core/frontend/applets/default_applets.h" #include "core/frontend/applets/default_applets.h"
#include "core/gdbstub/gdbstub.h" #include "core/gdbstub/gdbstub.h"
@ -879,7 +880,7 @@ void GMainWindow::OnGameListLoadFile(QString game_path) {
BootGame(game_path); BootGame(game_path);
} }
void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target) { void GMainWindow::OnGameListOpenFolder(u64 data_id, GameListOpenTarget target) {
std::string path; std::string path;
std::string open_target; std::string open_target;
@ -887,16 +888,24 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
case GameListOpenTarget::SAVE_DATA: { case GameListOpenTarget::SAVE_DATA: {
open_target = "Save Data"; open_target = "Save Data";
std::string sdmc_dir = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir); std::string sdmc_dir = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir);
path = FileSys::ArchiveSource_SDSaveData::GetSaveDataPathFor(sdmc_dir, program_id); path = FileSys::ArchiveSource_SDSaveData::GetSaveDataPathFor(sdmc_dir, data_id);
break; break;
} }
case GameListOpenTarget::APPLICATION: case GameListOpenTarget::EXT_DATA: {
open_target = "Application"; open_target = "Extra Data";
path = Service::AM::GetTitlePath(Service::FS::MediaType::SDMC, program_id) + "content/"; std::string sdmc_dir = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir);
path = FileSys::GetExtDataPathFromId(sdmc_dir, data_id);
break; break;
}
case GameListOpenTarget::APPLICATION: {
open_target = "Application";
auto media_type = Service::AM::GetTitleMediaType(data_id);
path = Service::AM::GetTitlePath(media_type, data_id) + "content/";
break;
}
case GameListOpenTarget::UPDATE_DATA: case GameListOpenTarget::UPDATE_DATA:
open_target = "Update Data"; open_target = "Update Data";
path = Service::AM::GetTitlePath(Service::FS::MediaType::SDMC, program_id + 0xe00000000) + path = Service::AM::GetTitlePath(Service::FS::MediaType::SDMC, data_id + 0xe00000000) +
"content/"; "content/";
break; break;
default: default:
@ -914,7 +923,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
return; return;
} }
LOG_INFO(Frontend, "Opening {} path for program_id={:016x}", open_target, program_id); LOG_INFO(Frontend, "Opening {} path for data_id={:016x}", open_target, data_id);
QDesktopServices::openUrl(QUrl::fromLocalFile(qpath)); QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));
} }

View File

@ -176,6 +176,13 @@ std::string GetExtDataContainerPath(const std::string& mount_point, bool shared)
return fmt::format("{}Nintendo 3DS/{}/{}/extdata/", mount_point, SYSTEM_ID, SDCARD_ID); return fmt::format("{}Nintendo 3DS/{}/{}/extdata/", mount_point, SYSTEM_ID, SDCARD_ID);
} }
std::string GetExtDataPathFromId(const std::string& mount_point, u64 extdata_id) {
u32 high = static_cast<u32>(extdata_id >> 32);
u32 low = static_cast<u32>(extdata_id & 0xFFFFFFFF);
return fmt::format("{}{:08x}/{:08x}/", GetExtDataContainerPath(mount_point, false), high, low);
}
Path ConstructExtDataBinaryPath(u32 media_type, u32 high, u32 low) { Path ConstructExtDataBinaryPath(u32 media_type, u32 high, u32 low) {
ExtSaveDataArchivePath path; ExtSaveDataArchivePath path;
path.media_type = media_type; path.media_type = media_type;

View File

@ -64,6 +64,15 @@ private:
*/ */
std::string GetExtSaveDataPath(const std::string& mount_point, const Path& path); std::string GetExtSaveDataPath(const std::string& mount_point, const Path& path);
/**
* Constructs a path to the concrete ExtData archive in the host filesystem based on the
* extdata ID and base mount point.
* @param mount_point The base mount point of the ExtSaveData archives.
* @param extdata_id The id of the ExtSaveData
* @returns The complete path to the specified extdata archive in the host filesystem
*/
std::string GetExtDataPathFromId(const std::string& mount_point, u64 extdata_id);
/** /**
* Constructs a path to the base folder to hold concrete ExtSaveData archives in the host file * Constructs a path to the base folder to hold concrete ExtSaveData archives in the host file
* system. * system.

View File

@ -576,6 +576,41 @@ Loader::ResultStatus NCCHContainer::ReadProgramId(u64_le& program_id) {
return Loader::ResultStatus::Success; return Loader::ResultStatus::Success;
} }
Loader::ResultStatus NCCHContainer::ReadExtdataId(u64& extdata_id) {
Loader::ResultStatus result = Load();
if (result != Loader::ResultStatus::Success)
return result;
if (!has_exheader)
return Loader::ResultStatus::ErrorNotUsed;
if (exheader_header.arm11_system_local_caps.storage_info.other_attributes >> 1) {
// Using extended save data access
// There would be multiple possible extdata IDs in this case. The best we can do for now is
// guessing that the first one would be the main save.
const std::array<u64, 6> extdata_ids{{
exheader_header.arm11_system_local_caps.storage_info.extdata_id0.Value(),
exheader_header.arm11_system_local_caps.storage_info.extdata_id1.Value(),
exheader_header.arm11_system_local_caps.storage_info.extdata_id2.Value(),
exheader_header.arm11_system_local_caps.storage_info.extdata_id3.Value(),
exheader_header.arm11_system_local_caps.storage_info.extdata_id4.Value(),
exheader_header.arm11_system_local_caps.storage_info.extdata_id5.Value(),
}};
for (u64 id : extdata_ids) {
if (id) {
// Found a non-zero ID, use it
extdata_id = id;
return Loader::ResultStatus::Success;
}
}
return Loader::ResultStatus::ErrorNotUsed;
}
extdata_id = exheader_header.arm11_system_local_caps.storage_info.ext_save_data_id;
return Loader::ResultStatus::Success;
}
bool NCCHContainer::HasExeFS() { bool NCCHContainer::HasExeFS() {
Loader::ResultStatus result = Load(); Loader::ResultStatus result = Load();
if (result != Loader::ResultStatus::Success) if (result != Loader::ResultStatus::Success)

View File

@ -125,9 +125,23 @@ struct ExHeader_SystemInfo {
}; };
struct ExHeader_StorageInfo { struct ExHeader_StorageInfo {
u8 ext_save_data_id[8]; union {
u64_le ext_save_data_id;
// When using extended savedata access
// Prefer the ID specified in the most significant bits
BitField<40, 20, u64_le> extdata_id3;
BitField<20, 20, u64_le> extdata_id4;
BitField<0, 20, u64_le> extdata_id5;
};
u8 system_save_data_id[8]; u8 system_save_data_id[8];
u8 reserved[8]; union {
u64_le storage_accessible_unique_ids;
// When using extended savedata access
// Prefer the ID specified in the most significant bits
BitField<40, 20, u64_le> extdata_id0;
BitField<20, 20, u64_le> extdata_id1;
BitField<0, 20, u64_le> extdata_id2;
};
u8 access_info[7]; u8 access_info[7];
u8 other_attributes; u8 other_attributes;
}; };
@ -251,6 +265,12 @@ public:
*/ */
Loader::ResultStatus ReadProgramId(u64_le& program_id); Loader::ResultStatus ReadProgramId(u64_le& program_id);
/**
* Get the Extdata ID of the NCCH container
* @return ResultStatus result of function
*/
Loader::ResultStatus ReadExtdataId(u64& extdata_id);
/** /**
* Checks whether the NCCH container contains an ExeFS * Checks whether the NCCH container contains an ExeFS
* @return bool check result * @return bool check result

View File

@ -157,6 +157,15 @@ public:
return ResultStatus::ErrorNotImplemented; return ResultStatus::ErrorNotImplemented;
} }
/**
* Get the extdata id for the application
* @param out_extdata_id Reference to store extdata id into
* @return ResultStatus result of function
*/
virtual ResultStatus ReadExtdataId(u64& out_extdata_id) {
return ResultStatus::ErrorNotImplemented;
}
/** /**
* Get the RomFS of the application * Get the RomFS of the application
* Since the RomFS can be huge, we return a file reference instead of copying to a buffer * Since the RomFS can be huge, we return a file reference instead of copying to a buffer

View File

@ -216,6 +216,14 @@ ResultStatus AppLoader_NCCH::ReadProgramId(u64& out_program_id) {
return ResultStatus::Success; return ResultStatus::Success;
} }
ResultStatus AppLoader_NCCH::ReadExtdataId(u64& out_extdata_id) {
ResultStatus result = base_ncch.ReadExtdataId(out_extdata_id);
if (result != ResultStatus::Success)
return result;
return ResultStatus::Success;
}
ResultStatus AppLoader_NCCH::ReadRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) { ResultStatus AppLoader_NCCH::ReadRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) {
return base_ncch.ReadRomFS(romfs_file); return base_ncch.ReadRomFS(romfs_file);
} }

View File

@ -51,6 +51,8 @@ public:
ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadProgramId(u64& out_program_id) override;
ResultStatus ReadExtdataId(u64& out_extdata_id) override;
ResultStatus ReadRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) override; ResultStatus ReadRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) override;
ResultStatus ReadUpdateRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) override; ResultStatus ReadUpdateRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) override;