Merge pull request #4229 from zhaowenlan1779/open-folder
citra_qt, core: game list "Open XXX Location" improvements
This commit is contained in:
commit
87e16c80ac
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Reference in New Issue