From d0da4397316b201c3c4aa250dc6426d339047090 Mon Sep 17 00:00:00 2001 From: Pengfei Date: Mon, 9 Aug 2021 00:42:45 +0800 Subject: [PATCH] Implement contents check --- src/core/file_decryptor.h | 182 ++++++++++++----------- src/core/importer.cpp | 89 ++++++++++- src/core/importer.h | 9 ++ src/frontend/CMakeLists.txt | 2 + src/frontend/helpers/frontend_common.cpp | 21 +++ src/frontend/helpers/frontend_common.h | 9 ++ src/frontend/helpers/simple_job.cpp | 89 +++++++---- src/frontend/helpers/simple_job.h | 76 +++++----- src/frontend/import_dialog.cpp | 52 +------ src/frontend/import_dialog.h | 175 +++++++++++----------- src/frontend/title_info_dialog.cpp | 38 ++++- src/frontend/title_info_dialog.h | 10 +- 12 files changed, 450 insertions(+), 302 deletions(-) create mode 100644 src/frontend/helpers/frontend_common.cpp create mode 100644 src/frontend/helpers/frontend_common.h diff --git a/src/core/file_decryptor.h b/src/core/file_decryptor.h index a048068..0ed4450 100644 --- a/src/core/file_decryptor.h +++ b/src/core/file_decryptor.h @@ -1,89 +1,93 @@ -// Copyright 2019 threeSD Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include "common/common_types.h" -#include "common/progress_callback.h" -#include "common/thread.h" -#include "core/key/key.h" - -namespace Core { - -class CryptoFunc; - -/** - * Generalized file decryptor. - * Helper that reads, decrypts and writes data. This uses three threads to process the data - * and call progress callbacks occasionally. - */ -class FileDecryptor { -public: - explicit FileDecryptor(); - ~FileDecryptor(); - - /** - * Set up the crypto to use. - * Default / nullptr is plain copy. - */ - void SetCrypto(std::shared_ptr crypto); - - /** - * Crypts and writes a file. - * - * @param source Source file - * @param size Size to read, decrypt and write - * @param destination Destination file - * @param callback Progress callback. default for nothing. - */ - bool CryptAndWriteFile( - std::shared_ptr source, std::size_t size, - std::shared_ptr destination, - const Common::ProgressCallback& callback = [](u64, u64) {}); - - void DataReadLoop(); - void DataDecryptLoop(); - void DataWriteLoop(); - - void Abort(); - -private: - static constexpr std::size_t BufferSize = 16 * 1024; // 16 KB - - std::shared_ptr source; - std::shared_ptr destination; - std::shared_ptr crypto; - - std::size_t total_size{}; - - std::array, 3> buffers; - std::array data_read_event; - std::array data_decrypted_event; - std::array data_written_event; - - std::unique_ptr read_thread; - std::unique_ptr decrypt_thread; - std::unique_ptr write_thread; - - Common::ProgressCallback callback; - - Common::Event completion_event; - bool is_good{true}; - std::atomic_bool is_running{false}; -}; - -class CryptoFunc { -public: - virtual ~CryptoFunc(); - virtual void ProcessData(u8* data, std::size_t size) = 0; -}; - -std::shared_ptr CreateCTRCrypto(const Key::AESKey& key, const Key::AESKey& ctr, - std::size_t seek_pos = 0); - -} // namespace Core +// Copyright 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include "common/common_types.h" +#include "common/progress_callback.h" +#include "common/thread.h" +#include "core/key/key.h" + +namespace FileUtil { +class IOFile; +} + +namespace Core { + +class CryptoFunc; + +/** + * Generalized file decryptor. + * Helper that reads, decrypts and writes data. This uses three threads to process the data + * and call progress callbacks occasionally. + */ +class FileDecryptor { +public: + explicit FileDecryptor(); + ~FileDecryptor(); + + /** + * Set up the crypto to use. + * Default / nullptr is plain copy. + */ + void SetCrypto(std::shared_ptr crypto); + + /** + * Crypts and writes a file. + * + * @param source Source file + * @param size Size to read, decrypt and write + * @param destination Destination file + * @param callback Progress callback. default for nothing. + */ + bool CryptAndWriteFile( + std::shared_ptr source, std::size_t size, + std::shared_ptr destination, + const Common::ProgressCallback& callback = [](u64, u64) {}); + + void DataReadLoop(); + void DataDecryptLoop(); + void DataWriteLoop(); + + void Abort(); + +private: + static constexpr std::size_t BufferSize = 16 * 1024; // 16 KB + + std::shared_ptr source; + std::shared_ptr destination; + std::shared_ptr crypto; + + std::size_t total_size{}; + + std::array, 3> buffers; + std::array data_read_event; + std::array data_decrypted_event; + std::array data_written_event; + + std::unique_ptr read_thread; + std::unique_ptr decrypt_thread; + std::unique_ptr write_thread; + + Common::ProgressCallback callback; + + Common::Event completion_event; + bool is_good{true}; + std::atomic_bool is_running{false}; +}; + +class CryptoFunc { +public: + virtual ~CryptoFunc(); + virtual void ProcessData(u8* data, std::size_t size) = 0; +}; + +std::shared_ptr CreateCTRCrypto(const Key::AESKey& key, const Key::AESKey& ctr, + std::size_t seek_pos = 0); + +} // namespace Core diff --git a/src/core/importer.cpp b/src/core/importer.cpp index 303a268..36beeda 100644 --- a/src/core/importer.cpp +++ b/src/core/importer.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include +#include #include "common/assert.h" #include "common/common_paths.h" #include "common/file_util.h" @@ -110,6 +111,7 @@ bool SDMCImporter::IsGood() const { void SDMCImporter::AbortImporting() { sdmc_decryptor->Abort(); + file_decryptor.Abort(); } bool SDMCImporter::ImportContent(const ContentSpecifier& specifier, @@ -202,11 +204,10 @@ bool SDMCImporter::ImportNandTitle(const ContentSpecifier& specifier, const auto base_path = config.system_titles_path.substr(0, config.system_titles_path.size() - 6); - FileDecryptor decryptor; return ImportTitleGeneric( base_path, specifier, callback, - [&base_path, &decryptor](const std::string& filepath, - const Common::ProgressCallback& wrapped_callback) { + [this, &base_path](const std::string& filepath, + const Common::ProgressCallback& wrapped_callback) { const auto physical_path = base_path + filepath.substr(1); const auto citra_path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "00000000000000000000000000000000" + filepath; @@ -215,7 +216,7 @@ bool SDMCImporter::ImportNandTitle(const ContentSpecifier& specifier, return false; } // Crypto is not set: plain copy with progress. - return decryptor.CryptAndWriteFile( + return file_decryptor.CryptAndWriteFile( std::make_shared(physical_path, "rb"), FileUtil::GetSize(physical_path), std::make_shared(citra_path, "wb"), wrapped_callback); @@ -789,6 +790,86 @@ void SDMCImporter::AbortBuildCIA() { cia_builder->Abort(); } +// Removed actual writing of the data +class HashOnlyFile : public FileUtil::IOFile { +public: + explicit HashOnlyFile() = default; + ~HashOnlyFile() override = default; + + std::size_t Write(const char* data, std::size_t length) override { + sha.Update(reinterpret_cast(data), length); + return length; + } + + bool VerifyHash(const u8* hash) { + const bool ret = sha.Verify(hash); + sha.Restart(); + return ret; + } + +private: + CryptoPP::SHA256 sha; +}; + +bool SDMCImporter::CheckTitleContents(const ContentSpecifier& specifier, + const Common::ProgressCallback& callback) { + + if (!IsTitle(specifier.type)) { + LOG_ERROR(Core, "Unsupported specifier type {}", static_cast(specifier.type)); + return false; + } + + // Load TMD + TitleMetadata tmd; + if (!LoadTMD(specifier.type, specifier.id, tmd)) { + return false; + } + + Common::ProgressCallbackWrapper wrapper{specifier.maximum_size}; + + const bool is_nand = + specifier.type == ContentType::SystemTitle || specifier.type == ContentType::SystemApplet; + const auto physical_path = + is_nand ? fmt::format("{}{:08x}/{:08x}/content/", config.system_titles_path, + (specifier.id >> 32), (specifier.id & 0xFFFFFFFF)) + : fmt::format("{}title/{:08x}/{:08x}/content/", config.sdmc_path, + (specifier.id >> 32), (specifier.id & 0xFFFFFFFF)); + for (const auto& tmd_chunk : tmd.tmd_chunks) { + // For DLCs, there one subfolder every 256 titles, but in practice hardcoded 00000000 + // should be fine (also matches GodMode9) + const auto sub_folder = + specifier.type == ContentType::DLC ? physical_path + "00000000/" : physical_path; + const auto path = fmt::format("{}{:08x}.app", sub_folder, static_cast(tmd_chunk.id)); + if (!FileUtil::Exists(path)) { + if (static_cast(tmd_chunk.type) & 0x4000) { // optional + continue; + } + LOG_INFO(Core, "Content {:08x} does not exist", static_cast(tmd_chunk.id)); + return false; + } + + std::shared_ptr source_file; + if (is_nand) { + source_file = std::make_shared(path, "rb"); + } else { + const auto relative_path = path.substr(config.sdmc_path.size() - 1); + source_file = std::make_shared(config.sdmc_path, relative_path, "rb"); + } + std::shared_ptr dest_file = std::make_shared(); + if (!file_decryptor.CryptAndWriteFile(source_file, source_file->GetSize(), dest_file, + wrapper.Wrap(callback))) { + return false; + } + if (!dest_file->VerifyHash(tmd_chunk.hash.data())) { + LOG_INFO(Core, "Hash dismatch for content {:08x}", static_cast(tmd_chunk.id)); + return false; + } + } + + callback(specifier.maximum_size, specifier.maximum_size); + return true; +} + // Add a certain amount to the titles' maximum sizes, so that they are always larger than CIA sizes constexpr u64 TitleSizeAllowance = 0xA000; diff --git a/src/core/importer.h b/src/core/importer.h index d26faea..f9a3657 100644 --- a/src/core/importer.h +++ b/src/core/importer.h @@ -10,6 +10,7 @@ #include #include "common/common_types.h" #include "common/progress_callback.h" +#include "core/file_decryptor.h" #include "core/file_sys/cia_common.h" namespace Core { @@ -165,6 +166,13 @@ public: */ void AbortBuildCIA(); + /** + * Checks the contents of a title against its TMD hashes. + */ + bool CheckTitleContents( + const ContentSpecifier& specifier, + const Common::ProgressCallback& callback = [](u64, u64) {}); + /** * Gets a list of dumpable content specifiers. */ @@ -221,6 +229,7 @@ private: bool is_good{}; Config config; std::unique_ptr sdmc_decryptor; + FileDecryptor file_decryptor; // Used for CIA building std::unique_ptr cia_builder; diff --git a/src/frontend/CMakeLists.txt b/src/frontend/CMakeLists.txt index 78d73d5..13e5cb5 100644 --- a/src/frontend/CMakeLists.txt +++ b/src/frontend/CMakeLists.txt @@ -9,6 +9,8 @@ endif() file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/themes/*) add_executable(threeSD + helpers/frontend_common.cpp + helpers/frontend_common.h helpers/multi_job.cpp helpers/multi_job.h helpers/simple_job.cpp diff --git a/src/frontend/helpers/frontend_common.cpp b/src/frontend/helpers/frontend_common.cpp new file mode 100644 index 0000000..1632fce --- /dev/null +++ b/src/frontend/helpers/frontend_common.cpp @@ -0,0 +1,21 @@ +// Copyright 2021 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "frontend/helpers/frontend_common.h" + +QString ReadableByteSize(qulonglong size) { + static const std::array units = {QT_TR_NOOP("B"), QT_TR_NOOP("KiB"), + QT_TR_NOOP("MiB"), QT_TR_NOOP("GiB"), + QT_TR_NOOP("TiB"), QT_TR_NOOP("PiB")}; + if (size == 0) + return QStringLiteral("0"); + int digit_groups = std::min(static_cast(std::log10(size) / std::log10(1024)), + static_cast(units.size())); + return QStringLiteral("%L1 %2") + .arg(size / std::pow(1024, digit_groups), 0, 'f', 1) + .arg(QObject::tr(units[digit_groups], "FrontendCommon")); +} diff --git a/src/frontend/helpers/frontend_common.h b/src/frontend/helpers/frontend_common.h new file mode 100644 index 0000000..8f65138 --- /dev/null +++ b/src/frontend/helpers/frontend_common.h @@ -0,0 +1,9 @@ +// Copyright 2021 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +QString ReadableByteSize(qulonglong size); diff --git a/src/frontend/helpers/simple_job.cpp b/src/frontend/helpers/simple_job.cpp index 5d4df0d..e97a3f0 100644 --- a/src/frontend/helpers/simple_job.cpp +++ b/src/frontend/helpers/simple_job.cpp @@ -1,26 +1,63 @@ -// Copyright 2020 Pengfei Zhu -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "frontend/helpers/simple_job.h" - -SimpleJob::SimpleJob(QObject* parent, ExecuteFunc execute_, AbortFunc abort_) - : QThread(parent), execute(std::move(execute_)), abort(std::move(abort_)) {} - -SimpleJob::~SimpleJob() = default; - -void SimpleJob::run() { - const bool ret = - execute([this](u64 current, u64 total) { emit ProgressUpdated(current, total); }); - - if (ret || canceled) { - emit Completed(); - } else { - emit ErrorOccured(); - } -} - -void SimpleJob::Cancel() { - canceled = true; - abort(); -} +// Copyright 2020 Pengfei Zhu +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "frontend/helpers/frontend_common.h" +#include "frontend/helpers/simple_job.h" + +SimpleJob::SimpleJob(QObject* parent, ExecuteFunc execute_, AbortFunc abort_) + : QThread(parent), execute(std::move(execute_)), abort(std::move(abort_)) {} + +SimpleJob::~SimpleJob() = default; + +void SimpleJob::run() { + const bool ret = + execute([this](u64 current, u64 total) { emit ProgressUpdated(current, total); }); + + if (ret || canceled) { + emit Completed(canceled); + } else { + emit ErrorOccured(); + } +} + +void SimpleJob::Cancel() { + canceled = true; + abort(); +} + +void SimpleJob::StartWithProgressDialog(QWidget* widget) { + // We need to create the bar ourselves to circumvent an issue caused by modal ProgressDialog's + // event handling. + auto* bar = new QProgressBar(widget); + bar->setRange(0, 100); + bar->setValue(0); + + auto* dialog = new QProgressDialog(tr("Initializing..."), tr("Cancel"), 0, 0, widget); + dialog->setBar(bar); + dialog->setWindowModality(Qt::WindowModal); + dialog->setMinimumDuration(0); + + connect(this, &SimpleJob::ProgressUpdated, this, [bar, dialog](u64 current, u64 total) { + // Try to map total to int range + // This is equal to ceil(total / INT_MAX) + const u64 multiplier = + (total + std::numeric_limits::max() - 1) / std::numeric_limits::max(); + bar->setMaximum(static_cast(total / multiplier)); + bar->setValue(static_cast(current / multiplier)); + dialog->setLabelText( + tr("%1 / %2").arg(ReadableByteSize(current)).arg(ReadableByteSize(total))); + }); + connect(this, &SimpleJob::ErrorOccured, this, [widget, dialog] { + QMessageBox::critical(widget, tr("threeSD"), + tr("Operation failed. Please refer to the log.")); + dialog->hide(); + }); + connect(this, &SimpleJob::Completed, this, [dialog] { dialog->setValue(dialog->maximum()); }); + connect(dialog, &QProgressDialog::canceled, this, &SimpleJob::Cancel); + + start(); +} diff --git a/src/frontend/helpers/simple_job.h b/src/frontend/helpers/simple_job.h index 47f77b8..e39ee6d 100644 --- a/src/frontend/helpers/simple_job.h +++ b/src/frontend/helpers/simple_job.h @@ -1,37 +1,39 @@ -// Copyright 2019 threeSD Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include "common/common_types.h" -#include "common/progress_callback.h" - -/** - * Lightweight wrapper around QThread, for easy use with progressive jobs. - */ -class SimpleJob : public QThread { - Q_OBJECT - -public: - using ExecuteFunc = std::function; - using AbortFunc = std::function; - - explicit SimpleJob(QObject* parent, ExecuteFunc execute, AbortFunc abort); - ~SimpleJob() override; - - void run() override; - void Cancel(); - -signals: - void ProgressUpdated(u64 current, u64 total); - void Completed(); - void ErrorOccured(); - -private: - ExecuteFunc execute; - AbortFunc abort; - bool canceled{}; -}; +// Copyright 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "common/common_types.h" +#include "common/progress_callback.h" + +/** + * Lightweight wrapper around QThread, for easy use with progressive jobs. + */ +class SimpleJob : public QThread { + Q_OBJECT + +public: + using ExecuteFunc = std::function; + using AbortFunc = std::function; + + explicit SimpleJob(QObject* parent, ExecuteFunc execute, AbortFunc abort); + ~SimpleJob() override; + + void run() override; + void Cancel(); + + void StartWithProgressDialog(QWidget* widget); + +signals: + void ProgressUpdated(u64 current, u64 total); + void Completed(bool canceled); + void ErrorOccured(); + +private: + ExecuteFunc execute; + AbortFunc abort; + bool canceled{}; +}; diff --git a/src/frontend/import_dialog.cpp b/src/frontend/import_dialog.cpp index 954bc20..cbea23b 100644 --- a/src/frontend/import_dialog.cpp +++ b/src/frontend/import_dialog.cpp @@ -24,25 +24,13 @@ #include "common/progress_callback.h" #include "common/scope_exit.h" #include "frontend/cia_build_dialog.h" +#include "frontend/helpers/frontend_common.h" #include "frontend/helpers/multi_job.h" #include "frontend/helpers/simple_job.h" #include "frontend/import_dialog.h" #include "frontend/title_info_dialog.h" #include "ui_import_dialog.h" -static QString ReadableByteSize(qulonglong size) { - static const std::array units = {QT_TR_NOOP("B"), QT_TR_NOOP("KiB"), - QT_TR_NOOP("MiB"), QT_TR_NOOP("GiB"), - QT_TR_NOOP("TiB"), QT_TR_NOOP("PiB")}; - if (size == 0) - return QStringLiteral("0"); - int digit_groups = std::min(static_cast(std::log10(size) / std::log10(1024)), - static_cast(units.size())); - return QStringLiteral("%L1 %2") - .arg(size / std::pow(1024, digit_groups), 0, 'f', 1) - .arg(QObject::tr(units[digit_groups], "ImportDialog")); -} - // content type, singular name, plural name, icon name // clang-format off static constexpr std::array, 9> @@ -686,40 +674,6 @@ void ImportDialog::RunMultiJob(MultiJob* job, std::size_t total_count, u64 total job->start(); } -// Runs the job, opening a dialog to report its progress. -void ImportDialog::RunSimpleJob(SimpleJob* job) { - // We need to create the bar ourselves to circumvent an issue caused by modal ProgressDialog's - // event handling. - auto* bar = new QProgressBar(this); - bar->setRange(0, 100); - bar->setValue(0); - - auto* dialog = new QProgressDialog(tr("Initializing..."), tr("Cancel"), 0, 0, this); - dialog->setBar(bar); - dialog->setWindowModality(Qt::WindowModal); - dialog->setMinimumDuration(0); - - connect(job, &SimpleJob::ProgressUpdated, this, [bar, dialog](u64 current, u64 total) { - // Try to map total to int range - // This is equal to ceil(total / INT_MAX) - const u64 multiplier = - (total + std::numeric_limits::max() - 1) / std::numeric_limits::max(); - bar->setMaximum(static_cast(total / multiplier)); - bar->setValue(static_cast(current / multiplier)); - dialog->setLabelText( - tr("%1 / %2").arg(ReadableByteSize(current)).arg(ReadableByteSize(total))); - }); - connect(job, &SimpleJob::ErrorOccured, this, [this, dialog] { - QMessageBox::critical(this, tr("threeSD"), - tr("Operation failed. Please refer to the log.")); - dialog->hide(); - }); - connect(job, &SimpleJob::Completed, this, [dialog] { dialog->setValue(dialog->maximum()); }); - connect(dialog, &QProgressDialog::canceled, this, [job] { job->Cancel(); }); - - job->start(); -} - void ImportDialog::StartImporting() { UpdateSizeDisplay(); if (!ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)->isEnabled()) { @@ -755,7 +709,7 @@ void ImportDialog::StartDumpingCXISingle(const Core::ContentSpecifier& specifier return importer->DumpCXI(specifier, path.toStdString(), callback); }, [this] { importer->AbortDumpCXI(); }); - RunSimpleJob(job); + job->StartWithProgressDialog(this); } void ImportDialog::StartBatchDumpingCXI() { @@ -830,7 +784,7 @@ void ImportDialog::StartBuildingCIASingle(const Core::ContentSpecifier& specifie return importer->BuildCIA(type, specifier, path.toStdString(), callback); }, [this] { importer->AbortBuildCIA(); }); - RunSimpleJob(job); + job->StartWithProgressDialog(this); } void ImportDialog::StartBatchBuildingCIA() { diff --git a/src/frontend/import_dialog.h b/src/frontend/import_dialog.h index 8f77dac..74b5096 100644 --- a/src/frontend/import_dialog.h +++ b/src/frontend/import_dialog.h @@ -1,88 +1,87 @@ -// Copyright 2019 threeSD Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include -#include "core/file_sys/ncch_container.h" -#include "core/importer.h" - -class AdvancedMenu; -class MultiJob; -class SimpleJob; -class QTreeWidgetItem; - -namespace Ui { -class ImportDialog; -} - -class ImportDialog : public QDialog { - Q_OBJECT - -public: - explicit ImportDialog(QWidget* parent, const Core::Config& config); - ~ImportDialog() override; - -private: - void RelistContent(); - void RepopulateContent(); - void UpdateSizeDisplay(); - void UpdateItemCheckState(QTreeWidgetItem* item); - std::vector GetSelectedContentList(); - - 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, QString replace_name = {}, - QPixmap replace_icon = {}); - - Core::ContentSpecifier SpecifierFromItem(QTreeWidgetItem* item) const; - - void OnContextMenu(const QPoint& point); - void ShowAdvancedMenu(); - - void RunMultiJob(MultiJob* job, std::size_t total_count, u64 total_size); - void RunSimpleJob(SimpleJob* job); - - void StartImporting(); - - void StartDumpingCXISingle(const Core::ContentSpecifier& content); - QString last_dump_cxi_path; // Used for recording last path in StartDumpingCXISingle - void StartBatchDumpingCXI(); - QString last_batch_dump_cxi_path; // Used for recording last path in StartBatchDumpingCXI - - void StartBuildingCIASingle(const Core::ContentSpecifier& content); - QString last_build_cia_path; // Used for recording last path in StartBuildingCIASingle - void StartBatchBuildingCIA(); - QString last_batch_build_cia_path; // Used for recording last path in StartBatchBuildingCIA - - std::unique_ptr ui; - - std::unique_ptr importer; - const Core::Config config; - - std::vector contents; - u64 total_selected_size = 0; - - // HACK: To tell whether the checkbox state change is a programmatic trigger - // TODO: Is there a more elegant way of doing the same? - bool program_trigger = false; - - // HACK: Block advanced menu trigger once. - bool block_advanced_menu = false; - friend class AdvancedMenu; - - // Whether the System Archive / System Data warning has been shown - bool system_warning_shown = false; - // Whether the Applets warning has been shown - bool applet_warning_shown = false; - - // TODO: Why this won't work as locals? - Core::ContentSpecifier current_content = {}; - std::size_t current_count = 0; -}; +// Copyright 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include "core/file_sys/ncch_container.h" +#include "core/importer.h" + +class AdvancedMenu; +class MultiJob; +class SimpleJob; +class QTreeWidgetItem; + +namespace Ui { +class ImportDialog; +} + +class ImportDialog : public QDialog { + Q_OBJECT + +public: + explicit ImportDialog(QWidget* parent, const Core::Config& config); + ~ImportDialog() override; + +private: + void RelistContent(); + void RepopulateContent(); + void UpdateSizeDisplay(); + void UpdateItemCheckState(QTreeWidgetItem* item); + std::vector GetSelectedContentList(); + + 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, QString replace_name = {}, + QPixmap replace_icon = {}); + + Core::ContentSpecifier SpecifierFromItem(QTreeWidgetItem* item) const; + + void OnContextMenu(const QPoint& point); + void ShowAdvancedMenu(); + + void RunMultiJob(MultiJob* job, std::size_t total_count, u64 total_size); + + void StartImporting(); + + void StartDumpingCXISingle(const Core::ContentSpecifier& content); + QString last_dump_cxi_path; // Used for recording last path in StartDumpingCXISingle + void StartBatchDumpingCXI(); + QString last_batch_dump_cxi_path; // Used for recording last path in StartBatchDumpingCXI + + void StartBuildingCIASingle(const Core::ContentSpecifier& content); + QString last_build_cia_path; // Used for recording last path in StartBuildingCIASingle + void StartBatchBuildingCIA(); + QString last_batch_build_cia_path; // Used for recording last path in StartBatchBuildingCIA + + std::unique_ptr ui; + + std::unique_ptr importer; + const Core::Config config; + + std::vector contents; + u64 total_selected_size = 0; + + // HACK: To tell whether the checkbox state change is a programmatic trigger + // TODO: Is there a more elegant way of doing the same? + bool program_trigger = false; + + // HACK: Block advanced menu trigger once. + bool block_advanced_menu = false; + friend class AdvancedMenu; + + // Whether the System Archive / System Data warning has been shown + bool system_warning_shown = false; + // Whether the Applets warning has been shown + bool applet_warning_shown = false; + + // TODO: Why this won't work as locals? + Core::ContentSpecifier current_content = {}; + std::size_t current_count = 0; +}; diff --git a/src/frontend/title_info_dialog.cpp b/src/frontend/title_info_dialog.cpp index 3b9d444..89f9095 100644 --- a/src/frontend/title_info_dialog.cpp +++ b/src/frontend/title_info_dialog.cpp @@ -11,28 +11,28 @@ #include "core/file_sys/ncch_container.h" #include "core/file_sys/title_metadata.h" #include "core/importer.h" +#include "frontend/helpers/simple_job.h" #include "frontend/title_info_dialog.h" #include "ui_title_info_dialog.h" TitleInfoDialog::TitleInfoDialog(QWidget* parent, const Core::Config& config, - Core::SDMCImporter& importer, - const Core::ContentSpecifier& specifier) - : QDialog(parent), ui(std::make_unique()) { + Core::SDMCImporter& importer_, Core::ContentSpecifier specifier_) + : QDialog(parent), ui(std::make_unique()), importer(importer_), + specifier(std::move(specifier_)) { ui->setupUi(this); const double scale = qApp->desktop()->logicalDpiX() / 96.0; resize(static_cast(width() * scale), static_cast(height() * scale)); - LoadInfo(config, importer, specifier); + LoadInfo(config); connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &TitleInfoDialog::accept); } TitleInfoDialog::~TitleInfoDialog() = default; -void TitleInfoDialog::LoadInfo(const Core::Config& config, Core::SDMCImporter& importer, - const Core::ContentSpecifier& specifier) { +void TitleInfoDialog::LoadInfo(const Core::Config& config) { Core::TitleMetadata tmd; if (!importer.LoadTMD(specifier, tmd)) { QMessageBox::warning(this, tr("threeSD"), tr("Could not load title information.")); @@ -100,6 +100,8 @@ void TitleInfoDialog::LoadInfo(const Core::Config& config, Core::SDMCImporter& i } else { ui->ticketCheckLabel->setText(tr("Missing")); } + connect(ui->contentsCheckButton, &QPushButton::clicked, this, + &TitleInfoDialog::ExecuteContentsCheck); // Load SMDH std::vector smdh_buffer; @@ -170,3 +172,27 @@ void TitleInfoDialog::UpdateNames() { ui->publisherLineEdit->setText( QString::fromStdString(Common::UTF16BufferToUTF8(title.publisher))); } + +void TitleInfoDialog::ExecuteContentsCheck() { + auto* job = new SimpleJob( + this, + [this](const Common::ProgressCallback& callback) { + contents_check_result = importer.CheckTitleContents(specifier, callback); + return true; + }, + [this] { importer.AbortImporting(); }); + connect(job, &SimpleJob::Completed, this, [this](bool canceled) { + if (canceled) { + return; + } + + ui->contentsCheckButton->setVisible(false); + ui->contentsCheckLabel->setVisible(true); + if (contents_check_result) { + ui->contentsCheckLabel->setText(tr("OK")); + } else { + ui->contentsCheckLabel->setText(tr("Failed")); + } + }); + job->StartWithProgressDialog(this); +} diff --git a/src/frontend/title_info_dialog.h b/src/frontend/title_info_dialog.h index d5da1d5..6d36477 100644 --- a/src/frontend/title_info_dialog.h +++ b/src/frontend/title_info_dialog.h @@ -22,15 +22,19 @@ class TitleInfoDialog : public QDialog { public: explicit TitleInfoDialog(QWidget* parent, const Core::Config& config, - Core::SDMCImporter& importer, const Core::ContentSpecifier& specifier); + Core::SDMCImporter& importer, Core::ContentSpecifier specifier); ~TitleInfoDialog(); private: - void LoadInfo(const Core::Config& config, Core::SDMCImporter& importer, - const Core::ContentSpecifier& specifier); + void LoadInfo(const Core::Config& config); void InitializeLanguageComboBox(); void UpdateNames(); + void ExecuteContentsCheck(); std::unique_ptr ui; + + Core::SDMCImporter& importer; + const Core::ContentSpecifier specifier; Core::SMDH smdh{}; + bool contents_check_result = false; };