Add CIABuildDialog

This commit is contained in:
Pengfei
2021-08-04 00:35:01 +08:00
parent bbcad43d1c
commit 1f0a969cb7
7 changed files with 310 additions and 19 deletions
+28 -4
View File
@@ -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<std::string_view, 3> 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<FileUtil::IOFile>(boot_content_path, "rb"));
destination.append(GetTitleFileName(ncch)).append(".cia");
destination.append(GetTitleFileName(ncch))
.append(BuildTypeSuffixes.at(static_cast<std::size_t>(build_type)));
} else {
const auto relative_path = boot_content_path.substr(config.sdmc_path.size() - 1);
NCCHContainer ncch(std::make_shared<SDMCFile>(config.sdmc_path, relative_path, "rb"));
destination.append(GetTitleFileName(ncch)).append(".cia");
destination.append(GetTitleFileName(ncch))
.append(BuildTypeSuffixes.at(static_cast<std::size_t>(build_type)));
}
}
@@ -699,7 +723,7 @@ bool SDMCImporter::BuildCIA(CIABuildType type, const ContentSpecifier& specifier
cia_builder = std::make_unique<CIABuilder>();
}
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({
{
+8 -2
View File
@@ -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
+3
View File
@@ -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
+94
View File
@@ -0,0 +1,94 @@
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QDesktopWidget>
#include <QFileDialog>
#include <QFileInfo>
#include <QMessageBox>
#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<Ui::CIABuildDialog>()), is_dir(is_dir_) {
ui->setupUi(this);
const double scale = qApp->desktop()->logicalDpiX() / 96.0;
resize(static_cast<int>(width() * scale), static_cast<int>(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.<br>");
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<QString, Core::CIABuildType> 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};
}
+27
View File
@@ -0,0 +1,27 @@
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <memory>
#include <utility>
#include <QDialog>
#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<QString, Core::CIABuildType> GetResults() const;
private:
std::unique_ptr<Ui::CIABuildDialog> ui;
bool is_dir;
};
+125
View File
@@ -0,0 +1,125 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CIABuildDialog</class>
<widget class="QDialog" name="CIABuildDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>510</width>
<height>260</height>
</rect>
</property>
<property name="windowTitle">
<string>Build CIA</string>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel">
<property name="text">
<string>Destination:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="destination"/>
</item>
<item>
<widget class="QToolButton" name="destinationExplore">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox">
<property name="title">
<string>Build Type</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QRadioButton" name="standardButton">
<property name="text">
<string>Standard</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel">
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="text">
<string>Recommended for general use.&lt;br&gt;Decrypted CIA with decrypted contents and standard ticket.</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="pirateLegitButton">
<property name="text">
<string>Legit with standard ticket ("pirate legit")</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="pirateLegitLabel">
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="text">
<string>Encrypted CIA with legit TMD, encrypted contents and standard ticket.</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="legitButton">
<property name="text">
<string>Legit</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="legitLabel">
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="text">
<string>Encrypted CIA with legit TMD, encrypted contents and legit ticket.&lt;br&gt;WARNING: Legit ticket may include console identifying information!</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Ok|QDialogButtonBox::Cancel</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+25 -13
View File
@@ -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);