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 "common/common_paths.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/loader/loader.h"
@ -417,7 +419,9 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
QMenu context_menu;
switch (selected.data(GameListItem::TypeRole).value<GameListItemType>()) {
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;
case GameListItemType::CustomDir:
AddPermDirPopup(context_menu, selected);
@ -431,23 +435,46 @@ void GameList::PopupContextMenu(const QPoint& 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_extdata_location = context_menu.addAction(tr("Open Extra Data 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* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
open_save_location->setEnabled(program_id != 0);
open_application_location->setVisible(FileUtil::Exists(
Service::AM::GetTitleContentPath(Service::FS::MediaType::SDMC, program_id)));
open_update_location->setEnabled(0x0004000000000000 <= program_id &&
program_id <= 0x00040000FFFFFFFF);
const bool is_application =
0x0004000000000000 <= program_id && program_id <= 0x00040000FFFFFFFF;
std::string sdmc_dir = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir);
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);
navigate_to_gamedb_entry->setVisible(it != compatibility_list.end());
connect(open_save_location, &QAction::triggered, [this, program_id] {
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] {
emit OpenFolderRequested(program_id, GameListOpenTarget::APPLICATION);
});
@ -659,6 +686,9 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
u64 program_id = 0;
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> original_smdh;
loader->ReadIcon(original_smdh);
@ -691,7 +721,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
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 GameListItemRegion(smdh),
new GameListItem(

View file

@ -27,7 +27,7 @@ class QTreeView;
class QToolButton;
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 {
Q_OBJECT
@ -89,7 +89,7 @@ private:
void RefreshGameDirectory();
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 AddPermDirPopup(QMenu& context_menu, QModelIndex selected);

View file

@ -135,12 +135,15 @@ public:
static const int TitleRole = SortRole;
static const int FullPathRole = SortRole + 1;
static const int ProgramIdRole = SortRole + 2;
static const int ExtdataIdRole = SortRole + 3;
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(game_path, FullPathRole);
setData(qulonglong(program_id), ProgramIdRole);
setData(qulonglong(extdata_id), ExtdataIdRole);
if (!Loader::IsValidSMDH(smdh_data)) {
// SMDH is not valid, set a default icon

View file

@ -52,6 +52,7 @@
#include "common/scm_rev.h"
#include "common/scope_exit.h"
#include "core/core.h"
#include "core/file_sys/archive_extsavedata.h"
#include "core/file_sys/archive_source_sd_savedata.h"
#include "core/frontend/applets/default_applets.h"
#include "core/gdbstub/gdbstub.h"
@ -879,7 +880,7 @@ void GMainWindow::OnGameListLoadFile(QString 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 open_target;
@ -887,16 +888,24 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
case GameListOpenTarget::SAVE_DATA: {
open_target = "Save Data";
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;
}
case GameListOpenTarget::APPLICATION:
open_target = "Application";
path = Service::AM::GetTitlePath(Service::FS::MediaType::SDMC, program_id) + "content/";
case GameListOpenTarget::EXT_DATA: {
open_target = "Extra Data";
std::string sdmc_dir = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir);
path = FileSys::GetExtDataPathFromId(sdmc_dir, data_id);
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:
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/";
break;
default:
@ -914,7 +923,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
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));
}

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);
}
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) {
ExtSaveDataArchivePath path;
path.media_type = media_type;

View file

@ -64,6 +64,15 @@ private:
*/
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
* system.

View file

@ -576,6 +576,41 @@ Loader::ResultStatus NCCHContainer::ReadProgramId(u64_le& program_id) {
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() {
Loader::ResultStatus result = Load();
if (result != Loader::ResultStatus::Success)

View file

@ -125,9 +125,23 @@ struct ExHeader_SystemInfo {
};
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 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 other_attributes;
};
@ -251,6 +265,12 @@ public:
*/
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
* @return bool check result

View file

@ -157,6 +157,15 @@ public:
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
* 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;
}
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) {
return base_ncch.ReadRomFS(romfs_file);
}

View file

@ -51,6 +51,8 @@ public:
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 ReadUpdateRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) override;