mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-03 00:38:58 +00:00
core, frontend: Add 'Dump CXI file' option
Right click on an application in the Select Contents dialog.
This commit is contained in:
+42
-8
@@ -62,7 +62,7 @@ bool SDMCImporter::IsGood() const {
|
|||||||
return is_good;
|
return is_good;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDMCImporter::Abort() {
|
void SDMCImporter::AbortImporting() {
|
||||||
decryptor->Abort();
|
decryptor->Abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,10 +319,8 @@ std::vector<ContentSpecifier> SDMCImporter::ListContent() const {
|
|||||||
// Regex for half Title IDs
|
// Regex for half Title IDs
|
||||||
static const std::regex title_regex{"[0-9a-f]{8}"};
|
static const std::regex title_regex{"[0-9a-f]{8}"};
|
||||||
|
|
||||||
std::tuple<std::string, u64, EncryptionType, bool, std::vector<u16>> SDMCImporter::LoadTitleData(
|
static bool LoadTMD(const std::string& sdmc_path, const std::string& path, SDMCDecryptor& decryptor,
|
||||||
const std::string& path) const {
|
TitleMetadata& out) {
|
||||||
// Remove trailing '/'
|
|
||||||
const auto sdmc_path = config.sdmc_path.substr(0, config.sdmc_path.size() - 1);
|
|
||||||
|
|
||||||
std::string title_metadata;
|
std::string title_metadata;
|
||||||
const bool ret = FileUtil::ForeachDirectoryEntry(
|
const bool ret = FileUtil::ForeachDirectoryEntry(
|
||||||
@@ -344,16 +342,26 @@ std::tuple<std::string, u64, EncryptionType, bool, std::vector<u16>> SDMCImporte
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (ret) { // TMD not found
|
if (ret) { // TMD not found
|
||||||
return {};
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!FileUtil::Exists(sdmc_path + path + title_metadata)) {
|
if (!FileUtil::Exists(sdmc_path + path + title_metadata)) {
|
||||||
// Probably TMD is not directly inside, aborting.
|
// Probably TMD is not directly inside, aborting.
|
||||||
return {};
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return out.Load(decryptor.DecryptFile(path + title_metadata)) == ResultStatus::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<std::string, u64, EncryptionType, bool, std::vector<u16>> SDMCImporter::LoadTitleData(
|
||||||
|
const std::string& path) const {
|
||||||
|
// Remove trailing '/'
|
||||||
|
const auto sdmc_path = config.sdmc_path.substr(0, config.sdmc_path.size() - 1);
|
||||||
|
|
||||||
TitleMetadata tmd;
|
TitleMetadata tmd;
|
||||||
tmd.Load(decryptor->DecryptFile(path + title_metadata));
|
if (!LoadTMD(sdmc_path, path, *decryptor, tmd)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
const auto boot_content_path = fmt::format("{}{:08x}.app", path, tmd.GetBootContentID());
|
const auto boot_content_path = fmt::format("{}{:08x}.app", path, tmd.GetBootContentID());
|
||||||
|
|
||||||
@@ -390,6 +398,32 @@ std::tuple<std::string, u64, EncryptionType, bool, std::vector<u16>> SDMCImporte
|
|||||||
encryption, seed_crypto, smdh.GetIcon(false)};
|
encryption, seed_crypto, smdh.GetIcon(false)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SDMCImporter::DumpCXI(const ContentSpecifier& specifier, const std::string& destination,
|
||||||
|
const ProgressCallback& callback) {
|
||||||
|
|
||||||
|
if (specifier.type != ContentType::Application) {
|
||||||
|
LOG_ERROR(Core, "Unsupported specifier type {}", static_cast<int>(specifier.type));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto content_path = fmt::format("/title/{:08x}/{:08x}/content/", specifier.id >> 32,
|
||||||
|
(specifier.id & 0xFFFFFFFF));
|
||||||
|
TitleMetadata tmd;
|
||||||
|
if (!LoadTMD(config.sdmc_path, content_path, *decryptor, tmd)) {
|
||||||
|
LOG_ERROR(Core, "Could not load tmd");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto boot_content_path =
|
||||||
|
fmt::format("{}{:08x}.app", content_path, tmd.GetBootContentID());
|
||||||
|
dump_cxi_ncch = std::make_unique<NCCHContainer>(config.sdmc_path, boot_content_path);
|
||||||
|
return dump_cxi_ncch->DecryptToFile(destination, callback) == ResultStatus::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDMCImporter::AbortDumpCXI() {
|
||||||
|
dump_cxi_ncch->AbortDecryptToFile();
|
||||||
|
}
|
||||||
|
|
||||||
void SDMCImporter::ListTitle(std::vector<ContentSpecifier>& out) const {
|
void SDMCImporter::ListTitle(std::vector<ContentSpecifier>& out) const {
|
||||||
const auto ProcessDirectory = [this, &out, &sdmc_path = config.sdmc_path](ContentType type,
|
const auto ProcessDirectory = [this, &out, &sdmc_path = config.sdmc_path](ContentType type,
|
||||||
u64 high_id) {
|
u64 high_id) {
|
||||||
|
|||||||
+26
-7
@@ -79,6 +79,8 @@ struct Config {
|
|||||||
std::string system_archives_path; ///< Path to system archives.
|
std::string system_archives_path; ///< Path to system archives.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class NCCHContainer;
|
||||||
|
|
||||||
class SDMCImporter {
|
class SDMCImporter {
|
||||||
public:
|
public:
|
||||||
/// (current_size, total_size)
|
/// (current_size, total_size)
|
||||||
@@ -93,24 +95,37 @@ public:
|
|||||||
~SDMCImporter();
|
~SDMCImporter();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Aborts a specific content by its specifier.
|
* Imports a specific content by its specifier.
|
||||||
* Blocks, but can be aborted on another thread if needed.
|
* Blocks, but can be aborted on another thread if needed.
|
||||||
* @return true on success, false otherwise
|
* @return true on success, false otherwise
|
||||||
*/
|
*/
|
||||||
bool ImportContent(const ContentSpecifier& specifier,
|
bool ImportContent(const ContentSpecifier& specifier,
|
||||||
const ProgressCallback& callback = [](std::size_t, std::size_t) {});
|
const ProgressCallback& callback = [](std::size_t, std::size_t) {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aborts current importing.
|
||||||
|
*/
|
||||||
|
void AbortImporting();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dumps a content to CXI.
|
||||||
|
* Blocks, but can be aborted on another thread.
|
||||||
|
* @return true on success, false otherwise
|
||||||
|
*/
|
||||||
|
bool DumpCXI(const ContentSpecifier& specifier, const std::string& destination,
|
||||||
|
const ProgressCallback& callback = [](std::size_t, std::size_t) {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aborts current CXI dumping.
|
||||||
|
*/
|
||||||
|
void AbortDumpCXI();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes/Cleans up a content. Used for deleting contents that have
|
* Deletes/Cleans up a content. Used for deleting contents that have
|
||||||
* not been fully imported.
|
* not been fully imported.
|
||||||
*/
|
*/
|
||||||
void DeleteContent(const ContentSpecifier& specifier);
|
void DeleteContent(const ContentSpecifier& specifier);
|
||||||
|
|
||||||
/**
|
|
||||||
* Aborts current importing.
|
|
||||||
*/
|
|
||||||
void Abort();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a list of dumpable content specifiers.
|
* Gets a list of dumpable content specifiers.
|
||||||
*/
|
*/
|
||||||
@@ -147,11 +162,15 @@ private:
|
|||||||
* Required to end with '/'.
|
* Required to end with '/'.
|
||||||
* @return {name, extdata_id, encryption, seed_crypto, icon}
|
* @return {name, extdata_id, encryption, seed_crypto, icon}
|
||||||
*/
|
*/
|
||||||
std::tuple<std::string, u64, EncryptionType, bool, std::vector<u16>> LoadTitleData(const std::string& path) const;
|
std::tuple<std::string, u64, EncryptionType, bool, std::vector<u16>> LoadTitleData(
|
||||||
|
const std::string& path) const;
|
||||||
|
|
||||||
bool is_good{};
|
bool is_good{};
|
||||||
Config config;
|
Config config;
|
||||||
std::unique_ptr<SDMCDecryptor> decryptor;
|
std::unique_ptr<SDMCDecryptor> decryptor;
|
||||||
|
|
||||||
|
// The NCCH used to dump CXIs.
|
||||||
|
std::unique_ptr<NCCHContainer> dump_cxi_ncch;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -15,10 +15,10 @@
|
|||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/data_container.h"
|
#include "core/data_container.h"
|
||||||
|
#include "core/importer.h"
|
||||||
#include "core/key/key.h"
|
#include "core/key/key.h"
|
||||||
#include "core/ncch/ncch_container.h"
|
#include "core/ncch/ncch_container.h"
|
||||||
#include "core/ncch/seed_db.h"
|
#include "core/ncch/seed_db.h"
|
||||||
#include "core/quick_decryptor.h"
|
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
@@ -440,10 +440,13 @@ ResultStatus NCCHContainer::DecryptToFile(const std::string& destination,
|
|||||||
|
|
||||||
if (!is_encrypted) {
|
if (!is_encrypted) {
|
||||||
// Simply copy everything
|
// Simply copy everything
|
||||||
QuickDecryptor<SDMCFile, FileUtil::IOFile> decryptor;
|
file->Seek(0, SEEK_SET);
|
||||||
|
|
||||||
const auto size = file->GetSize();
|
const auto size = file->GetSize();
|
||||||
decryptor.Reset(size);
|
decryptor.Reset(size);
|
||||||
decryptor.DecryptAndWriteFile(file, size, dest_file, callback);
|
|
||||||
|
const bool ret = decryptor.DecryptAndWriteFile(file, size, dest_file, callback);
|
||||||
|
return ret ? ResultStatus::Success : ResultStatus::Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write NCCH header
|
// Write NCCH header
|
||||||
@@ -463,7 +466,6 @@ ResultStatus NCCHContainer::DecryptToFile(const std::string& destination,
|
|||||||
return ResultStatus::Error;
|
return ResultStatus::Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
QuickDecryptor<SDMCFile, FileUtil::IOFile> decryptor;
|
|
||||||
const auto total_size =
|
const auto total_size =
|
||||||
file->GetSize() - sizeof(NCCH_Header) - sizeof(ExHeader_Header) - sizeof(ExeFs_Header);
|
file->GetSize() - sizeof(NCCH_Header) - sizeof(ExHeader_Header) - sizeof(ExeFs_Header);
|
||||||
decryptor.Reset(total_size);
|
decryptor.Reset(total_size);
|
||||||
@@ -477,12 +479,19 @@ ResultStatus NCCHContainer::DecryptToFile(const std::string& destination,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (aborted.exchange(false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
file->Seek(written, SEEK_SET);
|
file->Seek(written, SEEK_SET);
|
||||||
ASSERT_MSG(written <= offset, "Offsets are not in increasing order");
|
ASSERT_MSG(written <= offset, "Offsets are not in increasing order");
|
||||||
if (!decryptor.DecryptAndWriteFile(file, offset - written, dest_file, callback)) {
|
if (!decryptor.DecryptAndWriteFile(file, offset - written, dest_file, callback)) {
|
||||||
LOG_ERROR(Core, "Could not write data before {} to {}", name, destination);
|
LOG_ERROR(Core, "Could not write data before {} to {}", name, destination);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (aborted.exchange(false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!decryptor.DecryptAndWriteFile(file, size, dest_file, callback, decrypt, key, ctr,
|
if (!decryptor.DecryptAndWriteFile(file, size, dest_file, callback, decrypt, key, ctr,
|
||||||
aes_seek_pos)) {
|
aes_seek_pos)) {
|
||||||
LOG_ERROR(Core, "Could not write {} to {}", name, destination);
|
LOG_ERROR(Core, "Could not write {} to {}", name, destination);
|
||||||
@@ -536,6 +545,11 @@ ResultStatus NCCHContainer::DecryptToFile(const std::string& destination,
|
|||||||
return ResultStatus::Success;
|
return ResultStatus::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NCCHContainer::AbortDecryptToFile() {
|
||||||
|
aborted = true;
|
||||||
|
decryptor.Abort();
|
||||||
|
}
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
struct RomFSIVFCHeader {
|
struct RomFSIVFCHeader {
|
||||||
u32_le magic;
|
u32_le magic;
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
#include "core/decryptor.h"
|
#include "core/decryptor.h"
|
||||||
#include "core/importer.h"
|
|
||||||
#include "core/result_status.h"
|
#include "core/result_status.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
@@ -196,6 +195,8 @@ struct ExHeader_Header {
|
|||||||
|
|
||||||
static_assert(sizeof(ExHeader_Header) == 0x800, "ExHeader structure size is wrong");
|
static_assert(sizeof(ExHeader_Header) == 0x800, "ExHeader structure size is wrong");
|
||||||
|
|
||||||
|
enum class EncryptionType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper which implements an interface to deal with NCCH containers which can
|
* Helper which implements an interface to deal with NCCH containers which can
|
||||||
* contain ExeFS archives or RomFS archives for games or other applications.
|
* contain ExeFS archives or RomFS archives for games or other applications.
|
||||||
@@ -272,6 +273,11 @@ public:
|
|||||||
ResultStatus DecryptToFile(const std::string& destination,
|
ResultStatus DecryptToFile(const std::string& destination,
|
||||||
const ProgressCallback& callback = [](std::size_t, std::size_t) {});
|
const ProgressCallback& callback = [](std::size_t, std::size_t) {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aborts DecryptToFile. Simply aborts the decryptor.
|
||||||
|
*/
|
||||||
|
void AbortDecryptToFile();
|
||||||
|
|
||||||
NCCH_Header ncch_header;
|
NCCH_Header ncch_header;
|
||||||
ExHeader_Header exheader_header;
|
ExHeader_Header exheader_header;
|
||||||
ExeFs_Header exefs_header;
|
ExeFs_Header exefs_header;
|
||||||
@@ -298,6 +304,10 @@ private:
|
|||||||
std::string filepath;
|
std::string filepath;
|
||||||
std::shared_ptr<SDMCFile> file;
|
std::shared_ptr<SDMCFile> file;
|
||||||
std::shared_ptr<SDMCFile> exefs_file;
|
std::shared_ptr<SDMCFile> exefs_file;
|
||||||
|
|
||||||
|
// Used for DecryptToFile
|
||||||
|
QuickDecryptor<SDMCFile, FileUtil::IOFile> decryptor;
|
||||||
|
std::atomic_bool aborted{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ 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/import_job.cpp
|
||||||
|
helpers/import_job.h
|
||||||
|
helpers/progressive_job.cpp
|
||||||
|
helpers/progressive_job.h
|
||||||
import_dialog.cpp
|
import_dialog.cpp
|
||||||
import_dialog.h
|
import_dialog.h
|
||||||
import_dialog.ui
|
import_dialog.ui
|
||||||
import_job.cpp
|
|
||||||
import_job.h
|
|
||||||
main.cpp
|
main.cpp
|
||||||
main.h
|
main.h
|
||||||
main.ui
|
main.ui
|
||||||
|
|||||||
@@ -2,9 +2,7 @@
|
|||||||
// 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/import_job.h"
|
#include "frontend/helpers/import_job.h"
|
||||||
|
|
||||||
#include "common/assert.h"
|
|
||||||
|
|
||||||
ImportJob::ImportJob(QObject* parent, Core::SDMCImporter& importer_,
|
ImportJob::ImportJob(QObject* parent, Core::SDMCImporter& importer_,
|
||||||
std::vector<Core::ContentSpecifier> contents_)
|
std::vector<Core::ContentSpecifier> contents_)
|
||||||
@@ -39,5 +37,5 @@ void ImportJob::run() {
|
|||||||
|
|
||||||
void ImportJob::Cancel() {
|
void ImportJob::Cancel() {
|
||||||
cancelled.store(true);
|
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 <cmath>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
|
#include <QFileDialog>
|
||||||
#include <QFutureWatcher>
|
#include <QFutureWatcher>
|
||||||
|
#include <QMenu>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QProgressDialog>
|
#include <QProgressDialog>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
@@ -14,8 +16,9 @@
|
|||||||
#include <QtConcurrent/QtConcurrentRun>
|
#include <QtConcurrent/QtConcurrentRun>
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/scope_exit.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_dialog.h"
|
||||||
#include "frontend/import_job.h"
|
|
||||||
#include "ui_import_dialog.h"
|
#include "ui_import_dialog.h"
|
||||||
|
|
||||||
QString ReadableByteSize(qulonglong size) {
|
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(2, width() * 0.14);
|
||||||
ui->main->setColumnWidth(3, width() * 0.17);
|
ui->main->setColumnWidth(3, width() * 0.17);
|
||||||
ui->main->setColumnWidth(4, width() * 0.08);
|
ui->main->setColumnWidth(4, width() * 0.08);
|
||||||
|
|
||||||
|
connect(ui->main, &QTreeWidget::customContextMenuRequested, this, &ImportDialog::OnContextMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImportDialog::~ImportDialog() = default;
|
ImportDialog::~ImportDialog() = default;
|
||||||
@@ -502,3 +507,92 @@ void ImportDialog::StartImporting() {
|
|||||||
|
|
||||||
job->start();
|
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 <QDialog>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include "core/importer.h"
|
#include "core/importer.h"
|
||||||
|
#include "core/ncch/ncch_container.h"
|
||||||
|
|
||||||
class QTreeWidgetItem;
|
class QTreeWidgetItem;
|
||||||
|
|
||||||
@@ -38,6 +39,12 @@ private:
|
|||||||
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;
|
||||||
|
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::unique_ptr<Ui::ImportDialog> ui;
|
||||||
|
|
||||||
std::string user_path;
|
std::string user_path;
|
||||||
|
|||||||
@@ -41,6 +41,9 @@
|
|||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTreeWidget" name="main">
|
<widget class="QTreeWidget" name="main">
|
||||||
|
<property name="contextMenuPolicy">
|
||||||
|
<enum>Qt::CustomContextMenu</enum>
|
||||||
|
</property>
|
||||||
<column>
|
<column>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string/>
|
<string/>
|
||||||
|
|||||||
Reference in New Issue
Block a user