diff --git a/src/core/importer.cpp b/src/core/importer.cpp index 22f1c5c..f28d4e3 100644 --- a/src/core/importer.cpp +++ b/src/core/importer.cpp @@ -296,7 +296,7 @@ std::vector SDMCImporter::ListContent() const { // Regex for half Title IDs static const std::regex title_regex{"[0-9a-f]{8}"}; -std::tuple SDMCImporter::LoadTitleData( +std::tuple> SDMCImporter::LoadTitleData( const std::string& path) const { // Remove trailing '/' const auto sdmc_path = config.sdmc_path.substr(0, config.sdmc_path.size() - 1); @@ -364,7 +364,7 @@ std::tuple SDMCImporter::LoadTitleData( bool seed_crypto{}; ncch.ReadSeedCrypto(seed_crypto); return {Common::UTF16BufferToUTF8(smdh.GetShortTitle(SMDH::TitleLanguage::English)), extdata_id, - encryption, seed_crypto}; + encryption, seed_crypto, smdh.GetIcon(false)}; } void SDMCImporter::ListTitle(std::vector& out) const { @@ -392,12 +392,12 @@ void SDMCImporter::ListTitle(std::vector& out) const { if (FileUtil::Exists(directory + virtual_name + "/content/")) { const auto content_path = fmt::format("/title/{:08x}/{}/content/", high_id, virtual_name); - const auto& [name, extdata_id, encryption, seed_crypto] = + const auto& [name, extdata_id, encryption, seed_crypto, icon] = LoadTitleData(content_path); out.push_back( {type, id, FileUtil::Exists(citra_path + "content/"), FileUtil::GetDirectoryTreeSize(directory + virtual_name + "/content/"), - name, extdata_id, encryption, seed_crypto}); + name, extdata_id, encryption, seed_crypto, icon}); } if (type != ContentType::Application) { diff --git a/src/core/importer.h b/src/core/importer.h index bb5a5a5..57ac6a0 100644 --- a/src/core/importer.h +++ b/src/core/importer.h @@ -51,6 +51,7 @@ struct ContentSpecifier { u64 extdata_id; ///< Extdata ID for Applications. EncryptionType encryption = EncryptionType::None; ///< Only for NCCHs. Encryption scheme. bool seed_crypto = false; ///< Only for NCCHs. Whether seed crypto is used. + std::vector icon; ///< Optional. The content's icon. }; /** @@ -140,12 +141,12 @@ private: void DeleteSysdata(u64 id) const; /** - * Loads the English short title name, extdata id and encryption of a title. + * Loads the English short title name, extdata id, encryption and icon of a title. * @param path Path of the 'content' folder relative to the SDMC root folder. * Required to end with '/'. - * @return {name, extdata_id, encryption, seed_crypto} + * @return {name, extdata_id, encryption, seed_crypto, icon} */ - std::tuple LoadTitleData(const std::string& path) const; + std::tuple> LoadTitleData(const std::string& path) const; bool is_good{}; Config config; diff --git a/src/frontend/import_dialog.cpp b/src/frontend/import_dialog.cpp index d2747a3..0283b2b 100644 --- a/src/frontend/import_dialog.cpp +++ b/src/frontend/import_dialog.cpp @@ -60,6 +60,11 @@ QString GetContentTypeName(Core::ContentType type) { return QObject::tr(ContentTypeMap.at(static_cast(type)).second, "ImportDialog"); } +QPixmap GetContentIcon(const Core::ContentSpecifier& specifier) { + return QPixmap::fromImage(QImage(reinterpret_cast(specifier.icon.data()), 24, 24, + QImage::Format::Format_RGB16)); +} + ImportDialog::ImportDialog(QWidget* parent, const Core::Config& config) : QDialog(parent), ui(std::make_unique()), user_path(config.user_path), importer(config) { @@ -124,10 +129,13 @@ void ImportDialog::RelistContent() { future_watcher->setFuture(future); } -void ImportDialog::InsertTopLevelItem(const QString& text) { +void ImportDialog::InsertTopLevelItem(const QString& text, QPixmap icon) { auto* checkBox = new QCheckBox(); checkBox->setText(text); - checkBox->setStyleSheet(QStringLiteral("margin-left:7px")); + if (!icon.isNull()) { + checkBox->setIcon(QIcon(icon)); + } + checkBox->setStyleSheet(QStringLiteral("margin-left: 7px; icon-size: 24px")); checkBox->setTristate(true); checkBox->setProperty("previousState", static_cast(Qt::Unchecked)); @@ -164,7 +172,8 @@ void ImportDialog::InsertTopLevelItem(const QString& text) { } void ImportDialog::InsertSecondLevelItem(std::size_t row, const Core::ContentSpecifier& content, - std::size_t id) { + std::size_t id, QString replace_name, + QPixmap replace_icon) { auto* checkBox = new QCheckBox(); checkBox->setStyleSheet(QStringLiteral("margin-left:7px")); // HACK: The checkbox is used to record ID. Is there a better way? @@ -185,6 +194,10 @@ void ImportDialog::InsertSecondLevelItem(std::size_t row, const Core::ContentSpe name = GetContentName(content); } + if (!replace_name.isEmpty()) { + name = replace_name; + } + QString encryption = tr(EncryptionTypeMap.at(content.encryption)); if (content.seed_crypto) { encryption.append(tr(" (Seed)")); @@ -201,6 +214,12 @@ void ImportDialog::InsertSecondLevelItem(std::size_t row, const Core::ContentSpe {QString{}, name, ReadableByteSize(content.maximum_size), encryption, content.already_exists ? QStringLiteral("Yes") : QStringLiteral("No")}}; + if (!ui->title_view_button->isChecked()) { + // Display icon when present + item->setData(1, Qt::DecorationRole, + replace_icon.isNull() ? GetContentIcon(content) : replace_icon); + } + ui->main->invisibleRootItem()->child(row)->addChild(item); ui->main->setItemWidget(item, 0, checkBox); @@ -240,23 +259,26 @@ void ImportDialog::RepopulateContent() { ui->main->clear(); ui->main->setSortingEnabled(false); + std::map title_name_map; // title ID -> title name + std::map title_icon_map; // title ID -> title icon + std::unordered_map extdata_id_map; // extdata ID -> title ID + for (const auto& content : contents) { + if (content.type == Core::ContentType::Application) { + title_name_map.emplace(content.id, GetContentName(content)); + title_icon_map.emplace(content.id, GetContentIcon(content)); + extdata_id_map.emplace(content.extdata_id, content.id); + } + } + const bool use_title_view = ui->title_view_button->isChecked(); if (use_title_view) { - std::map title_name_map; // title ID -> title name - std::unordered_map extdata_id_map; // extdata ID -> title ID - for (const auto& content : contents) { - if (content.type == Core::ContentType::Application) { - title_name_map.emplace(content.id, GetContentName(content)); - extdata_id_map.emplace(content.extdata_id, content.id); - } - } title_name_map.insert_or_assign(0, tr("Ungrouped")); title_name_map.insert_or_assign(1, tr("System Archive")); title_name_map.insert_or_assign(2, tr("System Data")); std::unordered_map title_row_map; for (const auto& [id, name] : title_name_map) { - InsertTopLevelItem(name); + InsertTopLevelItem(name, title_icon_map.count(id) ? title_icon_map.at(id) : QPixmap{}); title_row_map[id] = ui->main->invisibleRootItem()->childCount() - 1; } @@ -300,7 +322,21 @@ void ImportDialog::RepopulateContent() { for (std::size_t i = 0; i < contents.size(); ++i) { const auto& content = contents[i]; - InsertSecondLevelItem(static_cast(content.type), content, i); + + QString name; + QPixmap icon; + if (content.type == Core::ContentType::Savegame) { + name = title_name_map.count(content.id) ? title_name_map.at(content.id) : QString{}; + icon = title_icon_map.count(content.id) ? title_icon_map.at(content.id) : QPixmap{}; + } else if (content.type == Core::ContentType::Extdata) { + if (extdata_id_map.count(content.id)) { + u64 title_id = extdata_id_map.at(content.id); + name = title_name_map.count(title_id) ? title_name_map.at(title_id) : QString{}; + icon = title_icon_map.count(title_id) ? title_icon_map.at(title_id) : QPixmap{}; + } + } + + InsertSecondLevelItem(static_cast(content.type), content, i, name, icon); } } diff --git a/src/frontend/import_dialog.h b/src/frontend/import_dialog.h index 053bdd9..2231a8a 100644 --- a/src/frontend/import_dialog.h +++ b/src/frontend/import_dialog.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "core/importer.h" class QTreeWidgetItem; @@ -31,9 +32,11 @@ private: std::vector GetSelectedContentList(); void StartImporting(); - void InsertTopLevelItem(const QString& text); + void InsertTopLevelItem(const QString& text, QPixmap icon = {}); + // When replace_name and replace_icon are present they are used instead of those in `content`. void InsertSecondLevelItem(std::size_t row, const Core::ContentSpecifier& content, - std::size_t id); + std::size_t id, QString replace_name = {}, + QPixmap replace_icon = {}); std::unique_ptr ui;