mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-03 00:38:58 +00:00
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:
+27
-31
@@ -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,11 +765,10 @@ 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 + "/")) {
|
||||||
@@ -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
@@ -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
@@ -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"),
|
||||||
|
|||||||
Reference in New Issue
Block a user