From 1f0a969cb7aa20aa70cffc0db2beb940e12d5641 Mon Sep 17 00:00:00 2001 From: Pengfei Date: Wed, 4 Aug 2021 00:35:01 +0800 Subject: [PATCH] Add CIABuildDialog --- src/core/importer.cpp | 32 +++++++- src/core/importer.h | 10 ++- src/frontend/CMakeLists.txt | 3 + src/frontend/cia_build_dialog.cpp | 94 ++++++++++++++++++++++ src/frontend/cia_build_dialog.h | 27 +++++++ src/frontend/cia_build_dialog.ui | 125 ++++++++++++++++++++++++++++++ src/frontend/import_dialog.cpp | 38 +++++---- 7 files changed, 310 insertions(+), 19 deletions(-) create mode 100644 src/frontend/cia_build_dialog.cpp create mode 100644 src/frontend/cia_build_dialog.h create mode 100644 src/frontend/cia_build_dialog.ui diff --git a/src/core/importer.cpp b/src/core/importer.cpp index 2b9eb8f..fd13e5f 100644 --- a/src/core/importer.cpp +++ b/src/core/importer.cpp @@ -649,7 +649,24 @@ void SDMCImporter::AbortDumpCXI() { dump_cxi_ncch->AbortDecryptToFile(); } -bool SDMCImporter::BuildCIA(CIABuildType type, const ContentSpecifier& specifier, +bool SDMCImporter::CanBuildLegitCIA(const ContentSpecifier& specifier) const { + if (specifier.type != ContentType::Application && specifier.type != ContentType::Update && + specifier.type != ContentType::DLC && specifier.type != ContentType::SystemTitle) { + return false; + } + + TitleMetadata tmd; + if (!LoadTMD(specifier.type, specifier.id, tmd)) { + return false; + } + if (!tmd.VerifyHashes() || !tmd.ValidateSignature()) { + return false; + } + // TODO: check ticket, etc? + return true; +} + +bool SDMCImporter::BuildCIA(CIABuildType build_type, const ContentSpecifier& specifier, std::string destination, const Common::ProgressCallback& callback, bool auto_filename) { @@ -678,6 +695,11 @@ bool SDMCImporter::BuildCIA(CIABuildType type, const ContentSpecifier& specifier : fmt::format("{}title/{:08x}/{:08x}/content/", config.sdmc_path, (specifier.id >> 32), (specifier.id & 0xFFFFFFFF)); + static constexpr std::array BuildTypeSuffixes{{ + ".standard.cia", + ".pirate-legit.cia", + ".legit.cia", + }}; if (auto_filename) { if (destination.back() != '/' && destination.back() != '\\') { destination.push_back('/'); @@ -686,11 +708,13 @@ bool SDMCImporter::BuildCIA(CIABuildType type, const ContentSpecifier& specifier fmt::format("{}{:08x}.app", physical_path, tmd.GetBootContentID()); if (is_nand) { NCCHContainer ncch(std::make_shared(boot_content_path, "rb")); - destination.append(GetTitleFileName(ncch)).append(".cia"); + destination.append(GetTitleFileName(ncch)) + .append(BuildTypeSuffixes.at(static_cast(build_type))); } else { const auto relative_path = boot_content_path.substr(config.sdmc_path.size() - 1); NCCHContainer ncch(std::make_shared(config.sdmc_path, relative_path, "rb")); - destination.append(GetTitleFileName(ncch)).append(".cia"); + destination.append(GetTitleFileName(ncch)) + .append(BuildTypeSuffixes.at(static_cast(build_type))); } } @@ -699,7 +723,7 @@ bool SDMCImporter::BuildCIA(CIABuildType type, const ContentSpecifier& specifier cia_builder = std::make_unique(); } - bool ret = cia_builder->Init(type, destination, tmd, config, + bool ret = cia_builder->Init(build_type, destination, tmd, config, FileUtil::GetDirectoryTreeSize(physical_path), callback); SCOPE_EXIT({ { diff --git a/src/core/importer.h b/src/core/importer.h index 3fc308b..2bcba81 100644 --- a/src/core/importer.h +++ b/src/core/importer.h @@ -145,8 +145,14 @@ public: * Blocks, but can be aborted on another thread. * @return true on success, false otherwise */ - bool BuildCIA(CIABuildType type, const ContentSpecifier& specifier, std::string destination, - const Common::ProgressCallback& callback, bool auto_filename = false); + bool BuildCIA(CIABuildType build_type, const ContentSpecifier& specifier, + std::string destination, const Common::ProgressCallback& callback, + bool auto_filename = false); + + /** + * Checks if a content can be built as a legit CIA. + */ + bool CanBuildLegitCIA(const ContentSpecifier& specifier) const; /** * Aborts current CIA building diff --git a/src/frontend/CMakeLists.txt b/src/frontend/CMakeLists.txt index 2fb5302..2c600cc 100644 --- a/src/frontend/CMakeLists.txt +++ b/src/frontend/CMakeLists.txt @@ -13,6 +13,9 @@ add_executable(threeSD helpers/multi_job.h helpers/simple_job.cpp helpers/simple_job.h + cia_build_dialog.cpp + cia_build_dialog.h + cia_build_dialog.ui import_dialog.cpp import_dialog.h import_dialog.ui diff --git a/src/frontend/cia_build_dialog.cpp b/src/frontend/cia_build_dialog.cpp new file mode 100644 index 0000000..4561cbd --- /dev/null +++ b/src/frontend/cia_build_dialog.cpp @@ -0,0 +1,94 @@ +// Copyright 2021 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include "common/assert.h" +#include "frontend/cia_build_dialog.h" +#include "ui_cia_build_dialog.h" + +CIABuildDialog::CIABuildDialog(QWidget* parent, bool is_dir_, bool is_nand, bool enable_legit, + const QString& default_path) + : QDialog(parent), ui(std::make_unique()), is_dir(is_dir_) { + ui->setupUi(this); + + const double scale = qApp->desktop()->logicalDpiX() / 96.0; + resize(static_cast(width() * scale), static_cast(height() * scale)); + + if (is_dir) { + setWindowTitle(tr("Batch Build CIA")); + } + if (is_nand) { + ui->pirateLegitButton->setVisible(false); + ui->pirateLegitLabel->setVisible(false); + + auto message = tr("Encrypted CIA with legit TMD, encrypted contents and legit ticket.
"); + if (is_dir) { + message.append(tr( + "Legit tickets for these titles do not include console-identifying information.")); + } else { + message.append(tr( + "Legit ticket for this title does not include console-identifying information.")); + } + ui->legitLabel->setText(message); + } + if (!enable_legit) { + const auto message = + is_dir ? tr("This option is not available as some of the titles are not legit.") + : tr("This option is not available as the title is not legit."); + ui->pirateLegitButton->setEnabled(false); + ui->pirateLegitLabel->setText(message); + ui->legitButton->setEnabled(false); + ui->legitLabel->setText(message); + } + + connect(ui->buttonBox, &QDialogButtonBox::accepted, [this] { + if (ui->destination->text().isEmpty()) { + const QString message = is_dir ? tr("Please specify destination folder.") + : tr("Please specify destination file."); + QMessageBox::warning(this, tr("threeSD"), message); + return; + } + accept(); + }); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &CIABuildDialog::reject); + + if (is_dir) { + ui->destination->setText(default_path); + } + connect(ui->destinationExplore, &QToolButton::clicked, [this, default_path] { + QString path; + if (is_dir) { + path = QFileDialog::getExistingDirectory(this, tr("Batch Build CIA"), + ui->destination->text()); + } else { + const auto cur = ui->destination->text().isEmpty() + ? default_path + : QFileInfo(ui->destination->text()).path(); + path = QFileDialog::getSaveFileName(this, tr("Build CIA"), cur, + tr("CTR Importable Archive (*.cia)")); + } + if (!path.isEmpty()) { + ui->destination->setText(path); + } + }); +} + +CIABuildDialog::~CIABuildDialog() = default; + +std::pair CIABuildDialog::GetResults() const { + Core::CIABuildType type; + if (ui->standardButton->isChecked()) { + type = Core::CIABuildType::Standard; + } else if (ui->pirateLegitButton->isChecked()) { + type = Core::CIABuildType::PirateLegit; + } else if (ui->legitButton->isChecked()) { + type = Core::CIABuildType::Legit; + } else { + UNREACHABLE(); + } + return {ui->destination->text(), type}; +} diff --git a/src/frontend/cia_build_dialog.h b/src/frontend/cia_build_dialog.h new file mode 100644 index 0000000..efbb591 --- /dev/null +++ b/src/frontend/cia_build_dialog.h @@ -0,0 +1,27 @@ +// Copyright 2021 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "core/ncch/cia_common.h" + +namespace Ui { +class CIABuildDialog; +} + +class CIABuildDialog : public QDialog { + Q_OBJECT + +public: + explicit CIABuildDialog(QWidget* parent, bool is_dir, bool is_nand, bool enable_legit, + const QString& default_path); + ~CIABuildDialog(); + + std::pair GetResults() const; + +private: + std::unique_ptr ui; + bool is_dir; +}; diff --git a/src/frontend/cia_build_dialog.ui b/src/frontend/cia_build_dialog.ui new file mode 100644 index 0000000..22f7947 --- /dev/null +++ b/src/frontend/cia_build_dialog.ui @@ -0,0 +1,125 @@ + + + CIABuildDialog + + + + 0 + 0 + 510 + 260 + + + + Build CIA + + + + + + + + Destination: + + + + + + + + + + + 0 + 0 + + + + ... + + + + + + + + + Build Type + + + + + + Standard + + + true + + + + + + + true + + + Recommended for general use.<br>Decrypted CIA with decrypted contents and standard ticket. + + + + + + + Legit with standard ticket ("pirate legit") + + + + + + + true + + + Encrypted CIA with legit TMD, encrypted contents and standard ticket. + + + + + + + Legit + + + + + + + true + + + Encrypted CIA with legit TMD, encrypted contents and legit ticket.<br>WARNING: Legit ticket may include console identifying information! + + + + + + + + + + Qt::Vertical + + + + + + + QDialogButtonBox::Ok|QDialogButtonBox::Cancel + + + + + + + + diff --git a/src/frontend/import_dialog.cpp b/src/frontend/import_dialog.cpp index 26a5d11..a9e6ba5 100644 --- a/src/frontend/import_dialog.cpp +++ b/src/frontend/import_dialog.cpp @@ -23,6 +23,7 @@ #include "common/logging/log.h" #include "common/progress_callback.h" #include "common/scope_exit.h" +#include "frontend/cia_build_dialog.h" #include "frontend/helpers/multi_job.h" #include "frontend/helpers/simple_job.h" #include "frontend/import_dialog.h" @@ -788,18 +789,21 @@ void ImportDialog::StartBatchDumpingCXI() { // CIA building void ImportDialog::StartBuildingCIASingle(const Core::ContentSpecifier& specifier) { - const QString path = QFileDialog::getSaveFileName(this, tr("Build CIA"), last_build_cia_path, - tr("CTR Importable Archive (*.cia)")); - if (path.isEmpty()) { + CIABuildDialog dialog(this, + /*is_dir*/ false, + /*is_nand*/ specifier.type == Core::ContentType::SystemTitle, + /*enable_legit*/ importer.CanBuildLegitCIA(specifier), + last_build_cia_path); + if (dialog.exec() != QDialog::Accepted) { return; } + const auto& [path, type] = dialog.GetResults(); last_build_cia_path = QFileInfo(path).path(); auto* job = new SimpleJob( this, - [this, specifier, path](const Common::ProgressCallback& callback) { - return importer.BuildCIA(Core::CIABuildType::Standard, specifier, path.toStdString(), - callback); + [this, specifier, path = path, type = type](const Common::ProgressCallback& callback) { + return importer.BuildCIA(type, specifier, path.toStdString(), callback); }, [this] { importer.AbortBuildCIA(); }); RunSimpleJob(job); @@ -835,11 +839,19 @@ void ImportDialog::StartBatchBuildingCIA() { to_import.erase(removed_iter, to_import.end()); - QString path = - QFileDialog::getExistingDirectory(this, tr("Batch Build CIA"), last_batch_build_cia_path); - if (path.isEmpty()) { + const bool is_nand = std::all_of(to_import.begin(), to_import.end(), + [](const Core::ContentSpecifier& specifier) { + return specifier.type == Core::ContentType::SystemTitle; + }); + const bool enable_legit = std::all_of(to_import.begin(), to_import.end(), + [this](const Core::ContentSpecifier& specifier) { + return importer.CanBuildLegitCIA(specifier); + }); + CIABuildDialog dialog(this, /*is_dir*/ true, is_nand, enable_legit, last_batch_build_cia_path); + if (dialog.exec() != QDialog::Accepted) { return; } + auto [path, type] = dialog.GetResults(); last_batch_build_cia_path = path; if (!path.endsWith(QChar::fromLatin1('/')) && !path.endsWith(QChar::fromLatin1('\\'))) { path.append(QStringLiteral("/")); @@ -853,10 +865,10 @@ void ImportDialog::StartBatchBuildingCIA() { }); auto* job = new MultiJob( this, importer, std::move(to_import), - [path](Core::SDMCImporter& importer, const Core::ContentSpecifier& specifier, - const Common::ProgressCallback& callback) { - return importer.BuildCIA(Core::CIABuildType::Standard, specifier, path.toStdString(), - callback, true); + [path = path, type = type](Core::SDMCImporter& importer, + const Core::ContentSpecifier& specifier, + const Common::ProgressCallback& callback) { + return importer.BuildCIA(type, specifier, path.toStdString(), callback, true); }, &Core::SDMCImporter::AbortBuildCIA); RunMultiJob(job, total_count, total_size);