mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-03 08:39:04 +00:00
core, frontend: Add 'Dump CXI file' option
Right click on an application in the Select Contents dialog.
This commit is contained in:
@@ -9,11 +9,13 @@ endif()
|
||||
file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/themes/*)
|
||||
|
||||
add_executable(threeSD
|
||||
helpers/import_job.cpp
|
||||
helpers/import_job.h
|
||||
helpers/progressive_job.cpp
|
||||
helpers/progressive_job.h
|
||||
import_dialog.cpp
|
||||
import_dialog.h
|
||||
import_dialog.ui
|
||||
import_job.cpp
|
||||
import_job.h
|
||||
main.cpp
|
||||
main.h
|
||||
main.ui
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "frontend/import_job.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "frontend/helpers/import_job.h"
|
||||
|
||||
ImportJob::ImportJob(QObject* parent, Core::SDMCImporter& importer_,
|
||||
std::vector<Core::ContentSpecifier> contents_)
|
||||
@@ -39,5 +37,5 @@ void ImportJob::run() {
|
||||
|
||||
void ImportJob::Cancel() {
|
||||
cancelled.store(true);
|
||||
importer.Abort();
|
||||
importer.AbortImporting();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2020 Pengfei Zhu
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "frontend/helpers/progressive_job.h"
|
||||
|
||||
ProgressiveJob::ProgressiveJob(QObject* parent, const ExecuteFunc& execute_,
|
||||
const AbortFunc& abort_)
|
||||
: QThread(parent), execute(execute_), abort(abort_) {}
|
||||
|
||||
ProgressiveJob::~ProgressiveJob() = default;
|
||||
|
||||
void ProgressiveJob::run() {
|
||||
const bool ret = execute(
|
||||
[this](std::size_t current, std::size_t total) { emit ProgressUpdated(current, total); });
|
||||
|
||||
if (ret || canceled) {
|
||||
emit Completed();
|
||||
} else {
|
||||
emit ErrorOccured();
|
||||
}
|
||||
}
|
||||
|
||||
void ProgressiveJob::Cancel() {
|
||||
canceled = true;
|
||||
abort();
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2019 threeSD Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QThread>
|
||||
#include "common/common_types.h"
|
||||
|
||||
/**
|
||||
* Lightweight wrapper around QThread, for easy use with progressive jobs.
|
||||
*/
|
||||
class ProgressiveJob : public QThread {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using ProgressCallback = std::function<void(std::size_t, std::size_t)>;
|
||||
using ExecuteFunc = std::function<bool(const ProgressCallback&)>;
|
||||
using AbortFunc = std::function<void()>;
|
||||
|
||||
explicit ProgressiveJob(QObject* parent, const ExecuteFunc& execute, const AbortFunc& abort);
|
||||
~ProgressiveJob() override;
|
||||
|
||||
void run() override;
|
||||
void Cancel();
|
||||
|
||||
signals:
|
||||
void ProgressUpdated(u64 current, u64 total);
|
||||
void Completed();
|
||||
void ErrorOccured();
|
||||
|
||||
private:
|
||||
ExecuteFunc execute;
|
||||
AbortFunc abort;
|
||||
bool canceled{};
|
||||
};
|
||||
@@ -6,7 +6,9 @@
|
||||
#include <cmath>
|
||||
#include <unordered_map>
|
||||
#include <QCheckBox>
|
||||
#include <QFileDialog>
|
||||
#include <QFutureWatcher>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QProgressDialog>
|
||||
#include <QPushButton>
|
||||
@@ -14,8 +16,9 @@
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "frontend/helpers/import_job.h"
|
||||
#include "frontend/helpers/progressive_job.h"
|
||||
#include "frontend/import_dialog.h"
|
||||
#include "frontend/import_job.h"
|
||||
#include "ui_import_dialog.h"
|
||||
|
||||
QString ReadableByteSize(qulonglong size) {
|
||||
@@ -119,6 +122,8 @@ ImportDialog::ImportDialog(QWidget* parent, const Core::Config& config)
|
||||
ui->main->setColumnWidth(2, width() * 0.14);
|
||||
ui->main->setColumnWidth(3, width() * 0.17);
|
||||
ui->main->setColumnWidth(4, width() * 0.08);
|
||||
|
||||
connect(ui->main, &QTreeWidget::customContextMenuRequested, this, &ImportDialog::OnContextMenu);
|
||||
}
|
||||
|
||||
ImportDialog::~ImportDialog() = default;
|
||||
@@ -502,3 +507,92 @@ void ImportDialog::StartImporting() {
|
||||
|
||||
job->start();
|
||||
}
|
||||
|
||||
Core::ContentSpecifier ImportDialog::SpecifierFromItem(QTreeWidgetItem* item) const {
|
||||
const auto* checkBox = static_cast<QCheckBox*>(ui->main->itemWidget(item, 0));
|
||||
return contents[checkBox->property("id").toInt()];
|
||||
}
|
||||
|
||||
void ImportDialog::OnContextMenu(const QPoint& point) {
|
||||
QTreeWidgetItem* item = ui->main->itemAt(point.x(), point.y());
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool title_view = ui->title_view_button->isChecked();
|
||||
|
||||
QMenu context_menu;
|
||||
if (item->parent()) { // Second level
|
||||
const auto& specifier = SpecifierFromItem(item);
|
||||
if (specifier.type != Core::ContentType::Application) {
|
||||
return;
|
||||
}
|
||||
|
||||
QAction* dump_cxi = context_menu.addAction(tr("Dump CXI file"));
|
||||
connect(dump_cxi, &QAction::triggered, [this, specifier] { StartDumpingCXI(specifier); });
|
||||
} else { // Top level
|
||||
if (!title_view) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < item->childCount(); ++i) {
|
||||
const auto& specifier = SpecifierFromItem(item->child(i));
|
||||
if (specifier.type == Core::ContentType::Application) {
|
||||
QAction* dump_base_cxi = context_menu.addAction(tr("Dump Base CXI file"));
|
||||
connect(dump_base_cxi, &QAction::triggered,
|
||||
[this, specifier] { StartDumpingCXI(specifier); });
|
||||
break;
|
||||
}
|
||||
// TODO: Add updates, etc
|
||||
}
|
||||
}
|
||||
context_menu.exec(ui->main->viewport()->mapToGlobal(point));
|
||||
}
|
||||
|
||||
void ImportDialog::StartDumpingCXI(const Core::ContentSpecifier& specifier) {
|
||||
const QString path = QFileDialog::getSaveFileName(this, tr("Dump CXI file"), last_dump_cxi_path,
|
||||
tr("CTR Executable Image (*.CXI)"));
|
||||
if (path.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
last_dump_cxi_path = QFileInfo(path).path();
|
||||
|
||||
// Try to map total_size to int range
|
||||
// This is equal to ceil(total_size / INT_MAX)
|
||||
const u64 multiplier = (specifier.maximum_size + std::numeric_limits<int>::max() - 1) /
|
||||
std::numeric_limits<int>::max();
|
||||
|
||||
auto* dialog = new QProgressDialog(tr("Initializing..."), tr("Cancel"), 0,
|
||||
static_cast<int>(specifier.maximum_size / multiplier), this);
|
||||
dialog->setWindowModality(Qt::WindowModal);
|
||||
dialog->setMinimumDuration(0);
|
||||
dialog->setValue(0);
|
||||
|
||||
auto* job = new ProgressiveJob(
|
||||
this,
|
||||
[this, specifier, path](const ProgressiveJob::ProgressCallback& callback) {
|
||||
if (!importer.DumpCXI(specifier, path.toStdString(), callback)) {
|
||||
FileUtil::Delete(path.toStdString());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[this] { importer.AbortDumpCXI(); });
|
||||
|
||||
connect(job, &ProgressiveJob::ProgressUpdated, this,
|
||||
[this, specifier, dialog, multiplier](u64 current, u64 total) {
|
||||
dialog->setValue(static_cast<int>(current / multiplier));
|
||||
dialog->setLabelText(tr("%1 / %2")
|
||||
.arg(ReadableByteSize(current))
|
||||
.arg(ReadableByteSize(specifier.maximum_size)));
|
||||
});
|
||||
connect(job, &ProgressiveJob::ErrorOccured, this, [this, dialog] {
|
||||
QMessageBox::critical(this, tr("threeSD"), tr("Failed to dump CXI!"));
|
||||
dialog->hide();
|
||||
});
|
||||
connect(job, &ProgressiveJob::Completed, this,
|
||||
[this, dialog] { dialog->setValue(dialog->maximum()); });
|
||||
connect(dialog, &QProgressDialog::canceled, this, [this, job] { job->Cancel(); });
|
||||
|
||||
job->start();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <QDialog>
|
||||
#include <QPixmap>
|
||||
#include "core/importer.h"
|
||||
#include "core/ncch/ncch_container.h"
|
||||
|
||||
class QTreeWidgetItem;
|
||||
|
||||
@@ -38,6 +39,12 @@ private:
|
||||
std::size_t id, QString replace_name = {},
|
||||
QPixmap replace_icon = {});
|
||||
|
||||
Core::ContentSpecifier SpecifierFromItem(QTreeWidgetItem* item) const;
|
||||
void OnContextMenu(const QPoint& point);
|
||||
void StartDumpingCXI(const Core::ContentSpecifier& content);
|
||||
Core::NCCHContainer dump_cxi_container; // NCCH container used for dumping CXI
|
||||
QString last_dump_cxi_path; // Used for recording last path in StartDumpingCXI
|
||||
|
||||
std::unique_ptr<Ui::ImportDialog> ui;
|
||||
|
||||
std::string user_path;
|
||||
|
||||
@@ -41,6 +41,9 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="main">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string/>
|
||||
|
||||
Reference in New Issue
Block a user