Decouple core and frontend further

1. Unified titles & nand titles in the backend
2. Moved Application/Update/DLC and Title/Applet distinction to frontend
3. Merged System Save/Extra into System Data
4. Restore the special icon to System Archives
This commit is contained in:
Pengfei
2021-08-26 22:31:18 +08:00
parent c4e4966598
commit 035c71bfe9
3 changed files with 178 additions and 116 deletions
+27 -31
View File
@@ -126,9 +126,7 @@ bool SDMCImporter::ImportContent(const ContentSpecifier& specifier,
bool SDMCImporter::ImportContentImpl(const ContentSpecifier& specifier,
const Common::ProgressCallback& callback) {
switch (specifier.type) {
case ContentType::Application:
case ContentType::Update:
case ContentType::DLC:
case ContentType::Title:
return ImportTitle(specifier, callback);
case ContentType::Savegame:
return ImportSavegame(specifier.id, callback);
@@ -140,8 +138,7 @@ bool SDMCImporter::ImportContentImpl(const ContentSpecifier& specifier,
return ImportNandExtdata(specifier.id, callback);
case ContentType::Sysdata:
return ImportSysdata(specifier.id, callback);
case ContentType::SystemTitle:
case ContentType::SystemApplet:
case ContentType::NandTitle:
return ImportNandTitle(specifier, callback);
default:
UNREACHABLE();
@@ -454,7 +451,7 @@ std::shared_ptr<FileUtil::IOFile> SDMCImporter::OpenContent(const ContentSpecifi
} else {
// For DLCs, there one subfolder every 256 titles, but in practice hardcoded 00000000
// should be fine (also matches GodMode9 behaviour)
const auto format_str = specifier.type == ContentType::DLC
const auto format_str = (specifier.id >> 32) == 0x0004008c
? "/title/{:08x}/{:08x}/content/00000000/{:08x}.app"
: "/title/{:08x}/{:08x}/content/{:08x}.app";
const auto path =
@@ -565,8 +562,9 @@ static std::string GetTitleFileName(NCCHContainer& ncch) {
bool SDMCImporter::DumpCXI(const ContentSpecifier& specifier, std::string destination,
const Common::ProgressCallback& callback, bool auto_filename) {
if (specifier.type != ContentType::Application) {
LOG_ERROR(Core, "Unsupported specifier type {}", static_cast<int>(specifier.type));
// not an Application
if (specifier.type != ContentType::Title || (specifier.id >> 32) != 0x00040000) {
LOG_ERROR(Core, "Unsupported specifier (id={:016x})", specifier.id);
return false;
}
@@ -767,11 +765,10 @@ bool SDMCImporter::CheckTitleContents(const ContentSpecifier& specifier,
constexpr u64 TitleSizeAllowance = 0xA000;
void SDMCImporter::ListTitle(std::vector<ContentSpecifier>& out) const {
const auto ProcessDirectory = [this, &out, &sdmc_path = config.sdmc_path](ContentType type,
u64 high_id) {
const auto ProcessDirectory = [this, &out, &sdmc_path = config.sdmc_path](u64 high_id) {
FileUtil::ForeachDirectoryEntry(
nullptr, fmt::format("{}title/{:08x}/", sdmc_path, high_id),
[this, &sdmc_path, type, high_id, &out](u64* /*num_entries_out*/,
[this, &sdmc_path, high_id, &out](u64* /*num_entries_out*/,
const std::string& directory,
const std::string& virtual_name) {
if (!FileUtil::IsDirectory(directory + virtual_name + "/")) {
@@ -792,8 +789,9 @@ void SDMCImporter::ListTitle(std::vector<ContentSpecifier>& out) const {
if (FileUtil::Exists(directory + virtual_name + "/content/")) {
do {
TitleMetadata tmd;
if (!LoadTMD(type, id, tmd)) {
out.push_back({type, id, FileUtil::Exists(citra_path + "content/"),
if (!LoadTMD(ContentType::Title, id, tmd)) {
out.push_back({ContentType::Title, id,
FileUtil::Exists(citra_path + "content/"),
FileUtil::GetDirectoryTreeSize(directory + virtual_name +
"/content/")});
break;
@@ -806,7 +804,8 @@ void SDMCImporter::ListTitle(std::vector<ContentSpecifier>& out) const {
std::make_shared<SDMCFile>(sdmc_path, boot_content_path, "rb"));
if (!ncch.Load()) {
LOG_WARNING(Core, "Could not load NCCH {}", boot_content_path);
out.push_back({type, id, FileUtil::Exists(citra_path + "content/"),
out.push_back({ContentType::Title, id,
FileUtil::Exists(citra_path + "content/"),
FileUtil::GetDirectoryTreeSize(directory + virtual_name +
"/content/")});
break;
@@ -816,12 +815,13 @@ void SDMCImporter::ListTitle(std::vector<ContentSpecifier>& out) const {
const auto size =
FileUtil::GetDirectoryTreeSize(directory + virtual_name + "/content/") +
TitleSizeAllowance;
out.push_back({type, id, FileUtil::Exists(citra_path + "content/"), size,
name, extdata_id, icon});
out.push_back({ContentType::Title, id,
FileUtil::Exists(citra_path + "content/"), size, name,
extdata_id, icon});
} while (false);
}
if (type != ContentType::Application) {
if (high_id != 0x00040000) { // Check savegame only for applications
return true;
}
if (FileUtil::Exists(directory + virtual_name + "/data/")) {
@@ -842,9 +842,9 @@ void SDMCImporter::ListTitle(std::vector<ContentSpecifier>& out) const {
});
};
ProcessDirectory(ContentType::Application, 0x00040000);
ProcessDirectory(ContentType::Update, 0x0004000e);
ProcessDirectory(ContentType::DLC, 0x0004008c);
ProcessDirectory(0x00040000);
ProcessDirectory(0x0004000e);
ProcessDirectory(0x0004008c);
}
// TODO: Simplify.
@@ -872,8 +872,8 @@ void SDMCImporter::ListNandTitle(std::vector<ContentSpecifier>& out) const {
if (FileUtil::Exists(content_path)) {
do {
TitleMetadata tmd;
if (!LoadTMD(ContentType::SystemTitle, id, tmd)) {
out.push_back({ContentType::SystemTitle, id,
if (!LoadTMD(ContentType::NandTitle, id, tmd)) {
out.push_back({ContentType::NandTitle, id,
FileUtil::Exists(citra_path + "content/"),
FileUtil::GetDirectoryTreeSize(content_path)});
break;
@@ -889,13 +889,12 @@ void SDMCImporter::ListNandTitle(std::vector<ContentSpecifier>& out) const {
}
const auto& [name, extdata_id, icon] = LoadTitleData(ncch);
const auto type = (id >> 32) == 0x00040030 ? ContentType::SystemApplet
: ContentType::SystemTitle;
const auto size =
FileUtil::GetDirectoryTreeSize(directory + virtual_name + "/content/") +
TitleSizeAllowance;
out.push_back({type, id, FileUtil::Exists(citra_path + "content/"), size,
name, extdata_id, icon});
out.push_back({ContentType::NandTitle, id,
FileUtil::Exists(citra_path + "content/"), size, name,
extdata_id, icon});
} while (false);
}
return true;
@@ -1038,9 +1037,7 @@ void SDMCImporter::ListSysdata(std::vector<ContentSpecifier>& out) const {
void SDMCImporter::DeleteContent(const ContentSpecifier& specifier) const {
switch (specifier.type) {
case ContentType::Application:
case ContentType::Update:
case ContentType::DLC:
case ContentType::Title:
return DeleteTitle(specifier.id);
case ContentType::Savegame:
return DeleteSavegame(specifier.id);
@@ -1052,8 +1049,7 @@ void SDMCImporter::DeleteContent(const ContentSpecifier& specifier) const {
return DeleteNandExtdata(specifier.id);
case ContentType::Sysdata:
return DeleteSysdata(specifier.id);
case ContentType::SystemTitle:
case ContentType::SystemApplet:
case ContentType::NandTitle:
return DeleteNandTitle(specifier.id);
default:
UNREACHABLE();
+5 -9
View File
@@ -26,25 +26,21 @@ class TitleMetadata;
* Applications, updates and DLCs are all considered titles.
*/
enum class ContentType {
Application,
Update,
DLC,
Title,
Savegame,
NandSavegame,
Extdata,
NandExtdata,
Sysdata,
SystemTitle,
SystemApplet, // This should belong to System Title, but they cause problems so a new category.
NandTitle,
};
constexpr std::size_t ContentTypeCount = 7;
constexpr bool IsTitle(ContentType type) {
return type == ContentType::Application || type == ContentType::Update ||
type == ContentType::DLC || type == ContentType::SystemTitle ||
type == ContentType::SystemApplet;
return type == ContentType::Title || type == ContentType::NandTitle;
}
constexpr bool IsNandTitle(ContentType type) {
return type == ContentType::SystemTitle || type == ContentType::SystemApplet;
return type == ContentType::NandTitle;
}
/**
+144 -74
View File
@@ -27,55 +27,126 @@
#include "frontend/title_info_dialog.h"
#include "ui_import_dialog.h"
// content type, singular name, plural name, icon name
// Groups that are used in the frontend. This is organized in a slightly different way than Core.
enum class DisplayGroup {
Application,
Update,
DLC,
Savegame,
Extdata,
Sysdata,
SystemTitle,
SystemApplet,
};
// clang-format off
static constexpr std::array<std::tuple<Core::ContentType, const char*, const char*, const char*>, 10>
ContentTypeMap{{
{Core::ContentType::Application, QT_TR_NOOP("Application"), QT_TR_NOOP("Applications"), "app"},
{Core::ContentType::Update, QT_TR_NOOP("Update"), QT_TR_NOOP("Updates"), "update"},
{Core::ContentType::DLC, QT_TR_NOOP("DLC"), QT_TR_NOOP("DLCs"), "dlc"},
{Core::ContentType::Savegame, QT_TR_NOOP("Save Data"), QT_TR_NOOP("Save Data"), "save_data"},
{Core::ContentType::NandSavegame, QT_TR_NOOP("System Save Data"), QT_TR_NOOP("System Save Data"), "save_data"},
{Core::ContentType::Extdata, QT_TR_NOOP("Extra Data"), QT_TR_NOOP("Extra Data"), "save_data"},
{Core::ContentType::NandExtdata, QT_TR_NOOP("System Extra Data"), QT_TR_NOOP("System Extra Data"), "save_data"},
{Core::ContentType::Sysdata, QT_TR_NOOP("System Data"), QT_TR_NOOP("System Data"), "system_data"},
{Core::ContentType::SystemTitle, QT_TR_NOOP("System Title"), QT_TR_NOOP("System Titles"), "hos"},
{Core::ContentType::SystemApplet, QT_TR_NOOP("System Applet"), QT_TR_NOOP("System Applets"), "hos"},
// group, singular name, plural name, icon name
constexpr std::array<std::tuple<DisplayGroup, const char*, const char*, const char*>, 8>
DisplayGroupMap{{
{DisplayGroup::Application, QT_TR_NOOP("Application"), QT_TR_NOOP("Applications"), "app"},
{DisplayGroup::Update, QT_TR_NOOP("Update"), QT_TR_NOOP("Updates"), "update"},
{DisplayGroup::DLC, QT_TR_NOOP("DLC"), QT_TR_NOOP("DLCs"), "dlc"},
{DisplayGroup::Savegame, QT_TR_NOOP("Save Data"), QT_TR_NOOP("Save Data"), "save_data"},
{DisplayGroup::Extdata, QT_TR_NOOP("Extra Data"), QT_TR_NOOP("Extra Data"), "save_data"},
{DisplayGroup::Sysdata, QT_TR_NOOP("System Data"), QT_TR_NOOP("System Data"), "system_data"},
{DisplayGroup::SystemTitle, QT_TR_NOOP("System Title"), QT_TR_NOOP("System Titles"), "hos"},
{DisplayGroup::SystemApplet, QT_TR_NOOP("System Applet"), QT_TR_NOOP("System Applets"), "hos"},
}};
// clang-format on
static DisplayGroup GetDisplayGroup(const Core::ContentSpecifier& specifier) {
if (specifier.type == Core::ContentType::Title) {
switch (specifier.id >> 32) {
case 0x00040000:
return DisplayGroup::Application;
case 0x0004000e:
return DisplayGroup::Update;
case 0x0004008c:
return DisplayGroup::DLC;
default:
UNREACHABLE();
}
}
if (specifier.type == Core::ContentType::Savegame) {
return DisplayGroup::Savegame;
}
if (specifier.type == Core::ContentType::Extdata) {
return DisplayGroup::Extdata;
}
if (specifier.type == Core::ContentType::NandSavegame ||
specifier.type == Core::ContentType::NandExtdata ||
specifier.type == Core::ContentType::Sysdata) { // These are grouped together
return DisplayGroup::Sysdata;
}
if (specifier.type == Core::ContentType::NandTitle) {
return (specifier.id >> 32) == 0x00040030 ? DisplayGroup::SystemApplet
: DisplayGroup::SystemTitle;
}
UNREACHABLE();
}
static QString GetContentName(const Core::ContentSpecifier& specifier) {
if (specifier.type == Core::ContentType::NandSavegame) {
return QObject::tr("System Save 0x%1", "ImportDialog")
.arg(specifier.id, 16, 16, QLatin1Char('0'));
}
if (specifier.type == Core::ContentType::NandExtdata) {
return QObject::tr("System Extra 0x%1", "ImportDialog")
.arg(specifier.id, 16, 16, QLatin1Char('0'));
}
return specifier.name.empty()
? QStringLiteral("0x%1").arg(specifier.id, 16, 16, QLatin1Char('0'))
: QString::fromStdString(specifier.name);
}
template <bool Plural = true>
static QString GetContentTypeName(Core::ContentType type) {
static QString GetDisplayGroupName(DisplayGroup group) {
if constexpr (Plural) {
return QObject::tr(std::get<2>(ContentTypeMap.at(static_cast<std::size_t>(type))),
return QObject::tr(std::get<2>(DisplayGroupMap.at(static_cast<std::size_t>(group))),
"ImportDialog");
} else {
return QObject::tr(std::get<1>(ContentTypeMap.at(static_cast<std::size_t>(type))),
return QObject::tr(std::get<1>(DisplayGroupMap.at(static_cast<std::size_t>(group))),
"ImportDialog");
}
}
static QPixmap GetContentTypeIcon(Core::ContentType type) {
template <bool Plural = true>
static QString GetDisplayGroupName(const Core::ContentSpecifier& specifier) {
return GetDisplayGroupName<Plural>(GetDisplayGroup(specifier));
}
static QPixmap GetDisplayGroupIcon(DisplayGroup group) {
return QIcon::fromTheme(
QString::fromUtf8(std::get<3>(ContentTypeMap.at(static_cast<std::size_t>(type)))))
QString::fromUtf8(std::get<3>(DisplayGroupMap.at(static_cast<std::size_t>(group)))))
.pixmap(24);
}
static QPixmap GetContentIcon(const Core::ContentSpecifier& specifier,
bool use_category_icon = false) {
if (specifier.icon.empty()) {
// Return a category icon, or a null icon
return use_category_icon ? GetContentTypeIcon(specifier.type)
: QIcon::fromTheme(QStringLiteral("unknown")).pixmap(24);
static QPixmap GetContentIcon(const Core::ContentSpecifier& specifier) {
if (!specifier.icon.empty()) {
return QPixmap::fromImage(QImage(reinterpret_cast<const uchar*>(specifier.icon.data()), 24,
24, QImage::Format::Format_RGB16));
}
return QPixmap::fromImage(QImage(reinterpret_cast<const uchar*>(specifier.icon.data()), 24, 24,
QImage::Format::Format_RGB16));
// Use a category icon to distinguish between different types of System Data
if (specifier.type == Core::ContentType::NandSavegame ||
specifier.type == Core::ContentType::NandExtdata) {
return GetDisplayGroupIcon(DisplayGroup::Savegame);
}
if (specifier.type == Core::ContentType::Sysdata) {
return GetDisplayGroupIcon(DisplayGroup::Sysdata);
}
// Use a special icon for NAND non-executable archives
if (specifier.type == Core::ContentType::NandTitle) {
const auto id_high = specifier.id >> 32;
if (id_high == 0x0004001b || id_high == 0x0004009b || id_high == 0x000400db) {
return QIcon::fromTheme(QStringLiteral("system_archive")).pixmap(24);
}
}
// Return a null icon otherwise
return QIcon::fromTheme(QStringLiteral("unknown")).pixmap(24);
}
ImportDialog::ImportDialog(QWidget* parent, const Core::Config& config_)
@@ -214,29 +285,28 @@ void ImportDialog::InsertTopLevelItem(QString text, QPixmap icon, u64 total_size
}
// Content types that themselves form a 'Title' like entity.
constexpr std::array<Core::ContentType, 5> SpecialContentTypeList{{
Core::ContentType::NandSavegame,
Core::ContentType::NandExtdata,
Core::ContentType::Sysdata,
Core::ContentType::SystemTitle,
Core::ContentType::SystemApplet,
constexpr std::array<DisplayGroup, 3> SpecialDisplayGroupList{{
DisplayGroup::Sysdata,
DisplayGroup::SystemTitle,
DisplayGroup::SystemApplet,
}};
void ImportDialog::InsertSecondLevelItem(std::size_t row, const Core::ContentSpecifier& content,
std::size_t id, QString replace_name,
QPixmap replace_icon) {
const bool use_title_view = ui->title_view_button->isChecked();
const auto group = GetDisplayGroup(content);
QString name;
if (use_title_view) {
if (row == 0) {
name = QStringLiteral("%1 (%2)")
.arg(GetContentName(content))
.arg(GetContentTypeName<false>(content.type));
} else if (row <= SpecialContentTypeList.size()) {
.arg(GetDisplayGroupName<false>(group));
} else if (row <= SpecialDisplayGroupList.size()) {
name = GetContentName(content);
} else {
name = GetContentTypeName<false>(content.type);
name = GetDisplayGroupName<false>(group);
}
} else {
name = GetContentName(content);
@@ -252,14 +322,13 @@ void ImportDialog::InsertSecondLevelItem(std::size_t row, const Core::ContentSpe
// Set icon
QPixmap icon;
if (replace_icon.isNull()) {
// Exclude system titles, they are a single group but have own icons.
if (use_title_view && content.type != Core::ContentType::SystemTitle &&
content.type != Core::ContentType::SystemApplet) {
icon = GetContentTypeIcon(content.type);
const bool in_special_group =
std::find(SpecialDisplayGroupList.begin(), SpecialDisplayGroupList.end(), group) !=
SpecialDisplayGroupList.end();
if (use_title_view && !in_special_group) {
icon = GetDisplayGroupIcon(group);
} else {
// When not in title view, System Data groups use category icons.
const bool use_category_icon = content.type == Core::ContentType::Sysdata;
icon = GetContentIcon(content, use_category_icon);
icon = GetContentIcon(content);
}
} else {
icon = replace_icon;
@@ -267,7 +336,7 @@ void ImportDialog::InsertSecondLevelItem(std::size_t row, const Core::ContentSpe
item->setIcon(0, QIcon(icon));
// Skip System Applets, but enable everything else by default.
if (!content.already_exists && content.type != Core::ContentType::SystemApplet) {
if (!content.already_exists && group != DisplayGroup::SystemApplet) {
item->setCheckState(0, Qt::Checked);
total_selected_size += content.maximum_size;
} else {
@@ -284,9 +353,10 @@ void ImportDialog::OnItemChanged(QTreeWidgetItem* item, int column) {
}
const auto& specifier = SpecifierFromItem(item);
const auto group = GetDisplayGroup(specifier);
if (item->checkState(0) == Qt::Checked) {
if (!applet_warning_shown && !specifier.already_exists &&
specifier.type == Core::ContentType::SystemApplet) {
group == DisplayGroup::SystemApplet) {
QMessageBox::warning(
this, tr("Warning"),
@@ -297,8 +367,7 @@ void ImportDialog::OnItemChanged(QTreeWidgetItem* item, int column) {
total_selected_size += specifier.maximum_size;
} else {
if (!system_warning_shown && !specifier.already_exists &&
(specifier.type == Core::ContentType::Sysdata ||
specifier.type == Core::ContentType::SystemTitle)) {
(group == DisplayGroup::Sysdata || group == DisplayGroup::SystemTitle)) {
QMessageBox::warning(this, tr("Warning"),
tr("You are de-selecting important files that may be necessary "
@@ -331,7 +400,8 @@ void ImportDialog::RepopulateContent() {
std::map<u64, TitleMapEntry> title_map;
std::unordered_map<u64, u64> extdata_id_map; // extdata ID -> title ID
for (const auto& content : contents) {
if (content.type == Core::ContentType::Application) {
// Applications
if (content.type == Core::ContentType::Title && (content.id >> 32) == 0x00040000) {
title_map[content.id].name = GetContentName(content);
title_map[content.id].icon = GetContentIcon(content);
extdata_id_map.emplace(content.extdata_id, content.id);
@@ -343,9 +413,7 @@ void ImportDialog::RepopulateContent() {
const u64 title_id = extdata_id_map.at(content.id);
title_map[title_id].contents.emplace_back(&content);
}
} else if (content.type == Core::ContentType::Application ||
content.type == Core::ContentType::Update ||
content.type == Core::ContentType::DLC ||
} else if (content.type == Core::ContentType::Title ||
content.type == Core::ContentType::Savegame) {
if (title_map.count(content.id)) {
title_map[content.id].contents.emplace_back(&content);
@@ -358,16 +426,16 @@ void ImportDialog::RepopulateContent() {
// Create 'Ungrouped' category.
InsertTopLevelItem(tr("Ungrouped"), QIcon::fromTheme(QStringLiteral("unknown")).pixmap(24));
// Create categories for special content types.
for (std::size_t i = 0; i < SpecialContentTypeList.size(); ++i) {
InsertTopLevelItem(GetContentTypeName(SpecialContentTypeList[i]),
GetContentTypeIcon(SpecialContentTypeList[i]));
// Create categories for special display groups.
for (std::size_t i = 0; i < SpecialDisplayGroupList.size(); ++i) {
InsertTopLevelItem(GetDisplayGroupName(SpecialDisplayGroupList[i]),
GetDisplayGroupIcon(SpecialDisplayGroupList[i]));
}
// Titles
// Applications
std::unordered_map<u64, u64> title_row_map;
for (auto& [id, entry] : title_map) {
// Process the title's contents
// Process the application's contents
u64 total_size = 0;
bool has_exist = false, has_non_exist = false;
for (const auto* content : entry.contents) {
@@ -398,9 +466,7 @@ void ImportDialog::RepopulateContent() {
std::size_t row = 0; // 0 for ungrouped (default)
switch (content.type) {
case Core::ContentType::Application:
case Core::ContentType::Update:
case Core::ContentType::DLC:
case Core::ContentType::Title:
case Core::ContentType::Savegame: {
// Fix the id
const auto real_id = content.id & 0xffffff00ffffffff;
@@ -416,10 +482,11 @@ void ImportDialog::RepopulateContent() {
break;
}
default: {
const std::size_t idx = std::find(SpecialContentTypeList.begin(),
SpecialContentTypeList.end(), content.type) -
SpecialContentTypeList.begin();
ASSERT_MSG(idx < SpecialContentTypeList.size(), "Content Type not handled");
const std::size_t idx =
std::find(SpecialDisplayGroupList.begin(), SpecialDisplayGroupList.end(),
GetDisplayGroup(content)) -
SpecialDisplayGroupList.begin();
ASSERT_MSG(idx < SpecialDisplayGroupList.size(), "Display Group not handled");
row = idx + 1;
break;
}
@@ -428,8 +495,8 @@ void ImportDialog::RepopulateContent() {
InsertSecondLevelItem(row, content, i);
}
} else {
for (const auto& [type, singular_name, plural_name, icon_name] : ContentTypeMap) {
InsertTopLevelItem(tr(plural_name), GetContentTypeIcon(type));
for (const auto& [group, singular_name, plural_name, icon_name] : DisplayGroupMap) {
InsertTopLevelItem(tr(plural_name), GetDisplayGroupIcon(group));
}
for (std::size_t i = 0; i < contents.size(); ++i) {
@@ -450,7 +517,8 @@ void ImportDialog::RepopulateContent() {
}
}
InsertSecondLevelItem(static_cast<std::size_t>(content.type), content, i, name, icon);
InsertSecondLevelItem(static_cast<std::size_t>(GetDisplayGroup(content)), content, i,
name, icon);
}
}
@@ -509,7 +577,8 @@ void ImportDialog::OnContextMenu(const QPoint& point) {
QMenu context_menu(this);
if (item->parent()) { // Second level
const auto& specifier = SpecifierFromItem(item);
if (specifier.type == Core::ContentType::Application) {
const auto group = GetDisplayGroup(specifier);
if (group == DisplayGroup::Application) {
context_menu.addAction(tr("Dump CXI file"),
[this, specifier] { StartDumpingCXISingle(specifier); });
}
@@ -528,15 +597,16 @@ void ImportDialog::OnContextMenu(const QPoint& point) {
for (int i = 0; i < item->childCount(); ++i) {
const auto& specifier = SpecifierFromItem(item->child(i));
if (specifier.type == Core::ContentType::Application) {
const auto group = GetDisplayGroup(specifier);
if (group == DisplayGroup::Application) {
context_menu.addAction(tr("Dump Base CXI file"),
[this, specifier] { StartDumpingCXISingle(specifier); });
context_menu.addAction(tr("Build Base CIA"),
[this, specifier] { StartBuildingCIASingle(specifier); });
} else if (specifier.type == Core::ContentType::Update) {
} else if (group == DisplayGroup::Update) {
context_menu.addAction(tr("Build Update CIA"),
[this, specifier] { StartBuildingCIASingle(specifier); });
} else if (specifier.type == Core::ContentType::DLC) {
} else if (group == DisplayGroup::DLC) {
context_menu.addAction(tr("Build DLC CIA"),
[this, specifier] { StartBuildingCIASingle(specifier); });
}
@@ -613,7 +683,7 @@ void ImportDialog::RunMultiJob(MultiJob* job, std::size_t total_count, u64 total
.arg(count)
.arg(total_count)
.arg(GetContentName(next_content))
.arg(GetContentTypeName<false>(next_content.type))
.arg(GetDisplayGroupName<false>(next_content))
.arg(FormatETA(eta)));
current_content = next_content;
current_count = count;
@@ -631,7 +701,7 @@ void ImportDialog::RunMultiJob(MultiJob* job, std::size_t total_count, u64 total
.arg(current_count)
.arg(total_count)
.arg(GetContentName(current_content))
.arg(GetContentTypeName<false>(current_content.type))
.arg(GetDisplayGroupName<false>(current_content))
.arg(ReadableByteSize(current_imported_size))
.arg(ReadableByteSize(current_content.maximum_size))
.arg(FormatETA(eta)));
@@ -647,7 +717,7 @@ void ImportDialog::RunMultiJob(MultiJob* job, std::size_t total_count, u64 total
for (const auto& content : failed_contents) {
list_content.append(QStringLiteral("<li>%1 (%2)</li>")
.arg(GetContentName(content))
.arg(GetContentTypeName<false>(content.type)));
.arg(GetDisplayGroupName<false>(content)));
}
QMessageBox::critical(this, tr("threeSD"),
tr("List of failed contents:<ul>%1</ul>").arg(list_content));
@@ -715,7 +785,7 @@ void ImportDialog::StartBatchDumpingCXI() {
const auto removed_iter = std::remove_if(
to_import.begin(), to_import.end(), [](const Core::ContentSpecifier& specifier) {
return specifier.type != Core::ContentType::Application;
return specifier.type != Core::ContentType::Title || (specifier.id >> 32) != 0x00040000;
});
if (removed_iter == to_import.begin()) { // No Applications selected
QMessageBox::critical(this, tr("threeSD"),