mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-02 16:49:04 +00:00
Add CIABuildDialog
This commit is contained in:
+28
-4
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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.<br>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.<br>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>
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user