Implement contents check

This commit is contained in:
Pengfei
2021-08-09 00:42:45 +08:00
parent ea24716a38
commit d0da439731
12 changed files with 450 additions and 302 deletions
+93 -89
View File
@@ -1,89 +1,93 @@
// Copyright 2019 threeSD Project // Copyright 2019 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <memory> #include <memory>
#include <string> #include <string>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/progress_callback.h" #include "common/progress_callback.h"
#include "common/thread.h" #include "common/thread.h"
#include "core/key/key.h" #include "core/key/key.h"
namespace Core { namespace FileUtil {
class IOFile;
class CryptoFunc; }
/** namespace Core {
* Generalized file decryptor.
* Helper that reads, decrypts and writes data. This uses three threads to process the data class CryptoFunc;
* and call progress callbacks occasionally.
*/ /**
class FileDecryptor { * Generalized file decryptor.
public: * Helper that reads, decrypts and writes data. This uses three threads to process the data
explicit FileDecryptor(); * and call progress callbacks occasionally.
~FileDecryptor(); */
class FileDecryptor {
/** public:
* Set up the crypto to use. explicit FileDecryptor();
* Default / nullptr is plain copy. ~FileDecryptor();
*/
void SetCrypto(std::shared_ptr<CryptoFunc> crypto); /**
* Set up the crypto to use.
/** * Default / nullptr is plain copy.
* Crypts and writes a file. */
* void SetCrypto(std::shared_ptr<CryptoFunc> crypto);
* @param source Source file
* @param size Size to read, decrypt and write /**
* @param destination Destination file * Crypts and writes a file.
* @param callback Progress callback. default for nothing. *
*/ * @param source Source file
bool CryptAndWriteFile( * @param size Size to read, decrypt and write
std::shared_ptr<FileUtil::IOFile> source, std::size_t size, * @param destination Destination file
std::shared_ptr<FileUtil::IOFile> destination, * @param callback Progress callback. default for nothing.
const Common::ProgressCallback& callback = [](u64, u64) {}); */
bool CryptAndWriteFile(
void DataReadLoop(); std::shared_ptr<FileUtil::IOFile> source, std::size_t size,
void DataDecryptLoop(); std::shared_ptr<FileUtil::IOFile> destination,
void DataWriteLoop(); const Common::ProgressCallback& callback = [](u64, u64) {});
void Abort(); void DataReadLoop();
void DataDecryptLoop();
private: void DataWriteLoop();
static constexpr std::size_t BufferSize = 16 * 1024; // 16 KB
void Abort();
std::shared_ptr<FileUtil::IOFile> source;
std::shared_ptr<FileUtil::IOFile> destination; private:
std::shared_ptr<CryptoFunc> crypto; static constexpr std::size_t BufferSize = 16 * 1024; // 16 KB
std::size_t total_size{}; std::shared_ptr<FileUtil::IOFile> source;
std::shared_ptr<FileUtil::IOFile> destination;
std::array<std::array<u8, BufferSize>, 3> buffers; std::shared_ptr<CryptoFunc> crypto;
std::array<Common::Event, 3> data_read_event;
std::array<Common::Event, 3> data_decrypted_event; std::size_t total_size{};
std::array<Common::Event, 3> data_written_event;
std::array<std::array<u8, BufferSize>, 3> buffers;
std::unique_ptr<std::thread> read_thread; std::array<Common::Event, 3> data_read_event;
std::unique_ptr<std::thread> decrypt_thread; std::array<Common::Event, 3> data_decrypted_event;
std::unique_ptr<std::thread> write_thread; std::array<Common::Event, 3> data_written_event;
Common::ProgressCallback callback; std::unique_ptr<std::thread> read_thread;
std::unique_ptr<std::thread> decrypt_thread;
Common::Event completion_event; std::unique_ptr<std::thread> write_thread;
bool is_good{true};
std::atomic_bool is_running{false}; Common::ProgressCallback callback;
};
Common::Event completion_event;
class CryptoFunc { bool is_good{true};
public: std::atomic_bool is_running{false};
virtual ~CryptoFunc(); };
virtual void ProcessData(u8* data, std::size_t size) = 0;
}; class CryptoFunc {
public:
std::shared_ptr<CryptoFunc> CreateCTRCrypto(const Key::AESKey& key, const Key::AESKey& ctr, virtual ~CryptoFunc();
std::size_t seek_pos = 0); virtual void ProcessData(u8* data, std::size_t size) = 0;
};
} // namespace Core
std::shared_ptr<CryptoFunc> CreateCTRCrypto(const Key::AESKey& key, const Key::AESKey& ctr,
std::size_t seek_pos = 0);
} // namespace Core
+85 -4
View File
@@ -3,6 +3,7 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <regex> #include <regex>
#include <cryptopp/sha.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/common_paths.h" #include "common/common_paths.h"
#include "common/file_util.h" #include "common/file_util.h"
@@ -110,6 +111,7 @@ bool SDMCImporter::IsGood() const {
void SDMCImporter::AbortImporting() { void SDMCImporter::AbortImporting() {
sdmc_decryptor->Abort(); sdmc_decryptor->Abort();
file_decryptor.Abort();
} }
bool SDMCImporter::ImportContent(const ContentSpecifier& specifier, bool SDMCImporter::ImportContent(const ContentSpecifier& specifier,
@@ -202,11 +204,10 @@ bool SDMCImporter::ImportNandTitle(const ContentSpecifier& specifier,
const auto base_path = const auto base_path =
config.system_titles_path.substr(0, config.system_titles_path.size() - 6); config.system_titles_path.substr(0, config.system_titles_path.size() - 6);
FileDecryptor decryptor;
return ImportTitleGeneric( return ImportTitleGeneric(
base_path, specifier, callback, base_path, specifier, callback,
[&base_path, &decryptor](const std::string& filepath, [this, &base_path](const std::string& filepath,
const Common::ProgressCallback& wrapped_callback) { const Common::ProgressCallback& wrapped_callback) {
const auto physical_path = base_path + filepath.substr(1); const auto physical_path = base_path + filepath.substr(1);
const auto citra_path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + const auto citra_path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"00000000000000000000000000000000" + filepath; "00000000000000000000000000000000" + filepath;
@@ -215,7 +216,7 @@ bool SDMCImporter::ImportNandTitle(const ContentSpecifier& specifier,
return false; return false;
} }
// Crypto is not set: plain copy with progress. // Crypto is not set: plain copy with progress.
return decryptor.CryptAndWriteFile( return file_decryptor.CryptAndWriteFile(
std::make_shared<FileUtil::IOFile>(physical_path, "rb"), std::make_shared<FileUtil::IOFile>(physical_path, "rb"),
FileUtil::GetSize(physical_path), FileUtil::GetSize(physical_path),
std::make_shared<FileUtil::IOFile>(citra_path, "wb"), wrapped_callback); std::make_shared<FileUtil::IOFile>(citra_path, "wb"), wrapped_callback);
@@ -789,6 +790,86 @@ void SDMCImporter::AbortBuildCIA() {
cia_builder->Abort(); 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<const CryptoPP::byte*>(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<int>(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<u32>(tmd_chunk.id));
if (!FileUtil::Exists(path)) {
if (static_cast<u16>(tmd_chunk.type) & 0x4000) { // optional
continue;
}
LOG_INFO(Core, "Content {:08x} does not exist", static_cast<u32>(tmd_chunk.id));
return false;
}
std::shared_ptr<FileUtil::IOFile> source_file;
if (is_nand) {
source_file = std::make_shared<FileUtil::IOFile>(path, "rb");
} else {
const auto relative_path = path.substr(config.sdmc_path.size() - 1);
source_file = std::make_shared<SDMCFile>(config.sdmc_path, relative_path, "rb");
}
std::shared_ptr<HashOnlyFile> dest_file = std::make_shared<HashOnlyFile>();
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<u32>(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 // Add a certain amount to the titles' maximum sizes, so that they are always larger than CIA sizes
constexpr u64 TitleSizeAllowance = 0xA000; constexpr u64 TitleSizeAllowance = 0xA000;
+9
View File
@@ -10,6 +10,7 @@
#include <vector> #include <vector>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/progress_callback.h" #include "common/progress_callback.h"
#include "core/file_decryptor.h"
#include "core/file_sys/cia_common.h" #include "core/file_sys/cia_common.h"
namespace Core { namespace Core {
@@ -165,6 +166,13 @@ public:
*/ */
void AbortBuildCIA(); 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. * Gets a list of dumpable content specifiers.
*/ */
@@ -221,6 +229,7 @@ private:
bool is_good{}; bool is_good{};
Config config; Config config;
std::unique_ptr<SDMCDecryptor> sdmc_decryptor; std::unique_ptr<SDMCDecryptor> sdmc_decryptor;
FileDecryptor file_decryptor;
// Used for CIA building // Used for CIA building
std::unique_ptr<CIABuilder> cia_builder; std::unique_ptr<CIABuilder> cia_builder;
+2
View File
@@ -9,6 +9,8 @@ endif()
file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/themes/*) file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/themes/*)
add_executable(threeSD add_executable(threeSD
helpers/frontend_common.cpp
helpers/frontend_common.h
helpers/multi_job.cpp helpers/multi_job.cpp
helpers/multi_job.h helpers/multi_job.h
helpers/simple_job.cpp helpers/simple_job.cpp
+21
View File
@@ -0,0 +1,21 @@
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <array>
#include <cmath>
#include <QObject>
#include "frontend/helpers/frontend_common.h"
QString ReadableByteSize(qulonglong size) {
static const std::array<const char*, 6> 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<int>(static_cast<int>(std::log10(size) / std::log10(1024)),
static_cast<int>(units.size()));
return QStringLiteral("%L1 %2")
.arg(size / std::pow(1024, digit_groups), 0, 'f', 1)
.arg(QObject::tr(units[digit_groups], "FrontendCommon"));
}
+9
View File
@@ -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>
QString ReadableByteSize(qulonglong size);
+63 -26
View File
@@ -1,26 +1,63 @@
// Copyright 2020 Pengfei Zhu // Copyright 2020 Pengfei Zhu
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include "frontend/helpers/simple_job.h" #include <QMessageBox>
#include <QProgressBar>
SimpleJob::SimpleJob(QObject* parent, ExecuteFunc execute_, AbortFunc abort_) #include <QProgressDialog>
: QThread(parent), execute(std::move(execute_)), abort(std::move(abort_)) {} #include "frontend/helpers/frontend_common.h"
#include "frontend/helpers/simple_job.h"
SimpleJob::~SimpleJob() = default;
SimpleJob::SimpleJob(QObject* parent, ExecuteFunc execute_, AbortFunc abort_)
void SimpleJob::run() { : QThread(parent), execute(std::move(execute_)), abort(std::move(abort_)) {}
const bool ret =
execute([this](u64 current, u64 total) { emit ProgressUpdated(current, total); }); SimpleJob::~SimpleJob() = default;
if (ret || canceled) { void SimpleJob::run() {
emit Completed(); const bool ret =
} else { execute([this](u64 current, u64 total) { emit ProgressUpdated(current, total); });
emit ErrorOccured();
} if (ret || canceled) {
} emit Completed(canceled);
} else {
void SimpleJob::Cancel() { emit ErrorOccured();
canceled = true; }
abort(); }
}
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<int>::max() - 1) / std::numeric_limits<int>::max();
bar->setMaximum(static_cast<int>(total / multiplier));
bar->setValue(static_cast<int>(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();
}
+39 -37
View File
@@ -1,37 +1,39 @@
// Copyright 2019 threeSD Project // Copyright 2019 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include <functional> #include <functional>
#include <QThread> #include <QThread>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/progress_callback.h" #include "common/progress_callback.h"
/** /**
* Lightweight wrapper around QThread, for easy use with progressive jobs. * Lightweight wrapper around QThread, for easy use with progressive jobs.
*/ */
class SimpleJob : public QThread { class SimpleJob : public QThread {
Q_OBJECT Q_OBJECT
public: public:
using ExecuteFunc = std::function<bool(const Common::ProgressCallback&)>; using ExecuteFunc = std::function<bool(const Common::ProgressCallback&)>;
using AbortFunc = std::function<void()>; using AbortFunc = std::function<void()>;
explicit SimpleJob(QObject* parent, ExecuteFunc execute, AbortFunc abort); explicit SimpleJob(QObject* parent, ExecuteFunc execute, AbortFunc abort);
~SimpleJob() override; ~SimpleJob() override;
void run() override; void run() override;
void Cancel(); void Cancel();
signals: void StartWithProgressDialog(QWidget* widget);
void ProgressUpdated(u64 current, u64 total);
void Completed(); signals:
void ErrorOccured(); void ProgressUpdated(u64 current, u64 total);
void Completed(bool canceled);
private: void ErrorOccured();
ExecuteFunc execute;
AbortFunc abort; private:
bool canceled{}; ExecuteFunc execute;
}; AbortFunc abort;
bool canceled{};
};
+3 -49
View File
@@ -24,25 +24,13 @@
#include "common/progress_callback.h" #include "common/progress_callback.h"
#include "common/scope_exit.h" #include "common/scope_exit.h"
#include "frontend/cia_build_dialog.h" #include "frontend/cia_build_dialog.h"
#include "frontend/helpers/frontend_common.h"
#include "frontend/helpers/multi_job.h" #include "frontend/helpers/multi_job.h"
#include "frontend/helpers/simple_job.h" #include "frontend/helpers/simple_job.h"
#include "frontend/import_dialog.h" #include "frontend/import_dialog.h"
#include "frontend/title_info_dialog.h" #include "frontend/title_info_dialog.h"
#include "ui_import_dialog.h" #include "ui_import_dialog.h"
static QString ReadableByteSize(qulonglong size) {
static const std::array<const char*, 6> 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<int>(static_cast<int>(std::log10(size) / std::log10(1024)),
static_cast<int>(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 // content type, singular name, plural name, icon name
// clang-format off // clang-format off
static constexpr std::array<std::tuple<Core::ContentType, const char*, const char*, const char*>, 9> static constexpr std::array<std::tuple<Core::ContentType, const char*, const char*, const char*>, 9>
@@ -686,40 +674,6 @@ void ImportDialog::RunMultiJob(MultiJob* job, std::size_t total_count, u64 total
job->start(); 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<int>::max() - 1) / std::numeric_limits<int>::max();
bar->setMaximum(static_cast<int>(total / multiplier));
bar->setValue(static_cast<int>(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() { void ImportDialog::StartImporting() {
UpdateSizeDisplay(); UpdateSizeDisplay();
if (!ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)->isEnabled()) { 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); return importer->DumpCXI(specifier, path.toStdString(), callback);
}, },
[this] { importer->AbortDumpCXI(); }); [this] { importer->AbortDumpCXI(); });
RunSimpleJob(job); job->StartWithProgressDialog(this);
} }
void ImportDialog::StartBatchDumpingCXI() { void ImportDialog::StartBatchDumpingCXI() {
@@ -830,7 +784,7 @@ void ImportDialog::StartBuildingCIASingle(const Core::ContentSpecifier& specifie
return importer->BuildCIA(type, specifier, path.toStdString(), callback); return importer->BuildCIA(type, specifier, path.toStdString(), callback);
}, },
[this] { importer->AbortBuildCIA(); }); [this] { importer->AbortBuildCIA(); });
RunSimpleJob(job); job->StartWithProgressDialog(this);
} }
void ImportDialog::StartBatchBuildingCIA() { void ImportDialog::StartBatchBuildingCIA() {
+87 -88
View File
@@ -1,88 +1,87 @@
// Copyright 2019 threeSD Project // Copyright 2019 threeSD Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include <QDialog> #include <QDialog>
#include <QPixmap> #include <QPixmap>
#include "core/file_sys/ncch_container.h" #include "core/file_sys/ncch_container.h"
#include "core/importer.h" #include "core/importer.h"
class AdvancedMenu; class AdvancedMenu;
class MultiJob; class MultiJob;
class SimpleJob; class SimpleJob;
class QTreeWidgetItem; class QTreeWidgetItem;
namespace Ui { namespace Ui {
class ImportDialog; class ImportDialog;
} }
class ImportDialog : public QDialog { class ImportDialog : public QDialog {
Q_OBJECT Q_OBJECT
public: public:
explicit ImportDialog(QWidget* parent, const Core::Config& config); explicit ImportDialog(QWidget* parent, const Core::Config& config);
~ImportDialog() override; ~ImportDialog() override;
private: private:
void RelistContent(); void RelistContent();
void RepopulateContent(); void RepopulateContent();
void UpdateSizeDisplay(); void UpdateSizeDisplay();
void UpdateItemCheckState(QTreeWidgetItem* item); void UpdateItemCheckState(QTreeWidgetItem* item);
std::vector<Core::ContentSpecifier> GetSelectedContentList(); std::vector<Core::ContentSpecifier> GetSelectedContentList();
void InsertTopLevelItem(const QString& text, QPixmap icon = {}); void InsertTopLevelItem(const QString& text, QPixmap icon = {});
// When replace_name and replace_icon are present they are used instead of those in `content`. // 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, void 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 = {});
Core::ContentSpecifier SpecifierFromItem(QTreeWidgetItem* item) const; Core::ContentSpecifier SpecifierFromItem(QTreeWidgetItem* item) const;
void OnContextMenu(const QPoint& point); void OnContextMenu(const QPoint& point);
void ShowAdvancedMenu(); void ShowAdvancedMenu();
void RunMultiJob(MultiJob* job, std::size_t total_count, u64 total_size); void RunMultiJob(MultiJob* job, std::size_t total_count, u64 total_size);
void RunSimpleJob(SimpleJob* job);
void StartImporting();
void StartImporting();
void StartDumpingCXISingle(const Core::ContentSpecifier& content);
void StartDumpingCXISingle(const Core::ContentSpecifier& content); QString last_dump_cxi_path; // Used for recording last path in StartDumpingCXISingle
QString last_dump_cxi_path; // Used for recording last path in StartDumpingCXISingle void StartBatchDumpingCXI();
void StartBatchDumpingCXI(); QString last_batch_dump_cxi_path; // Used for recording last path in StartBatchDumpingCXI
QString last_batch_dump_cxi_path; // Used for recording last path in StartBatchDumpingCXI
void StartBuildingCIASingle(const Core::ContentSpecifier& content);
void StartBuildingCIASingle(const Core::ContentSpecifier& content); QString last_build_cia_path; // Used for recording last path in StartBuildingCIASingle
QString last_build_cia_path; // Used for recording last path in StartBuildingCIASingle void StartBatchBuildingCIA();
void StartBatchBuildingCIA(); QString last_batch_build_cia_path; // Used for recording last path in StartBatchBuildingCIA
QString last_batch_build_cia_path; // Used for recording last path in StartBatchBuildingCIA
std::unique_ptr<Ui::ImportDialog> ui;
std::unique_ptr<Ui::ImportDialog> ui;
std::unique_ptr<Core::SDMCImporter> importer;
std::unique_ptr<Core::SDMCImporter> importer; const Core::Config config;
const Core::Config config;
std::vector<Core::ContentSpecifier> contents;
std::vector<Core::ContentSpecifier> contents; u64 total_selected_size = 0;
u64 total_selected_size = 0;
// HACK: To tell whether the checkbox state change is a programmatic trigger
// HACK: To tell whether the checkbox state change is a programmatic trigger // TODO: Is there a more elegant way of doing the same?
// TODO: Is there a more elegant way of doing the same? bool program_trigger = false;
bool program_trigger = false;
// HACK: Block advanced menu trigger once.
// HACK: Block advanced menu trigger once. bool block_advanced_menu = false;
bool block_advanced_menu = false; friend class AdvancedMenu;
friend class AdvancedMenu;
// Whether the System Archive / System Data warning has been shown
// Whether the System Archive / System Data warning has been shown bool system_warning_shown = false;
bool system_warning_shown = false; // Whether the Applets warning has been shown
// Whether the Applets warning has been shown bool applet_warning_shown = false;
bool applet_warning_shown = false;
// TODO: Why this won't work as locals?
// TODO: Why this won't work as locals? Core::ContentSpecifier current_content = {};
Core::ContentSpecifier current_content = {}; std::size_t current_count = 0;
std::size_t current_count = 0; };
};
+32 -6
View File
@@ -11,28 +11,28 @@
#include "core/file_sys/ncch_container.h" #include "core/file_sys/ncch_container.h"
#include "core/file_sys/title_metadata.h" #include "core/file_sys/title_metadata.h"
#include "core/importer.h" #include "core/importer.h"
#include "frontend/helpers/simple_job.h"
#include "frontend/title_info_dialog.h" #include "frontend/title_info_dialog.h"
#include "ui_title_info_dialog.h" #include "ui_title_info_dialog.h"
TitleInfoDialog::TitleInfoDialog(QWidget* parent, const Core::Config& config, TitleInfoDialog::TitleInfoDialog(QWidget* parent, const Core::Config& config,
Core::SDMCImporter& importer, Core::SDMCImporter& importer_, Core::ContentSpecifier specifier_)
const Core::ContentSpecifier& specifier) : QDialog(parent), ui(std::make_unique<Ui::TitleInfoDialog>()), importer(importer_),
: QDialog(parent), ui(std::make_unique<Ui::TitleInfoDialog>()) { specifier(std::move(specifier_)) {
ui->setupUi(this); ui->setupUi(this);
const double scale = qApp->desktop()->logicalDpiX() / 96.0; const double scale = qApp->desktop()->logicalDpiX() / 96.0;
resize(static_cast<int>(width() * scale), static_cast<int>(height() * scale)); resize(static_cast<int>(width() * scale), static_cast<int>(height() * scale));
LoadInfo(config, importer, specifier); LoadInfo(config);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &TitleInfoDialog::accept); connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &TitleInfoDialog::accept);
} }
TitleInfoDialog::~TitleInfoDialog() = default; TitleInfoDialog::~TitleInfoDialog() = default;
void TitleInfoDialog::LoadInfo(const Core::Config& config, Core::SDMCImporter& importer, void TitleInfoDialog::LoadInfo(const Core::Config& config) {
const Core::ContentSpecifier& specifier) {
Core::TitleMetadata tmd; Core::TitleMetadata tmd;
if (!importer.LoadTMD(specifier, tmd)) { if (!importer.LoadTMD(specifier, tmd)) {
QMessageBox::warning(this, tr("threeSD"), tr("Could not load title information.")); 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 { } else {
ui->ticketCheckLabel->setText(tr("Missing")); ui->ticketCheckLabel->setText(tr("Missing"));
} }
connect(ui->contentsCheckButton, &QPushButton::clicked, this,
&TitleInfoDialog::ExecuteContentsCheck);
// Load SMDH // Load SMDH
std::vector<u8> smdh_buffer; std::vector<u8> smdh_buffer;
@@ -170,3 +172,27 @@ void TitleInfoDialog::UpdateNames() {
ui->publisherLineEdit->setText( ui->publisherLineEdit->setText(
QString::fromStdString(Common::UTF16BufferToUTF8(title.publisher))); 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);
}
+7 -3
View File
@@ -22,15 +22,19 @@ class TitleInfoDialog : public QDialog {
public: public:
explicit TitleInfoDialog(QWidget* parent, const Core::Config& config, explicit TitleInfoDialog(QWidget* parent, const Core::Config& config,
Core::SDMCImporter& importer, const Core::ContentSpecifier& specifier); Core::SDMCImporter& importer, Core::ContentSpecifier specifier);
~TitleInfoDialog(); ~TitleInfoDialog();
private: private:
void LoadInfo(const Core::Config& config, Core::SDMCImporter& importer, void LoadInfo(const Core::Config& config);
const Core::ContentSpecifier& specifier);
void InitializeLanguageComboBox(); void InitializeLanguageComboBox();
void UpdateNames(); void UpdateNames();
void ExecuteContentsCheck();
std::unique_ptr<Ui::TitleInfoDialog> ui; std::unique_ptr<Ui::TitleInfoDialog> ui;
Core::SDMCImporter& importer;
const Core::ContentSpecifier specifier;
Core::SMDH smdh{}; Core::SMDH smdh{};
bool contents_check_result = false;
}; };