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